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: if ($toAdd) {
524: foreach ($resources as $resource) {
525: foreach ($roles as $role) {
526: $rules = & $this->getRules($resource, $role, TRUE);
527: if (count($privileges) === 0) {
528: $rules['allPrivileges']['type'] = $type;
529: $rules['allPrivileges']['assert'] = $assertion;
530: if (!isset($rules['byPrivilege'])) {
531: $rules['byPrivilege'] = array();
532: }
533: } else {
534: foreach ($privileges as $privilege) {
535: $rules['byPrivilege'][$privilege]['type'] = $type;
536: $rules['byPrivilege'][$privilege]['assert'] = $assertion;
537: }
538: }
539: }
540: }
541:
542: } else {
543: foreach ($resources as $resource) {
544: foreach ($roles as $role) {
545: $rules = & $this->getRules($resource, $role);
546: if ($rules === NULL) {
547: continue;
548: }
549: if (count($privileges) === 0) {
550: if ($resource === self::ALL && $role === self::ALL) {
551: if ($type === $rules['allPrivileges']['type']) {
552: $rules = array(
553: 'allPrivileges' => array(
554: 'type' => self::DENY,
555: 'assert' => NULL
556: ),
557: 'byPrivilege' => array()
558: );
559: }
560: continue;
561: }
562: if ($type === $rules['allPrivileges']['type']) {
563: unset($rules['allPrivileges']);
564: }
565: } else {
566: foreach ($privileges as $privilege) {
567: if (isset($rules['byPrivilege'][$privilege]) &&
568: $type === $rules['byPrivilege'][$privilege]['type']
569: ) {
570: unset($rules['byPrivilege'][$privilege]);
571: }
572: }
573: }
574: }
575: }
576: }
577: return $this;
578: }
579:
580:
581:
582:
583:
584: 585: 586: 587: 588: 589: 590: 591: 592: 593: 594: 595: 596: 597:
598: public function isAllowed($role = self::ALL, $resource = self::ALL, $privilege = self::ALL)
599: {
600: $this->queriedRole = $role;
601: if ($role !== self::ALL) {
602: if ($role instanceof IRole) {
603: $role = $role->getRoleId();
604: }
605: $this->checkRole($role);
606: }
607:
608: $this->queriedResource = $resource;
609: if ($resource !== self::ALL) {
610: if ($resource instanceof IResource) {
611: $resource = $resource->getResourceId();
612: }
613: $this->checkResource($resource);
614: }
615:
616: do {
617:
618: if ($role !== NULL && NULL !== ($result = $this->searchRolePrivileges($privilege === self::ALL, $role, $resource, $privilege))) {
619: break;
620: }
621:
622: if ($privilege === self::ALL) {
623: if ($rules = $this->getRules($resource, self::ALL)) {
624: foreach ($rules['byPrivilege'] as $privilege => $rule) {
625: if (self::DENY === ($result = $this->getRuleType($resource, NULL, $privilege))) {
626: break 2;
627: }
628: }
629: if (NULL !== ($result = $this->getRuleType($resource, NULL, NULL))) {
630: break;
631: }
632: }
633: } else {
634: if (NULL !== ($result = $this->getRuleType($resource, NULL, $privilege))) {
635: break;
636:
637: } elseif (NULL !== ($result = $this->getRuleType($resource, NULL, NULL))) {
638: break;
639: }
640: }
641:
642: $resource = $this->resources[$resource]['parent'];
643: } while (TRUE);
644:
645: $this->queriedRole = $this->queriedResource = NULL;
646: return $result;
647: }
648:
649:
650: 651: 652: 653:
654: public function getQueriedRole()
655: {
656: return $this->queriedRole;
657: }
658:
659:
660: 661: 662: 663:
664: public function getQueriedResource()
665: {
666: return $this->queriedResource;
667: }
668:
669:
670:
671:
672:
673: 674: 675: 676: 677: 678: 679: 680: 681:
682: private function searchRolePrivileges($all, $role, $resource, $privilege)
683: {
684: $dfs = array(
685: 'visited' => array(),
686: 'stack' => array($role),
687: );
688:
689: while (NULL !== ($role = array_pop($dfs['stack']))) {
690: if (isset($dfs['visited'][$role])) {
691: continue;
692: }
693: if ($all) {
694: if ($rules = $this->getRules($resource, $role)) {
695: foreach ($rules['byPrivilege'] as $privilege2 => $rule) {
696: if (self::DENY === $this->getRuleType($resource, $role, $privilege2)) {
697: return self::DENY;
698: }
699: }
700: if (NULL !== ($type = $this->getRuleType($resource, $role, NULL))) {
701: return $type;
702: }
703: }
704: } else {
705: if (NULL !== ($type = $this->getRuleType($resource, $role, $privilege))) {
706: return $type;
707:
708: } elseif (NULL !== ($type = $this->getRuleType($resource, $role, NULL))) {
709: return $type;
710: }
711: }
712:
713: $dfs['visited'][$role] = TRUE;
714: foreach ($this->roles[$role]['parents'] as $roleParent => $foo) {
715: $dfs['stack'][] = $roleParent;
716: }
717: }
718: return NULL;
719: }
720:
721:
722: 723: 724: 725: 726: 727: 728:
729: private function getRuleType($resource, $role, $privilege)
730: {
731: if (!$rules = $this->getRules($resource, $role)) {
732: return NULL;
733: }
734:
735: if ($privilege === self::ALL) {
736: if (isset($rules['allPrivileges'])) {
737: $rule = $rules['allPrivileges'];
738: } else {
739: return NULL;
740: }
741: } elseif (!isset($rules['byPrivilege'][$privilege])) {
742: return NULL;
743:
744: } else {
745: $rule = $rules['byPrivilege'][$privilege];
746: }
747:
748: if ($rule['assert'] === NULL || Nette\Utils\Callback::invoke($rule['assert'], $this, $role, $resource, $privilege)) {
749: return $rule['type'];
750:
751: } elseif ($resource !== self::ALL || $role !== self::ALL || $privilege !== self::ALL) {
752: return NULL;
753:
754: } elseif (self::ALLOW === $rule['type']) {
755: return self::DENY;
756:
757: } else {
758: return self::ALLOW;
759: }
760: }
761:
762:
763: 764: 765: 766: 767: 768: 769: 770:
771: private function & getRules($resource, $role, $create = FALSE)
772: {
773: $null = NULL;
774: if ($resource === self::ALL) {
775: $visitor = & $this->rules['allResources'];
776: } else {
777: if (!isset($this->rules['byResource'][$resource])) {
778: if (!$create) {
779: return $null;
780: }
781: $this->rules['byResource'][$resource] = array();
782: }
783: $visitor = & $this->rules['byResource'][$resource];
784: }
785:
786: if ($role === self::ALL) {
787: if (!isset($visitor['allRoles'])) {
788: if (!$create) {
789: return $null;
790: }
791: $visitor['allRoles']['byPrivilege'] = array();
792: }
793: return $visitor['allRoles'];
794: }
795:
796: if (!isset($visitor['byRole'][$role])) {
797: if (!$create) {
798: return $null;
799: }
800: $visitor['byRole'][$role]['byPrivilege'] = array();
801: }
802:
803: return $visitor['byRole'][$role];
804: }
805:
806: }
807: