Completed
Branch feature/currentUserRefactoring (c13c1d)
by Schlaefer
04:13
created

Permissions::bootstrapCategories()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 4
nop 0
dl 0
loc 25
rs 9.52
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Saito - The Threaded Web Forum
7
 *
8
 * @copyright Copyright (c) the Saito Project Developers
9
 * @link https://github.com/Schlaefer/Saito
10
 * @license http://opensource.org/licenses/MIT
11
 */
12
13
namespace Saito\User\Permission;
14
15
use App\Model\Table\CategoriesTable;
16
use Cake\Cache\Cache;
17
use InvalidArgumentException;
18
use Saito\RememberTrait;
19
use Saito\User\ForumsUserInterface;
20
use Saito\User\Permission\Identifier\IdentifierInterface;
21
use Saito\User\Permission\Roles;
22
use Stopwatch\Lib\Stopwatch;
23
24
/**
25
 * Class Permission
26
 *
27
 * Implements simple access control scheme.
28
 *
29
 * @package Saito\User
30
 */
31
class Permissions
32
{
33
    use RememberTrait;
34
35
    /** @var Roles */
36
    protected $roles;
37
38
    /** @var PermissionConfig */
39
    protected $PermissionConfig;
40
41
    /** @var CategoriesTable */
42
    protected $categories;
43
44
    /**
45
     * Constructor
46
     *
47
     * @param Roles $roles The roles
48
     * @param PermissionConfig $permissionConfig The config
49
     * @param CategoriesTable $categories Categories for accession permissions
50
     */
51
    public function __construct(Roles $roles, PermissionConfig $permissionConfig, CategoriesTable $categories)
52
    {
53
        Stopwatch::start('Permission::__construct()');
54
        $this->roles = $roles;
55
        $this->PermissionConfig = $permissionConfig;
56
        $this->categories = $categories;
57
58
        $categories = Cache::remember(
59
            'saito.core.permission.categories',
60
            function () {
61
                return $this->bootstrapCategories();
62
            }
63
        );
64
        foreach ($categories as $resource) {
65
            $this->PermissionConfig->allowRole($resource['resource'], $resource['role']);
66
        }
67
68
        Stopwatch::stop('Permission::__construct()');
69
    }
70
71
    /**
72
     * Check if access to resource is allowed.
73
     *
74
     * @param ForumsUserInterface $user CurrentUser
75
     * @param string $resource Resource
76
     * @param IdentifierInterface ...$identifiers Identifiers
77
     * @return bool
78
     */
79
    public function check(ForumsUserInterface $user, string $resource, IdentifierInterface ...$identifiers): bool
80
    {
81
        /// Force allow all check
82
        $force = $this->PermissionConfig->getForce($resource);
83
        if ($force) {
84
            return $force->check($resource);
85
        }
86
87
        $roleObject = null;
88
89
        if (!empty($identifiers)) {
90
            foreach ($identifiers as $identifier) {
91
                $type = $identifier->type();
92
                switch ($type) {
93
                    case ('owner'):
94
                        /// Owner check
95
                        foreach ($this->PermissionConfig->getOwner($resource) as $allowance) {
96
                            if ($allowance->check($resource, $user, $identifier->get())) {
97
                                return true;
98
                            }
99
                        }
100
                        break;
101
                    case ('role'):
102
                        // Just remember if there's a role object. Performed below.
103
                        $roleObject = $identifier->get();
104
                        break;
105
                    default:
106
                        new InvalidArgumentException(
107
                            sprintf('Unknown identifier type "%s" in permissin check.', $type)
108
                        );
109
                }
110
            }
111
        }
112
113
        /// Role check
114
        $roleAllowances = $this->PermissionConfig->getRole($resource);
115
        if (!empty($roleAllowances)) {
116
            foreach ($roleAllowances as $allowance) {
117
                $role = $user->getRole();
118
                $roles = $this->roles->get($role);
119
                if ($allowance->check($resource, $roles, $roleObject)) {
120
                    return true;
121
                }
122
            }
123
        }
124
125
        return false;
126
    }
127
128
    /**
129
     * Gets the roles object
130
     *
131
     * @return Roles
132
     */
133
    public function getRoles(): Roles
134
    {
135
        return $this->roles;
136
    }
137
138
    /**
139
     * convert category-accessions and insert them as resources
140
     *
141
     * Resource: `saito.core.category.<category-ID>.<action>`
142
     *
143
     * @return array [['resource' => <Resource>, 'role' => <role-type>]]
144
     */
145
    protected function bootstrapCategories(): array
146
    {
147
        $resources = [];
148
        $categories = $this->categories->getAllCategories();
149
        $roles = $this->roles->getAvailable(true);
150
        $accessions = array_combine(array_column($roles, 'id'), array_column($roles, 'type'));
151
        $actions = [
152
            'read' => 'accession',
153
            'thread' => 'accession_new_thread',
154
            'answer' => 'accession_new_posting'
155
        ];
156
        foreach ($categories as $category) {
157
            foreach ($actions as $action => $field) {
158
                if (empty($accessions[$category->get($field)])) {
159
                    continue;
160
                }
161
                $role = $accessions[$category->get($field)];
162
                $categoryId = $category->get('id');
163
                $resource = "saito.core.category.{$categoryId}.{$action}";
164
                $resources[] = ['resource' => $resource, 'role' => $role];
165
            }
166
        }
167
168
        return $resources;
169
    }
170
}
171