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