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