1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10: 11:
12:
13:
14:
15: 16: 17: 18: 19: 20: 21: 22: 23:
24: class NPermission extends NObject implements IAuthorizator
25: {
26:
27: private $roles = array();
28:
29:
30: private $resources = array();
31:
32:
33: private $rules = array(
34: 'allResources' => array(
35: 'allRoles' => array(
36: 'allPrivileges' => array(
37: 'type' => self::DENY,
38: 'assert' => NULL,
39: ),
40: 'byPrivilege' => array(),
41: ),
42: 'byRole' => array(),
43: ),
44: 'byResource' => array(),
45: );
46:
47:
48: private $queriedRole, $queriedResource;
49:
50:
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 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:
101: public function hasRole($role)
102: {
103: $this->checkRole($role, FALSE);
104: return isset($this->roles[$role]);
105: }
106:
107:
108:
109: 110: 111: 112: 113: 114: 115:
116: private function checkRole($role, $need = TRUE)
117: {
118: if (!is_string($role) || $role === '') {
119: throw new InvalidArgumentException("Role must be a non-empty string.");
120:
121: } elseif ($need && !isset($this->roles[$role])) {
122: throw new InvalidStateException("Role '$role' does not exist.");
123: }
124: }
125:
126:
127:
128: 129: 130: 131: 132:
133: public function getRoleParents($role)
134: {
135: $this->checkRole($role);
136: return array_keys($this->roles[$role]['parents']);
137: }
138:
139:
140:
141: 142: 143: 144: 145: 146: 147: 148: 149:
150: public function roleInheritsFrom($role, $inherit, $onlyParents = FALSE)
151: {
152: $this->checkRole($role);
153: $this->checkRole($inherit);
154:
155: $inherits = isset($this->roles[$role]['parents'][$inherit]);
156:
157: if ($inherits || $onlyParents) {
158: return $inherits;
159: }
160:
161: foreach ($this->roles[$role]['parents'] as $parent => $foo) {
162: if ($this->roleInheritsFrom($parent, $inherit)) {
163: return TRUE;
164: }
165: }
166:
167: return FALSE;
168: }
169:
170:
171:
172: 173: 174: 175: 176: 177: 178:
179: public function removeRole($role)
180: {
181: $this->checkRole($role);
182:
183: foreach ($this->roles[$role]['children'] as $child => $foo) {
184: unset($this->roles[$child]['parents'][$role]);
185: }
186:
187: foreach ($this->roles[$role]['parents'] as $parent => $foo) {
188: unset($this->roles[$parent]['children'][$role]);
189: }
190:
191: unset($this->roles[$role]);
192:
193: foreach ($this->rules['allResources']['byRole'] as $roleCurrent => $rules) {
194: if ($role === $roleCurrent) {
195: unset($this->rules['allResources']['byRole'][$roleCurrent]);
196: }
197: }
198:
199: foreach ($this->rules['byResource'] as $resourceCurrent => $visitor) {
200: if (isset($visitor['byRole'])) {
201: foreach ($visitor['byRole'] as $roleCurrent => $rules) {
202: if ($role === $roleCurrent) {
203: unset($this->rules['byResource'][$resourceCurrent]['byRole'][$roleCurrent]);
204: }
205: }
206: }
207: }
208:
209: return $this;
210: }
211:
212:
213:
214: 215: 216: 217: 218:
219: public function removeAllRoles()
220: {
221: $this->roles = array();
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: 250:
251: public function addResource($resource, $parent = NULL)
252: {
253: $this->checkResource($resource, FALSE);
254:
255: if (isset($this->resources[$resource])) {
256: throw new InvalidStateException("Resource '$resource' already exists in the list.");
257: }
258:
259: if ($parent !== NULL) {
260: $this->checkResource($parent);
261: $this->resources[$parent]['children'][$resource] = TRUE;
262: }
263:
264: $this->resources[$resource] = array(
265: 'parent' => $parent,
266: 'children' => array()
267: );
268:
269: return $this;
270: }
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:
294: private function checkResource($resource, $need = TRUE)
295: {
296: if (!is_string($resource) || $resource === '') {
297: throw new InvalidArgumentException("Resource must be a non-empty string.");
298:
299: } elseif ($need && !isset($this->resources[$resource])) {
300: throw new InvalidStateException("Resource '$resource' does not exist.");
301: }
302: }
303:
304:
305:
306: 307: 308: 309: 310: 311: 312: 313: 314: 315:
316: public function resourceInheritsFrom($resource, $inherit, $onlyParent = FALSE)
317: {
318: $this->checkResource($resource);
319: $this->checkResource($inherit);
320:
321: if ($this->resources[$resource]['parent'] === NULL) {
322: return FALSE;
323: }
324:
325: $parent = $this->resources[$resource]['parent'];
326: if ($inherit === $parent) {
327: return TRUE;
328:
329: } elseif ($onlyParent) {
330: return FALSE;
331: }
332:
333: while ($this->resources[$parent]['parent'] !== NULL) {
334: $parent = $this->resources[$parent]['parent'];
335: if ($inherit === $parent) {
336: return TRUE;
337: }
338: }
339:
340: return FALSE;
341: }
342:
343:
344:
345: 346: 347: 348: 349: 350: 351:
352: public function removeResource($resource)
353: {
354: $this->checkResource($resource);
355:
356: $parent = $this->resources[$resource]['parent'];
357: if ($parent !== NULL) {
358: unset($this->resources[$parent]['children'][$resource]);
359: }
360:
361: $removed = array($resource);
362: foreach ($this->resources[$resource]['children'] as $child => $foo) {
363: $this->removeResource($child);
364: $removed[] = $child;
365: }
366:
367: foreach ($removed as $resourceRemoved) {
368: foreach ($this->rules['byResource'] as $resourceCurrent => $rules) {
369: if ($resourceRemoved === $resourceCurrent) {
370: unset($this->rules['byResource'][$resourceCurrent]);
371: }
372: }
373: }
374:
375: unset($this->resources[$resource]);
376: return $this;
377: }
378:
379:
380:
381: 382: 383: 384:
385: public function removeAllResources()
386: {
387: foreach ($this->resources as $resource => $foo) {
388: foreach ($this->rules['byResource'] as $resourceCurrent => $rules) {
389: if ($resource === $resourceCurrent) {
390: unset($this->rules['byResource'][$resourceCurrent]);
391: }
392: }
393: }
394:
395: $this->resources = array();
396: return $this;
397: }
398:
399:
400:
401:
402:
403:
404:
405: 406: 407: 408: 409: 410: 411: 412: 413: 414:
415: public function allow($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL, $assertion = NULL)
416: {
417: $this->setRule(TRUE, self::ALLOW, $roles, $resources, $privileges, $assertion);
418: return $this;
419: }
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:
449: public function removeAllow($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL)
450: {
451: $this->setRule(FALSE, self::ALLOW, $roles, $resources, $privileges);
452: return $this;
453: }
454:
455:
456:
457: 458: 459: 460: 461: 462: 463: 464:
465: public function removeDeny($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL)
466: {
467: $this->setRule(FALSE, self::DENY, $roles, $resources, $privileges);
468: return $this;
469: }
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: $assertion = $assertion ? callback($assertion) : NULL;
523:
524: if ($toAdd) {
525: foreach ($resources as $resource) {
526: foreach ($roles as $role) {
527: $rules = & $this->getRules($resource, $role, TRUE);
528: if (count($privileges) === 0) {
529: $rules['allPrivileges']['type'] = $type;
530: $rules['allPrivileges']['assert'] = $assertion;
531: if (!isset($rules['byPrivilege'])) {
532: $rules['byPrivilege'] = array();
533: }
534: } else {
535: foreach ($privileges as $privilege) {
536: $rules['byPrivilege'][$privilege]['type'] = $type;
537: $rules['byPrivilege'][$privilege]['assert'] = $assertion;
538: }
539: }
540: }
541: }
542:
543: } else {
544: foreach ($resources as $resource) {
545: foreach ($roles as $role) {
546: $rules = & $this->getRules($resource, $role);
547: if ($rules === NULL) {
548: continue;
549: }
550: if (count($privileges) === 0) {
551: if ($resource === self::ALL && $role === self::ALL) {
552: if ($type === $rules['allPrivileges']['type']) {
553: $rules = array(
554: 'allPrivileges' => array(
555: 'type' => self::DENY,
556: 'assert' => NULL
557: ),
558: 'byPrivilege' => array()
559: );
560: }
561: continue;
562: }
563: if ($type === $rules['allPrivileges']['type']) {
564: unset($rules['allPrivileges']);
565: }
566: } else {
567: foreach ($privileges as $privilege) {
568: if (isset($rules['byPrivilege'][$privilege]) &&
569: $type === $rules['byPrivilege'][$privilege]['type']) {
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: 599:
600: public function isAllowed($role = self::ALL, $resource = self::ALL, $privilege = self::ALL)
601: {
602: $this->queriedRole = $role;
603: if ($role !== self::ALL) {
604: if ($role instanceof IRole) {
605: $role = $role->getRoleId();
606: }
607: $this->checkRole($role);
608: }
609:
610: $this->queriedResource = $resource;
611: if ($resource !== self::ALL) {
612: if ($resource instanceof IResource) {
613: $resource = $resource->getResourceId();
614: }
615: $this->checkResource($resource);
616: }
617:
618: do {
619:
620: if ($role !== NULL && NULL !== ($result = $this->searchRolePrivileges($privilege === self::ALL, $role, $resource, $privilege))) {
621: break;
622: }
623:
624: if ($privilege === self::ALL) {
625: if ($rules = $this->getRules($resource, self::ALL)) {
626: foreach ($rules['byPrivilege'] as $privilege => $rule) {
627: if (self::DENY === ($result = $this->getRuleType($resource, NULL, $privilege))) {
628: break 2;
629: }
630: }
631: if (NULL !== ($result = $this->getRuleType($resource, NULL, NULL))) {
632: break;
633: }
634: }
635: } else {
636: if (NULL !== ($result = $this->getRuleType($resource, NULL, $privilege))) {
637: break;
638:
639: } elseif (NULL !== ($result = $this->getRuleType($resource, NULL, NULL))) {
640: break;
641: }
642: }
643:
644: $resource = $this->resources[$resource]['parent'];
645: } while (TRUE);
646:
647: $this->queriedRole = $this->queriedResource = NULL;
648: return $result;
649: }
650:
651:
652:
653: 654: 655: 656:
657: public function getQueriedRole()
658: {
659: return $this->queriedRole;
660: }
661:
662:
663:
664: 665: 666: 667:
668: public function getQueriedResource()
669: {
670: return $this->queriedResource;
671: }
672:
673:
674:
675:
676:
677:
678:
679: 680: 681: 682: 683: 684: 685: 686: 687:
688: private function searchRolePrivileges($all, $role, $resource, $privilege)
689: {
690: $dfs = array(
691: 'visited' => array(),
692: 'stack' => array($role),
693: );
694:
695: while (NULL !== ($role = array_pop($dfs['stack']))) {
696: if (isset($dfs['visited'][$role])) {
697: continue;
698: }
699: if ($all) {
700: if ($rules = $this->getRules($resource, $role)) {
701: foreach ($rules['byPrivilege'] as $privilege2 => $rule) {
702: if (self::DENY === $this->getRuleType($resource, $role, $privilege2)) {
703: return self::DENY;
704: }
705: }
706: if (NULL !== ($type = $this->getRuleType($resource, $role, NULL))) {
707: return $type;
708: }
709: }
710: } else {
711: if (NULL !== ($type = $this->getRuleType($resource, $role, $privilege))) {
712: return $type;
713:
714: } elseif (NULL !== ($type = $this->getRuleType($resource, $role, NULL))) {
715: return $type;
716: }
717: }
718:
719: $dfs['visited'][$role] = TRUE;
720: foreach ($this->roles[$role]['parents'] as $roleParent => $foo) {
721: $dfs['stack'][] = $roleParent;
722: }
723: }
724: return NULL;
725: }
726:
727:
728:
729: 730: 731: 732: 733: 734: 735:
736: private function getRuleType($resource, $role, $privilege)
737: {
738: if (!$rules = $this->getRules($resource, $role)) {
739: return NULL;
740: }
741:
742: if ($privilege === self::ALL) {
743: if (isset($rules['allPrivileges'])) {
744: $rule = $rules['allPrivileges'];
745: } else {
746: return NULL;
747: }
748: } elseif (!isset($rules['byPrivilege'][$privilege])) {
749: return NULL;
750:
751: } else {
752: $rule = $rules['byPrivilege'][$privilege];
753: }
754:
755: if ($rule['assert'] === NULL || $rule['assert']->__invoke($this, $role, $resource, $privilege)) {
756: return $rule['type'];
757:
758: } elseif ($resource !== self::ALL || $role !== self::ALL || $privilege !== self::ALL) {
759: return NULL;
760:
761: } elseif (self::ALLOW === $rule['type']) {
762: return self::DENY;
763:
764: } else {
765: return self::ALLOW;
766: }
767: }
768:
769:
770:
771: 772: 773: 774: 775: 776: 777: 778:
779: private function & getRules($resource, $role, $create = FALSE)
780: {
781: $null = NULL;
782: if ($resource === self::ALL) {
783: $visitor = & $this->rules['allResources'];
784: } else {
785: if (!isset($this->rules['byResource'][$resource])) {
786: if (!$create) {
787: return $null;
788: }
789: $this->rules['byResource'][$resource] = array();
790: }
791: $visitor = & $this->rules['byResource'][$resource];
792: }
793:
794: if ($role === self::ALL) {
795: if (!isset($visitor['allRoles'])) {
796: if (!$create) {
797: return $null;
798: }
799: $visitor['allRoles']['byPrivilege'] = array();
800: }
801: return $visitor['allRoles'];
802: }
803:
804: if (!isset($visitor['byRole'][$role])) {
805: if (!$create) {
806: return $null;
807: }
808: $visitor['byRole'][$role]['byPrivilege'] = array();
809: }
810:
811: return $visitor['byRole'][$role];
812: }
813:
814: }
815: