Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
248 / 248 |
|
100.00% |
35 / 35 |
CRAP | |
100.00% |
1 / 1 |
Acl | |
100.00% |
248 / 248 |
|
100.00% |
35 / 35 |
169 | |
100.00% |
1 / 1 |
__construct | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
8 | |||
getRole | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
getRoles | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
hasRole | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
addRole | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
4 | |||
addRoles | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
getResource | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
hasResource | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
addResource | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getResources | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
addResources | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
setStrict | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
isStrict | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
allow | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
8 | |||
removeAllowRule | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
12 | |||
deny | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
8 | |||
removeDenyRule | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
12 | |||
isAllowed | |
100.00% |
33 / 33 |
|
100.00% |
1 / 1 |
20 | |||
isAllowedMany | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
6 | |||
isAllowedManyStrict | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
isDenied | |
100.00% |
27 / 27 |
|
100.00% |
1 / 1 |
16 | |||
isDeniedMany | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
6 | |||
isDeniedManyStrict | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
createAssertion | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
deleteAssertion | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
hasAssertionKey | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
getAssertionKey | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
4 | |||
addPolicy | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 | |||
hasPolicies | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
evaluatePolicies | |
100.00% |
23 / 23 |
|
100.00% |
1 / 1 |
19 | |||
evaluatePolicy | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
5 | |||
verifyRole | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
4 | |||
verifyResource | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
4 | |||
generateAssertionKey | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
traverseChildren | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 |
1 | <?php |
2 | /** |
3 | * Pop PHP Framework (http://www.popphp.org/) |
4 | * |
5 | * @link https://github.com/popphp/popphp-framework |
6 | * @author Nick Sagona, III <dev@nolainteractive.com> |
7 | * @copyright Copyright (c) 2009-2023 NOLA Interactive, LLC. (http://www.nolainteractive.com) |
8 | * @license http://www.popphp.org/license New BSD License |
9 | */ |
10 | |
11 | /** |
12 | * @namespace |
13 | */ |
14 | namespace Pop\Acl; |
15 | |
16 | use Pop\Acl\Assertion\AssertionInterface; |
17 | |
18 | /** |
19 | * ACL class |
20 | * |
21 | * @category Pop |
22 | * @package Pop\Acl |
23 | * @author Nick Sagona, III <dev@nolainteractive.com> |
24 | * @copyright Copyright (c) 2009-2023 NOLA Interactive, LLC. (http://www.nolainteractive.com) |
25 | * @license http://www.popphp.org/license New BSD License |
26 | * @version 3.4.0 |
27 | */ |
28 | class Acl |
29 | { |
30 | |
31 | /** |
32 | * Array of roles |
33 | * @var array |
34 | */ |
35 | protected $roles = []; |
36 | |
37 | /** |
38 | * Array of resources |
39 | * @var array |
40 | */ |
41 | protected $resources = []; |
42 | |
43 | /** |
44 | * Array of allowed roles, resources and permissions |
45 | * @var array |
46 | */ |
47 | protected $allowed = []; |
48 | |
49 | /** |
50 | * Array of denied roles, resources and permissions |
51 | * @var array |
52 | */ |
53 | protected $denied = []; |
54 | |
55 | /** |
56 | * Array of assertions |
57 | * @var array |
58 | */ |
59 | protected $assertions = [ |
60 | 'allowed' => [], |
61 | 'denied' => [] |
62 | ]; |
63 | |
64 | /** |
65 | * Array of policies |
66 | * @var array |
67 | */ |
68 | protected $policies = []; |
69 | |
70 | /** |
71 | * Strict flag |
72 | * @var boolean |
73 | */ |
74 | protected $strict = false; |
75 | |
76 | /** |
77 | * Constructor |
78 | * |
79 | * Instantiate the ACL object |
80 | */ |
81 | public function __construct() |
82 | { |
83 | $args = func_get_args(); |
84 | |
85 | foreach ($args as $arg) { |
86 | if (is_array($arg)) { |
87 | foreach ($arg as $a) { |
88 | if ($a instanceof AclRole) { |
89 | $this->addRole($a); |
90 | } else if ($a instanceof AclResource) { |
91 | $this->addResource($a); |
92 | } |
93 | } |
94 | } else if ($arg instanceof AclRole) { |
95 | $this->addRole($arg); |
96 | } else if ($arg instanceof AclResource) { |
97 | $this->addResource($arg); |
98 | } |
99 | } |
100 | } |
101 | |
102 | /** |
103 | * Get a role |
104 | * |
105 | * @param string $role |
106 | * @return AclRole |
107 | */ |
108 | public function getRole($role) |
109 | { |
110 | return (isset($this->roles[$role])) ? $this->roles[$role] : null; |
111 | } |
112 | |
113 | /** |
114 | * Get roles |
115 | * |
116 | * @return array |
117 | */ |
118 | public function getRoles() |
119 | { |
120 | return $this->roles; |
121 | } |
122 | |
123 | /** |
124 | * See if a role has been added |
125 | * |
126 | * @param string $role |
127 | * @return boolean |
128 | */ |
129 | public function hasRole($role) |
130 | { |
131 | return (isset($this->roles[$role])); |
132 | } |
133 | |
134 | /** |
135 | * Add a role |
136 | * |
137 | * @param AclRole $role |
138 | * @return Acl |
139 | */ |
140 | public function addRole(AclRole $role) |
141 | { |
142 | if (!isset($this->roles[$role->getName()])) { |
143 | $this->roles[$role->getName()] = $role; |
144 | |
145 | // Traverse up if role has parents |
146 | while ($role->hasParent()) { |
147 | $role = $role->getParent(); |
148 | $this->roles[$role->getName()] = $role; |
149 | } |
150 | |
151 | // Traverse down if the role has children |
152 | if ($role->hasChildren()) { |
153 | $this->traverseChildren($role->getChildren()); |
154 | } |
155 | } |
156 | return $this; |
157 | } |
158 | |
159 | /** |
160 | * Add roles |
161 | * |
162 | * @param array $roles |
163 | * @return Acl |
164 | */ |
165 | public function addRoles(array $roles) |
166 | { |
167 | foreach ($roles as $role) { |
168 | $this->addRole($role); |
169 | } |
170 | |
171 | return $this; |
172 | } |
173 | |
174 | /** |
175 | * Get a resource |
176 | * |
177 | * @param string $resource |
178 | * @return AclResource |
179 | */ |
180 | public function getResource($resource) |
181 | { |
182 | return (isset($this->resources[$resource])) ? $this->resources[$resource] : null; |
183 | } |
184 | |
185 | /** |
186 | * See if a resource has been added |
187 | * |
188 | * @param string $resource |
189 | * @return boolean |
190 | */ |
191 | public function hasResource($resource) |
192 | { |
193 | return (isset($this->resources[$resource])); |
194 | } |
195 | |
196 | /** |
197 | * Add a resource |
198 | * |
199 | * @param AclResource $resource |
200 | * @return Acl |
201 | */ |
202 | public function addResource(AclResource $resource) |
203 | { |
204 | $this->resources[$resource->getName()] = $resource; |
205 | return $this; |
206 | } |
207 | |
208 | /** |
209 | * Get resources |
210 | * |
211 | * @return array |
212 | */ |
213 | public function getResources() |
214 | { |
215 | return $this->resources; |
216 | } |
217 | |
218 | /** |
219 | * Add resources |
220 | * |
221 | * @param array $resources |
222 | * @throws Exception |
223 | * @return Acl |
224 | */ |
225 | public function addResources(array $resources) |
226 | { |
227 | foreach ($resources as $resource) { |
228 | $this->addResource($resource); |
229 | } |
230 | |
231 | return $this; |
232 | } |
233 | |
234 | /** |
235 | * Set strict |
236 | * |
237 | * @param boolean $strict |
238 | * @return Acl |
239 | */ |
240 | public function setStrict($strict = true) |
241 | { |
242 | $this->strict = (bool)$strict; |
243 | return $this; |
244 | } |
245 | |
246 | /** |
247 | * See if ACL object is set to strict |
248 | * |
249 | * @return boolean |
250 | */ |
251 | public function isStrict() |
252 | { |
253 | return $this->strict; |
254 | } |
255 | |
256 | /** |
257 | * Allow a user role permission to a resource or resources |
258 | * |
259 | * @param mixed $role |
260 | * @param mixed $resource |
261 | * @param mixed $permission |
262 | * @param AssertionInterface $assertion |
263 | * @throws Exception |
264 | * @return Acl |
265 | */ |
266 | public function allow($role, $resource = null, $permission = null, AssertionInterface $assertion = null) |
267 | { |
268 | if ($this->verifyRole($role)) { |
269 | $role = $this->roles[(string)$role]; |
270 | |
271 | if (!isset($this->allowed[(string)$role])) { |
272 | $this->allowed[(string)$role] = []; |
273 | } |
274 | |
275 | if ((null !== $resource) && ($this->verifyResource($resource))) { |
276 | $resource = $this->resources[(string)$resource]; |
277 | |
278 | if (!isset($this->allowed[(string)$role][(string)$resource])) { |
279 | $this->allowed[(string)$role][(string)$resource] = []; |
280 | } |
281 | if (null !== $permission) { |
282 | $this->allowed[(string)$role][(string)$resource][] = $permission; |
283 | } |
284 | } |
285 | |
286 | // If an assertion has been passed |
287 | if (null !== $assertion) { |
288 | $this->createAssertion($assertion, 'allowed', $role, $resource, $permission); |
289 | } |
290 | } |
291 | |
292 | return $this; |
293 | } |
294 | |
295 | /** |
296 | * Remove an allow rule |
297 | * |
298 | * @param mixed $role |
299 | * @param mixed $resource |
300 | * @param mixed $permission |
301 | * @throws Exception |
302 | * @return Acl |
303 | */ |
304 | public function removeAllowRule($role, $resource = null, $permission = null) |
305 | { |
306 | if (($this->verifyRole($role)) && isset($this->allowed[(string)$role])) { |
307 | // If only role passed |
308 | if ((null === $resource) && (null === $permission)) { |
309 | unset($this->allowed[(string)$role]); |
310 | // If role & resource passed |
311 | } else if ((null !== $resource) && (null === $permission) && ($this->verifyResource($resource)) && |
312 | isset($this->allowed[(string)$role][(string)$resource])) { |
313 | unset($this->allowed[(string)$role][(string)$resource]); |
314 | // If role, resource & permission passed |
315 | } else { |
316 | if (($this->verifyResource($resource)) && isset($this->allowed[(string)$role][(string)$resource]) && |
317 | in_array($permission, $this->allowed[(string)$role][(string)$resource])) { |
318 | $key = array_search($permission, $this->allowed[(string)$role][(string)$resource]); |
319 | unset($this->allowed[(string)$role][(string)$resource][$key]); |
320 | } |
321 | } |
322 | |
323 | $this->deleteAssertion('allowed', $role, $resource, $permission); |
324 | } |
325 | |
326 | return $this; |
327 | } |
328 | |
329 | /** |
330 | * Deny a user role permission to a resource or resources |
331 | * |
332 | * @param mixed $role |
333 | * @param mixed $resource |
334 | * @param mixed $permission |
335 | * @param AssertionInterface $assertion |
336 | * @throws Exception |
337 | * @return Acl |
338 | */ |
339 | public function deny($role, $resource = null, $permission = null, AssertionInterface $assertion = null) |
340 | { |
341 | if ($this->verifyRole($role)) { |
342 | $role = $this->roles[(string)$role]; |
343 | |
344 | if (!isset($this->denied[(string)$role])) { |
345 | $this->denied[(string)$role] = []; |
346 | } |
347 | |
348 | if ((null !== $resource) && ($this->verifyResource($resource))) { |
349 | $resource = $this->resources[(string)$resource]; |
350 | |
351 | if (!isset($this->denied[(string)$role][(string)$resource])) { |
352 | $this->denied[(string)$role][(string)$resource] = []; |
353 | } |
354 | if (null !== $permission) { |
355 | $this->denied[(string)$role][(string)$resource][] = $permission; |
356 | } |
357 | } |
358 | |
359 | // If an assertion has been passed |
360 | if (null !== $assertion) { |
361 | $this->createAssertion($assertion, 'denied', $role, $resource, $permission); |
362 | } |
363 | } |
364 | |
365 | return $this; |
366 | } |
367 | |
368 | /** |
369 | * Remove a deny rule |
370 | * |
371 | * @param mixed $role |
372 | * @param mixed $resource |
373 | * @param mixed $permission |
374 | * @throws Exception |
375 | * @return Acl |
376 | */ |
377 | public function removeDenyRule($role, $resource = null, $permission = null) |
378 | { |
379 | if (($this->verifyRole($role)) && isset($this->denied[(string)$role])) { |
380 | // If only role passed |
381 | if ((null === $resource) && (null === $permission)) { |
382 | unset($this->denied[(string)$role]); |
383 | // If role & resource passed |
384 | } else if ((null !== $resource) && (null === $permission) && ($this->verifyResource($resource)) && |
385 | isset($this->denied[(string)$role][(string)$resource])) { |
386 | unset($this->denied[(string)$role][(string)$resource]); |
387 | // If role, resource & permission passed |
388 | } else { |
389 | if (($this->verifyResource($resource)) && isset($this->denied[(string)$role][(string)$resource]) && |
390 | in_array($permission, $this->denied[(string)$role][(string)$resource])) { |
391 | $key = array_search($permission, $this->denied[(string)$role][(string)$resource]); |
392 | unset($this->denied[(string)$role][(string)$resource][$key]); |
393 | } |
394 | } |
395 | |
396 | $this->deleteAssertion('denied', $role, $resource, $permission); |
397 | } |
398 | |
399 | return $this; |
400 | } |
401 | |
402 | /** |
403 | * Determine if the user is allowed |
404 | * |
405 | * @param mixed $role |
406 | * @param mixed $resource |
407 | * @param mixed $permission |
408 | * @return boolean |
409 | */ |
410 | public function isAllowed($role, $resource = null, $permission = null) |
411 | { |
412 | $result = false; |
413 | |
414 | if ($this->verifyRole($role)) { |
415 | if (null !== $resource) { |
416 | $this->verifyResource($resource); |
417 | } |
418 | |
419 | // If is not denied |
420 | if (!$this->isDenied($role, $resource, $permission)) { |
421 | // If not strict, pass |
422 | if (!$this->strict) { |
423 | $result = true; |
424 | // If strict, check for explicit allow rule |
425 | } else { |
426 | $roleToCheck = $this->roles[(string)$role]; |
427 | while (null !== $roleToCheck) { |
428 | if (isset($this->allowed[(string)$roleToCheck])) { |
429 | // No explicit resources or permissions |
430 | if (count($this->allowed[(string)$roleToCheck]) == 0) { |
431 | $result = true; |
432 | // Resource set, but no explicit permissions |
433 | } else if ((null !== $resource) && isset($this->allowed[(string)$roleToCheck][(string)$resource]) && |
434 | (count($this->allowed[(string)$roleToCheck][(string)$resource]) == 0)) { |
435 | $result = true; |
436 | // Else, has resource and permissions set |
437 | } else if ((null !== $resource) && (null !== $permission) && |
438 | isset($this->allowed[(string)$roleToCheck][(string)$resource]) && |
439 | (count($this->allowed[(string)$roleToCheck][(string)$resource]) > 0)) { |
440 | $permissions = (!is_array($permission)) ? [$permission] : $permission; |
441 | $allowedPermissions = array_intersect( |
442 | $permissions, $this->allowed[(string)$roleToCheck][(string)$resource] |
443 | ); |
444 | |
445 | $result = (count($allowedPermissions) == count($permissions)); |
446 | } |
447 | } |
448 | $roleToCheck = $roleToCheck->getParent(); |
449 | } |
450 | } |
451 | } |
452 | } |
453 | |
454 | // Check for assertions |
455 | if (($result) && ($this->hasAssertionKey('allowed', $role, $resource, $permission))) { |
456 | $assertionKey = $this->getAssertionKey('allowed', $role, $resource, $permission); |
457 | $assertionRole = $this->roles[(string)$role]; |
458 | $assertionResource = (null !== $resource) ? $this->resources[(string)$resource] : null; |
459 | $result = |
460 | $this->assertions['allowed'][$assertionKey]->assert($this, $assertionRole, $assertionResource, $permission); |
461 | } |
462 | |
463 | // Check for policies |
464 | if ($this->hasPolicies()) { |
465 | $result = $this->evaluatePolicies($role, $resource, $permission); |
466 | } |
467 | |
468 | return $result; |
469 | } |
470 | |
471 | /** |
472 | * Determine if a user that is assigned many roles is allowed |
473 | * If one of the roles is allowed, then the user will be allowed (return true) |
474 | * |
475 | * @param array $roles |
476 | * @param mixed $resource |
477 | * @param mixed $permission |
478 | * @throws Exception |
479 | * @return boolean |
480 | */ |
481 | public function isAllowedMany(array $roles, $resource = null, $permission = null) |
482 | { |
483 | if ($this->strict) { |
484 | $result = true; |
485 | foreach ($roles as $role) { |
486 | if (!$this->isAllowed($role, $resource, $permission)) { |
487 | $result = false; |
488 | break; |
489 | } |
490 | } |
491 | } else { |
492 | $result = false; |
493 | foreach ($roles as $role) { |
494 | if ($this->isAllowed($role, $resource, $permission)) { |
495 | $result = true; |
496 | break; |
497 | } |
498 | } |
499 | } |
500 | |
501 | return $result; |
502 | } |
503 | |
504 | /** |
505 | * Determine if a user that is assigned many roles is allowed |
506 | * All of the roles must be allowed to allow the user (return true) |
507 | * |
508 | * @param array $roles |
509 | * @param mixed $resource |
510 | * @param mixed $permission |
511 | * @return boolean |
512 | */ |
513 | public function isAllowedManyStrict(array $roles, $resource = null, $permission = null) |
514 | { |
515 | $this->strict = true; |
516 | return $this->isAllowedMany($roles, $resource, $permission); |
517 | } |
518 | |
519 | /** |
520 | * Determine if the user is denied |
521 | * |
522 | * @param mixed $role |
523 | * @param mixed $resource |
524 | * @param mixed $permission |
525 | * @throws Exception |
526 | * @return boolean |
527 | */ |
528 | public function isDenied($role, $resource = null, $permission = null) |
529 | { |
530 | $result = false; |
531 | |
532 | if ($this->verifyRole($role)) { |
533 | if (null !== $resource) { |
534 | $this->verifyResource($resource); |
535 | } |
536 | |
537 | // Check if the user, resource and/or permission is denied |
538 | $roleToCheck = $this->roles[(string)$role]; |
539 | while (null !== $roleToCheck) { |
540 | if (isset($this->denied[(string)$roleToCheck])) { |
541 | if (count($this->denied[(string)$roleToCheck]) > 0) { |
542 | if ((null !== $resource) && array_key_exists((string)$resource, $this->denied[(string)$roleToCheck])) { |
543 | if (count($this->denied[(string)$roleToCheck][(string)$resource]) > 0) { |
544 | if (null !== $permission) { |
545 | $permissions = (!is_array($permission)) ? [$permission] : $permission; |
546 | foreach ($permissions as $p) { |
547 | if (in_array($p, $this->denied[(string)$roleToCheck][(string)$resource])) { |
548 | $result = true; |
549 | } |
550 | } |
551 | } |
552 | } else { |
553 | $result = true; |
554 | } |
555 | } |
556 | } else { |
557 | $result = true; |
558 | } |
559 | } |
560 | $roleToCheck = $roleToCheck->getParent(); |
561 | } |
562 | } |
563 | |
564 | // Check for assertions |
565 | if ($this->hasAssertionKey('denied', $role, $resource, $permission)) { |
566 | $assertionKey = $this->getAssertionKey('denied', $role, $resource, $permission); |
567 | $assertionRole = $this->roles[(string)$role]; |
568 | $assertionResource = (null !== $resource) ? $this->resources[(string)$resource] : null; |
569 | $result = |
570 | $this->assertions['denied'][$assertionKey]->assert($this, $assertionRole, $assertionResource, $permission); |
571 | } |
572 | |
573 | // Check for policies |
574 | if ($this->hasPolicies()) { |
575 | $result = (!$this->evaluatePolicies($role, $resource, $permission)); |
576 | } |
577 | |
578 | return $result; |
579 | } |
580 | |
581 | /** |
582 | * Determine if a user that is assigned many roles is denied |
583 | * If one of the roles is denied, then the user will be denied (return true) |
584 | * |
585 | * @param array $roles |
586 | * @param mixed $resource |
587 | * @param mixed $permission |
588 | * @throws Exception |
589 | * @return boolean |
590 | */ |
591 | public function isDeniedMany(array $roles, $resource = null, $permission = null) |
592 | { |
593 | if ($this->strict) { |
594 | $result = true; |
595 | foreach ($roles as $role) { |
596 | if (!$this->isDenied($role, $resource, $permission)) { |
597 | $result = false; |
598 | break; |
599 | } |
600 | } |
601 | } else { |
602 | $result = false; |
603 | foreach ($roles as $role) { |
604 | if ($this->isDenied($role, $resource, $permission)) { |
605 | $result = true; |
606 | break; |
607 | } |
608 | } |
609 | } |
610 | |
611 | return $result; |
612 | } |
613 | |
614 | /** |
615 | * Determine if a user that is assigned many roles is denied |
616 | * All of the roles must be denied to deny the user (return true) |
617 | * |
618 | * @param array $roles |
619 | * @param mixed $resource |
620 | * @param mixed $permission |
621 | * @throws Exception |
622 | * @return boolean |
623 | */ |
624 | public function isDeniedManyStrict(array $roles, $resource = null, $permission = null) |
625 | { |
626 | $this->strict = true; |
627 | return $this->isDeniedMany($roles, $resource, $permission); |
628 | } |
629 | |
630 | /** |
631 | * Create assertion |
632 | * |
633 | * @param AssertionInterface $assertion |
634 | * @param string $type |
635 | * @param mixed $role |
636 | * @param mixed $resource |
637 | * @param string $permission |
638 | * @throws \InvalidArgumentException |
639 | * @return void |
640 | */ |
641 | public function createAssertion(AssertionInterface $assertion, $type, $role, $resource = null, $permission = null) |
642 | { |
643 | $key = $this->generateAssertionKey($role, $resource, $permission); |
644 | |
645 | if (($type != 'allowed') && ($type != 'denied')) { |
646 | throw new \InvalidArgumentException("Error: The assertion type must be either 'allowed' or 'denied'."); |
647 | } |
648 | $this->assertions[$type][$key] = $assertion; |
649 | } |
650 | |
651 | /** |
652 | * Delete assertion |
653 | * |
654 | * @param string $type |
655 | * @param mixed $role |
656 | * @param mixed $resource |
657 | * @param string $permission |
658 | * @return void |
659 | */ |
660 | public function deleteAssertion($type, $role, $resource = null, $permission = null) |
661 | { |
662 | $key = $this->generateAssertionKey($role, $resource, $permission); |
663 | |
664 | if (isset($this->assertions[$type][$key])) { |
665 | unset($this->assertions[$type][$key]); |
666 | } |
667 | } |
668 | |
669 | /** |
670 | * Has assertion key |
671 | * |
672 | * @param string $type |
673 | * @param mixed $role |
674 | * @param mixed $resource |
675 | * @param string $permission |
676 | * @throws \InvalidArgumentException |
677 | * @return boolean |
678 | */ |
679 | public function hasAssertionKey($type, $role, $resource = null, $permission = null) |
680 | { |
681 | $key = $this->generateAssertionKey($role, $resource, $permission); |
682 | |
683 | if (($type != 'allowed') && ($type != 'denied')) { |
684 | throw new \InvalidArgumentException("Error: The assertion type must be either 'allowed' or 'denied'."); |
685 | } |
686 | |
687 | return (isset($this->assertions[$type][$key])); |
688 | } |
689 | |
690 | /** |
691 | * Get assertion key |
692 | * |
693 | * @param string $type |
694 | * @param mixed $role |
695 | * @param mixed $resource |
696 | * @param string $permission |
697 | * @throws \InvalidArgumentException |
698 | * @return string |
699 | */ |
700 | public function getAssertionKey($type, $role, $resource = null, $permission = null) |
701 | { |
702 | $key = $this->generateAssertionKey($role, $resource, $permission); |
703 | |
704 | if (($type != 'allowed') && ($type != 'denied')) { |
705 | throw new \InvalidArgumentException("Error: The assertion type must be either 'allowed' or 'denied'."); |
706 | } |
707 | |
708 | return (isset($this->assertions[$type][$key])) ? $key : null; |
709 | } |
710 | |
711 | /** |
712 | * Add policy |
713 | * |
714 | * @param string $method |
715 | * @param mixed $role |
716 | * @param mixed $resource |
717 | * @return Acl |
718 | */ |
719 | public function addPolicy($method, $role, $resource = null) |
720 | { |
721 | $this->policies[] = [ |
722 | 'method' => $method, |
723 | 'role' => $role, |
724 | 'resource' => $resource |
725 | ]; |
726 | |
727 | return $this; |
728 | } |
729 | |
730 | /** |
731 | * Has policies |
732 | * |
733 | * @return boolean |
734 | */ |
735 | public function hasPolicies() |
736 | { |
737 | return (count($this->policies) > 0); |
738 | } |
739 | |
740 | /** |
741 | * Evaluate policies |
742 | * |
743 | * @param mixed $role |
744 | * @param mixed $resource |
745 | * @param mixed $permission |
746 | * @return boolean|null |
747 | */ |
748 | public function evaluatePolicies($role = null, $resource = null, $permission = null) |
749 | { |
750 | $result = null; |
751 | |
752 | if ((null === $role) && (null === $resource) && (null === $permission)) { |
753 | foreach ($this->policies as $policy) { |
754 | $result = $this->evaluatePolicy($policy['method'], $policy['role'], $policy['resource']); |
755 | if ($result === false) { |
756 | return false; |
757 | } |
758 | } |
759 | } else { |
760 | $policyRole = null; |
761 | $policyResource = null; |
762 | $policyMethod = (null !== $permission) ? $permission : null; |
763 | |
764 | if (null !== $role) { |
765 | $this->verifyRole($role); |
766 | $policyRole = ($role instanceof AclRole) ? $role->getName() : $role; |
767 | } |
768 | if (null !== $resource) { |
769 | $this->verifyResource($resource); |
770 | $policyResource = ($resource instanceof AclResource) ? $resource->getName() : $resource; |
771 | } |
772 | |
773 | foreach ($this->policies as $policy) { |
774 | if (((null === $policyRole) || ($policyRole == $policy['role'])) && |
775 | ((null === $policyResource) || ($policyResource == $policy['resource'])) && |
776 | ((null === $policyMethod) || ($policyMethod == $policy['method']))) { |
777 | $result = $this->evaluatePolicy($policy['method'], $policy['role'], $policy['resource']); |
778 | if ($result === false) { |
779 | return false; |
780 | } |
781 | } |
782 | } |
783 | } |
784 | |
785 | return $result; |
786 | } |
787 | |
788 | /** |
789 | * Evaluate policy |
790 | * |
791 | * @param string $method |
792 | * @param mixed $role |
793 | * @param mixed $resource |
794 | * @throws Exception |
795 | * @return boolean |
796 | */ |
797 | public function evaluatePolicy($method, $role, $resource = null) |
798 | { |
799 | if (is_string($role) && ($this->verifyRole($role))) { |
800 | $role = $this->roles[(string)$role]; |
801 | } |
802 | |
803 | if (!in_array('Pop\Acl\Policy\PolicyTrait', class_uses($role))) { |
804 | throw new Exception('Error: The role must use Pop\Acl\Policy\PolicyTrait.'); |
805 | } |
806 | |
807 | if (null !== $resource) { |
808 | $this->verifyResource($resource); |
809 | $resource = $this->resources[(string)$resource]; |
810 | } |
811 | |
812 | return $role->can($method, $resource); |
813 | } |
814 | |
815 | /** |
816 | * Verify role |
817 | * |
818 | * @param mixed $role |
819 | * @throws Exception |
820 | * @return boolean |
821 | */ |
822 | protected function verifyRole($role) |
823 | { |
824 | if (!is_string($role) && !($role instanceof AclRole)) { |
825 | throw new \InvalidArgumentException('Error: The role must be a string or an instance of Role.'); |
826 | } |
827 | if (!isset($this->roles[(string)$role])) { |
828 | throw new Exception("Error: The role '" . (string)$role . "' has not been added."); |
829 | } |
830 | |
831 | return true; |
832 | } |
833 | |
834 | /** |
835 | * Verify resource |
836 | * |
837 | * @param mixed $resource |
838 | * @throws Exception |
839 | * @return boolean |
840 | */ |
841 | protected function verifyResource($resource) |
842 | { |
843 | if (!is_string($resource) && !($resource instanceof AclResource)) { |
844 | throw new \InvalidArgumentException('Error: The resource must be a string or an instance of Resource.'); |
845 | } |
846 | if (!isset($this->resources[(string)$resource])) { |
847 | throw new Exception("Error: The resource '" . (string)$resource . "' has not been added."); |
848 | } |
849 | |
850 | return true; |
851 | } |
852 | |
853 | /** |
854 | * Generate assertion key |
855 | * |
856 | * @param mixed $role |
857 | * @param mixed $resource |
858 | * @param string $permission |
859 | * @return string |
860 | */ |
861 | protected function generateAssertionKey($role, $resource = null, $permission = null) |
862 | { |
863 | $key = (string)$role; |
864 | |
865 | if (null !== $resource) { |
866 | $key .= '-' . (string)$resource; |
867 | } |
868 | if (null !== $permission) { |
869 | $key .= '-' . (string)$permission; |
870 | } |
871 | |
872 | return $key; |
873 | } |
874 | |
875 | /** |
876 | * Traverse child roles to add them to the ACL object |
877 | * |
878 | * @param array $roles |
879 | * @return void |
880 | */ |
881 | protected function traverseChildren(array $roles) |
882 | { |
883 | foreach ($roles as $role) { |
884 | $this->addRole($role); |
885 | if ($role->hasChildren()) { |
886 | $this->traverseChildren($role->getChildren()); |
887 | } |
888 | } |
889 | } |
890 | |
891 | } |