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