1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Nette\Security;
9:
10: use Nette;
11:
12:
13: 14: 15: 16: 17: 18: 19:
20: class Permission implements IAuthorizator
21: {
22: use Nette\SmartObject;
23:
24:
25: private $roles = [];
26:
27:
28: private $resources = [];
29:
30:
31: private $rules = [
32: 'allResources' => [
33: 'allRoles' => [
34: 'allPrivileges' => [
35: 'type' => self::DENY,
36: 'assert' => null,
37: ],
38: 'byPrivilege' => [],
39: ],
40: 'byRole' => [],
41: ],
42: 'byResource' => [],
43: ];
44:
45:
46: private $queriedRole;
47:
48: private $queriedResource;
49:
50:
51:
52:
53:
54: 55: 56: 57: 58: 59: 60: 61: 62:
63: public function addRole($role, $parents = null)
64: {
65: $this->checkRole($role, false);
66: if (isset($this->roles[$role])) {
67: throw new Nette\InvalidStateException("Role '$role' already exists in the list.");
68: }
69:
70: $roleParents = [];
71:
72: if ($parents !== null) {
73: if (!is_array($parents)) {
74: $parents = [$parents];
75: }
76:
77: foreach ($parents as $parent) {
78: $this->checkRole($parent);
79: $roleParents[$parent] = true;
80: $this->roles[$parent]['children'][$role] = true;
81: }
82: }
83:
84: $this->roles[$role] = [
85: 'parents' => $roleParents,
86: 'children' => [],
87: ];
88:
89: return $this;
90: }
91:
92:
93: 94: 95: 96: 97:
98: public function hasRole($role)
99: {
100: $this->checkRole($role, false);
101: return isset($this->roles[$role]);
102: }
103:
104:
105: 106: 107: 108: 109: 110: 111:
112: private function checkRole($role, $throw = true)
113: {
114: if (!is_string($role) || $role === '') {
115: throw new Nette\InvalidArgumentException('Role must be a non-empty string.');
116:
117: } elseif ($throw && !isset($this->roles[$role])) {
118: throw new Nette\InvalidStateException("Role '$role' does not exist.");
119: }
120: }
121:
122:
123: 124: 125: 126:
127: public function getRoles()
128: {
129: return array_keys($this->roles);
130: }
131:
132:
133: 134: 135: 136: 137:
138: public function getRoleParents($role)
139: {
140: $this->checkRole($role);
141: return array_keys($this->roles[$role]['parents']);
142: }
143:
144:
145: 146: 147: 148: 149: 150: 151: 152: 153:
154: public function roleInheritsFrom($role, $inherit, $onlyParents = false)
155: {
156: $this->checkRole($role);
157: $this->checkRole($inherit);
158:
159: $inherits = isset($this->roles[$role]['parents'][$inherit]);
160:
161: if ($inherits || $onlyParents) {
162: return $inherits;
163: }
164:
165: foreach ($this->roles[$role]['parents'] as $parent => $foo) {
166: if ($this->roleInheritsFrom($parent, $inherit)) {
167: return true;
168: }
169: }
170:
171: return false;
172: }
173:
174:
175: 176: 177: 178: 179: 180: 181:
182: public function removeRole($role)
183: {
184: $this->checkRole($role);
185:
186: foreach ($this->roles[$role]['children'] as $child => $foo) {
187: unset($this->roles[$child]['parents'][$role]);
188: }
189:
190: foreach ($this->roles[$role]['parents'] as $parent => $foo) {
191: unset($this->roles[$parent]['children'][$role]);
192: }
193:
194: unset($this->roles[$role]);
195:
196: foreach ($this->rules['allResources']['byRole'] as $roleCurrent => $rules) {
197: if ($role === $roleCurrent) {
198: unset($this->rules['allResources']['byRole'][$roleCurrent]);
199: }
200: }
201:
202: foreach ($this->rules['byResource'] as $resourceCurrent => $visitor) {
203: if (isset($visitor['byRole'])) {
204: foreach ($visitor['byRole'] as $roleCurrent => $rules) {
205: if ($role === $roleCurrent) {
206: unset($this->rules['byResource'][$resourceCurrent]['byRole'][$roleCurrent]);
207: }
208: }
209: }
210: }
211:
212: return $this;
213: }
214:
215:
216: 217: 218: 219: 220:
221: public function removeAllRoles()
222: {
223: $this->roles = [];
224:
225: foreach ($this->rules['allResources']['byRole'] as $roleCurrent => $rules) {
226: unset($this->rules['allResources']['byRole'][$roleCurrent]);
227: }
228:
229: foreach ($this->rules['byResource'] as $resourceCurrent => $visitor) {
230: foreach ($visitor['byRole'] as $roleCurrent => $rules) {
231: unset($this->rules['byResource'][$resourceCurrent]['byRole'][$roleCurrent]);
232: }
233: }
234:
235: return $this;
236: }
237:
238:
239:
240:
241:
242: 243: 244: 245: 246: 247: 248: 249: 250:
251: public function addResource($resource, $parent = null)
252: {
253: $this->checkResource($resource, false);
254:
255: if (isset($this->resources[$resource])) {
256: throw new Nette\InvalidStateException("Resource '$resource' already exists in the list.");
257: }
258:
259: if ($parent !== null) {
260: $this->checkResource($parent);
261: $this->resources[$parent]['children'][$resource] = true;
262: }
263:
264: $this->resources[$resource] = [
265: 'parent' => $parent,
266: 'children' => [],
267: ];
268:
269: return $this;
270: }
271:
272:
273: 274: 275: 276: 277:
278: public function hasResource($resource)
279: {
280: $this->checkResource($resource, false);
281: return isset($this->resources[$resource]);
282: }
283:
284:
285: 286: 287: 288: 289: 290: 291:
292: private function checkResource($resource, $throw = true)
293: {
294: if (!is_string($resource) || $resource === '') {
295: throw new Nette\InvalidArgumentException('Resource must be a non-empty string.');
296:
297: } elseif ($throw && !isset($this->resources[$resource])) {
298: throw new Nette\InvalidStateException("Resource '$resource' does not exist.");
299: }
300: }
301:
302:
303: 304: 305: 306:
307: public function getResources()
308: {
309: return array_keys($this->resources);
310: }
311:
312:
313: 314: 315: 316: 317: 318: 319: 320: 321: 322:
323: public function resourceInheritsFrom($resource, $inherit, $onlyParent = false)
324: {
325: $this->checkResource($resource);
326: $this->checkResource($inherit);
327:
328: if ($this->resources[$resource]['parent'] === null) {
329: return false;
330: }
331:
332: $parent = $this->resources[$resource]['parent'];
333: if ($inherit === $parent) {
334: return true;
335:
336: } elseif ($onlyParent) {
337: return false;
338: }
339:
340: while ($this->resources[$parent]['parent'] !== null) {
341: $parent = $this->resources[$parent]['parent'];
342: if ($inherit === $parent) {
343: return true;
344: }
345: }
346:
347: return false;
348: }
349:
350:
351: 352: 353: 354: 355: 356: 357:
358: public function removeResource($resource)
359: {
360: $this->checkResource($resource);
361:
362: $parent = $this->resources[$resource]['parent'];
363: if ($parent !== null) {
364: unset($this->resources[$parent]['children'][$resource]);
365: }
366:
367: $removed = [$resource];
368: foreach ($this->resources[$resource]['children'] as $child => $foo) {
369: $this->removeResource($child);
370: $removed[] = $child;
371: }
372:
373: foreach ($removed as $resourceRemoved) {
374: foreach ($this->rules['byResource'] as $resourceCurrent => $rules) {
375: if ($resourceRemoved === $resourceCurrent) {
376: unset($this->rules['byResource'][$resourceCurrent]);
377: }
378: }
379: }
380:
381: unset($this->resources[$resource]);
382: return $this;
383: }
384:
385:
386: 387: 388: 389:
390: public function removeAllResources()
391: {
392: foreach ($this->resources as $resource => $foo) {
393: foreach ($this->rules['byResource'] as $resourceCurrent => $rules) {
394: if ($resource === $resourceCurrent) {
395: unset($this->rules['byResource'][$resourceCurrent]);
396: }
397: }
398: }
399:
400: $this->resources = [];
401: return $this;
402: }
403:
404:
405:
406:
407:
408: 409: 410: 411: 412: 413: 414: 415: 416: 417:
418: public function allow($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL, $assertion = null)
419: {
420: $this->setRule(true, self::ALLOW, $roles, $resources, $privileges, $assertion);
421: return $this;
422: }
423:
424:
425: 426: 427: 428: 429: 430: 431: 432: 433: 434:
435: public function deny($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL, $assertion = null)
436: {
437: $this->setRule(true, self::DENY, $roles, $resources, $privileges, $assertion);
438: return $this;
439: }
440:
441:
442: 443: 444: 445: 446: 447: 448: 449:
450: public function removeAllow($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL)
451: {
452: $this->setRule(false, self::ALLOW, $roles, $resources, $privileges);
453: return $this;
454: }
455:
456:
457: 458: 459: 460: 461: 462: 463: 464:
465: public function removeDeny($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL)
466: {
467: $this->setRule(false, self::DENY, $roles, $resources, $privileges);
468: return $this;
469: }
470:
471:
472: 473: 474: 475: 476: 477: 478: 479: 480: 481: 482:
483: protected function setRule($toAdd, $type, $roles, $resources, $privileges, $assertion = null)
484: {
485:
486: if ($roles === self::ALL) {
487: $roles = [self::ALL];
488:
489: } else {
490: if (!is_array($roles)) {
491: $roles = [$roles];
492: }
493:
494: foreach ($roles as $role) {
495: $this->checkRole($role);
496: }
497: }
498:
499:
500: if ($resources === self::ALL) {
501: $resources = [self::ALL];
502:
503: } else {
504: if (!is_array($resources)) {
505: $resources = [$resources];
506: }
507:
508: foreach ($resources as $resource) {
509: $this->checkResource($resource);
510: }
511: }
512:
513:
514: if ($privileges === self::ALL) {
515: $privileges = [];
516:
517: } elseif (!is_array($privileges)) {
518: $privileges = [$privileges];
519: }
520:
521: if ($toAdd) {
522: foreach ($resources as $resource) {
523: foreach ($roles as $role) {
524: $rules = &$this->getRules($resource, $role, true);
525: if (count($privileges) === 0) {
526: $rules['allPrivileges']['type'] = $type;
527: $rules['allPrivileges']['assert'] = $assertion;
528: if (!isset($rules['byPrivilege'])) {
529: $rules['byPrivilege'] = [];
530: }
531: } else {
532: foreach ($privileges as $privilege) {
533: $rules['byPrivilege'][$privilege]['type'] = $type;
534: $rules['byPrivilege'][$privilege]['assert'] = $assertion;
535: }
536: }
537: }
538: }
539:
540: } else {
541: foreach ($resources as $resource) {
542: foreach ($roles as $role) {
543: $rules = &$this->getRules($resource, $role);
544: if ($rules === null) {
545: continue;
546: }
547: if (count($privileges) === 0) {
548: if ($resource === self::ALL && $role === self::ALL) {
549: if ($type === $rules['allPrivileges']['type']) {
550: $rules = [
551: 'allPrivileges' => [
552: 'type' => self::DENY,
553: 'assert' => null,
554: ],
555: 'byPrivilege' => [],
556: ];
557: }
558: continue;
559: }
560: if ($type === $rules['allPrivileges']['type']) {
561: unset($rules['allPrivileges']);
562: }
563: } else {
564: foreach ($privileges as $privilege) {
565: if (isset($rules['byPrivilege'][$privilege]) &&
566: $type === $rules['byPrivilege'][$privilege]['type']
567: ) {
568: unset($rules['byPrivilege'][$privilege]);
569: }
570: }
571: }
572: }
573: }
574: }
575: return $this;
576: }
577:
578:
579:
580:
581:
582: 583: 584: 585: 586: 587: 588: 589: 590: 591: 592: 593: 594: 595:
596: public function isAllowed($role = self::ALL, $resource = self::ALL, $privilege = self::ALL)
597: {
598: $this->queriedRole = $role;
599: if ($role !== self::ALL) {
600: if ($role instanceof IRole) {
601: $role = $role->getRoleId();
602: }
603: $this->checkRole($role);
604: }
605:
606: $this->queriedResource = $resource;
607: if ($resource !== self::ALL) {
608: if ($resource instanceof IResource) {
609: $resource = $resource->getResourceId();
610: }
611: $this->checkResource($resource);
612: }
613:
614: do {
615:
616: if ($role !== null && ($result = $this->searchRolePrivileges($privilege === self::ALL, $role, $resource, $privilege)) !== null) {
617: break;
618: }
619:
620: if ($privilege === self::ALL) {
621: if ($rules = $this->getRules($resource, self::ALL)) {
622: foreach ($rules['byPrivilege'] as $privilege => $rule) {
623: if (($result = $this->getRuleType($resource, null, $privilege)) === self::DENY) {
624: break 2;
625: }
626: }
627: if (($result = $this->getRuleType($resource, null, null)) !== null) {
628: break;
629: }
630: }
631: } else {
632: if (($result = $this->getRuleType($resource, null, $privilege)) !== null) {
633: break;
634:
635: } elseif (($result = $this->getRuleType($resource, null, null)) !== null) {
636: break;
637: }
638: }
639:
640: $resource = $this->resources[$resource]['parent'];
641: } while (true);
642:
643: $this->queriedRole = $this->queriedResource = null;
644: return $result;
645: }
646:
647:
648: 649: 650: 651:
652: public function getQueriedRole()
653: {
654: return $this->queriedRole;
655: }
656:
657:
658: 659: 660: 661:
662: public function getQueriedResource()
663: {
664: return $this->queriedResource;
665: }
666:
667:
668:
669:
670:
671: 672: 673: 674: 675: 676: 677: 678: 679:
680: private function searchRolePrivileges($all, $role, $resource, $privilege)
681: {
682: $dfs = [
683: 'visited' => [],
684: 'stack' => [$role],
685: ];
686:
687: while (($role = array_pop($dfs['stack'])) !== null) {
688: if (isset($dfs['visited'][$role])) {
689: continue;
690: }
691: if ($all) {
692: if ($rules = $this->getRules($resource, $role)) {
693: foreach ($rules['byPrivilege'] as $privilege2 => $rule) {
694: if ($this->getRuleType($resource, $role, $privilege2) === self::DENY) {
695: return self::DENY;
696: }
697: }
698: if (($type = $this->getRuleType($resource, $role, null)) !== null) {
699: return $type;
700: }
701: }
702: } else {
703: if (($type = $this->getRuleType($resource, $role, $privilege)) !== null) {
704: return $type;
705:
706: } elseif (($type = $this->getRuleType($resource, $role, null)) !== null) {
707: return $type;
708: }
709: }
710:
711: $dfs['visited'][$role] = true;
712: foreach ($this->roles[$role]['parents'] as $roleParent => $foo) {
713: $dfs['stack'][] = $roleParent;
714: }
715: }
716: return null;
717: }
718:
719:
720: 721: 722: 723: 724: 725: 726:
727: private function getRuleType($resource, $role, $privilege)
728: {
729: if (!$rules = $this->getRules($resource, $role)) {
730: return null;
731: }
732:
733: if ($privilege === self::ALL) {
734: if (isset($rules['allPrivileges'])) {
735: $rule = $rules['allPrivileges'];
736: } else {
737: return null;
738: }
739: } elseif (!isset($rules['byPrivilege'][$privilege])) {
740: return null;
741:
742: } else {
743: $rule = $rules['byPrivilege'][$privilege];
744: }
745:
746: if ($rule['assert'] === null || call_user_func($rule['assert'], $this, $role, $resource, $privilege)) {
747: return $rule['type'];
748:
749: } elseif ($resource !== self::ALL || $role !== self::ALL || $privilege !== self::ALL) {
750: return null;
751:
752: } elseif ($rule['type'] === self::ALLOW) {
753: return self::DENY;
754:
755: } else {
756: return self::ALLOW;
757: }
758: }
759:
760:
761: 762: 763: 764: 765: 766: 767: 768:
769: private function &getRules($resource, $role, $create = false)
770: {
771: $null = null;
772: if ($resource === self::ALL) {
773: $visitor = &$this->rules['allResources'];
774: } else {
775: if (!isset($this->rules['byResource'][$resource])) {
776: if (!$create) {
777: return $null;
778: }
779: $this->rules['byResource'][$resource] = [];
780: }
781: $visitor = &$this->rules['byResource'][$resource];
782: }
783:
784: if ($role === self::ALL) {
785: if (!isset($visitor['allRoles'])) {
786: if (!$create) {
787: return $null;
788: }
789: $visitor['allRoles']['byPrivilege'] = [];
790: }
791: return $visitor['allRoles'];
792: }
793:
794: if (!isset($visitor['byRole'][$role])) {
795: if (!$create) {
796: return $null;
797: }
798: $visitor['byRole'][$role]['byPrivilege'] = [];
799: }
800:
801: return $visitor['byRole'][$role];
802: }
803: }
804: