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