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