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