Failed Conditions
Pull Request — master (#13)
by Adrien
05:33 queued 02:11
created

Acl::addRole()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
c 0
b 0
f 0
nc 1
nop 2
dl 0
loc 5
ccs 3
cts 3
cp 1
crap 1
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Ecodev\Felix\Acl;
6
7
use Doctrine\Common\Util\ClassUtils;
8
use Ecodev\Felix\Model\CurrentUser;
9
use Ecodev\Felix\Model\Model;
10
use Laminas\Permissions\Acl\Assertion\AssertionInterface;
11
use Laminas\Permissions\Acl\Resource\ResourceInterface;
12
use Laminas\Permissions\Acl\Role\RoleInterface;
13
14
class Acl extends \Laminas\Permissions\Acl\Acl
15
{
16
    /**
17
     * The message explaining the last denial.
18
     */
19
    private ?string $message = null;
20
21
    /**
22
     * @var string[]
23
     */
24
    private array $reasons = [];
25
26
    private DebugAcl $debugAcl;
27
28 3
    public function __construct()
29
    {
30 3
        $this->debugAcl = new DebugAcl();
31
    }
32
33 3
    public function addRole($role, $parents = null)
34
    {
35 3
        $this->debugAcl->addRole($role, $parents);
36
37 3
        return parent::addRole($role, $parents);
38
    }
39
40 3
    public function addResource($resource, $parent = null)
41
    {
42 3
        $this->debugAcl->addResource($resource, $parent);
43
44 3
        return parent::addResource($resource, $parent);
45
    }
46
47 3
    public function allow($roles = null, $resources = null, $privileges = null, ?AssertionInterface $assert = null)
48
    {
49 3
        $this->debugAcl->allow($roles, $resources, $privileges, $assert);
50
51 3
        return parent::allow($roles, $resources, $privileges, $assert);
52
    }
53
54
    public function deny($roles = null, $resources = null, $privileges = null, ?AssertionInterface $assert = null)
55
    {
56
        $this->debugAcl->deny($roles, $resources, $privileges, $assert);
57
58
        return parent::deny($roles, $resources, $privileges, $assert);
59
    }
60
61 3
    protected function createModelResource(string $class): ModelResource
1 ignored issue
show
Bug introduced by
The type Ecodev\Felix\Acl\ModelResource was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
62
    {
63 3
        $resource = new ModelResource($class);
64 3
        $this->addResource($resource);
65
66 3
        return $resource;
67
    }
68
69
    /**
70
     * Override parent to provide compatibility with MultipleRoles.
71
     *
72
     * @param RoleInterface|string $role
73
     * @param ResourceInterface|string $resource
74
     * @param string $privilege
75
     */
76 3
    public function isAllowed($role = null, $resource = null, $privilege = null): bool
77
    {
78
        // Normalize roles
79 3
        if ($role instanceof MultipleRoles) {
80 1
            $roles = $role->getRoles();
81
        } else {
82 2
            $roles = [$role];
83
        }
84
85
        // If at least one role is allowed, then return early
86 3
        foreach ($roles as $role) {
87 3
            if (parent::isAllowed($role, $resource, $privilege)) {
88 1
                return true;
89
            }
90
        }
91
92 3
        return false;
93
    }
94
95
    /**
96
     * Return whether the current user is allowed to do something.
97
     *
98
     * This should be the main method to do all ACL checks.
99
     */
100 3
    public function isCurrentUserAllowed(Model $model, string $privilege): bool
101
    {
102 3
        $resource = new ModelResource($this->getClass($model), $model);
103 3
        $role = $this->getCurrentRole();
104 3
        $this->reasons = [];
105
106 3
        $isAllowed = $this->isAllowed($role, $resource, $privilege);
107
108 3
        $this->message = $this->buildMessage($resource, $privilege, $role, $isAllowed);
109
110 3
        return $isAllowed;
111
    }
112
113
    /**
114
     * Set the reason for rejection that will be shown to end-user.
115
     *
116
     * This method always return false for usage convenience and should be used by all assertions,
117
     * instead of only return false themselves.
118
     *
119
     * @return false
120
     */
121 2
    public function reject(string $reason): bool
122
    {
123 2
        $this->reasons[] = $reason;
124
125 2
        return false;
126
    }
127
128 3
    private function getClass(Model $resource): string
129
    {
130 3
        return ClassUtils::getRealClass($resource::class);
131
    }
132
133 3
    private function getCurrentRole(): MultipleRoles|string
134
    {
135 3
        $user = CurrentUser::get();
136 3
        if (!$user) {
137 1
            return 'anonymous';
138
        }
139
140 3
        return $user->getRole();
141
    }
142
143 3
    private function buildMessage(ModelResource $resource, ?string $privilege, MultipleRoles|string $role, bool $isAllowed): ?string
144
    {
145 3
        if ($isAllowed) {
146 1
            return null;
147
        }
148
149 3
        $resource = $resource->getName();
150
151 3
        $user = CurrentUser::get();
152 3
        $userName = $user ? 'User "' . $user->getLogin() . '"' : 'Non-logged user';
153 3
        $privilege ??= 'NULL';
154
155 3
        $message = "$userName with role $role is not allowed on resource \"$resource\" with privilege \"$privilege\"";
156
157 3
        $count = count($this->reasons);
158 3
        if ($count === 1) {
159 1
            $message .= ' because ' . $this->reasons[0];
160 3
        } elseif ($count) {
161 1
            $list = array_map(fn ($reason) => '- ' . $reason, $this->reasons);
162 1
            $message .= ' because:' . PHP_EOL . PHP_EOL . implode(PHP_EOL, $list);
163
        }
164
165 3
        return $message;
166
    }
167
168
    /**
169
     * Returns the message explaining the last denial, if any.
170
     */
171 3
    public function getLastDenialMessage(): ?string
172
    {
173 3
        return $this->message;
174
    }
175
176
    /**
177
     * @return array<array{resource:string, privileges: array<int, array{privilege:null|string, allowed: bool, allowIf: string[], denyIf: string[]}>}>
178
     */
179
    public function show(MultipleRoles|string $role): array
180
    {
181
        $result = [];
182
        /** @var string[] $resources */
183
        $resources = $this->getResources();
184
        sort($resources);
185
186
        foreach ($resources as $resource) {
187
            $privileges = [];
188
            foreach ($this->debugAcl->getPrivileges() as $privilege) {
189
                $privileges[] = $this->debugAcl->show($role, $resource, $privilege);
190
            }
191
192
            $result[] = [
193
                'resource' => $resource,
194
                'privileges' => $privileges,
195
            ];
196
        }
197
198
        ksort($result);
199
200
        return $result;
201
    }
202
}
203