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: class NPermission extends NObject 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 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 InvalidArgumentException('Role must be a non-empty string.');
117:
118: } elseif ($need && !isset($this->roles[$role])) {
119: throw new 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 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 InvalidArgumentException('Resource must be a non-empty string.');
297:
298: } elseif ($need && !isset($this->resources[$resource])) {
299: throw new 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: $assertion = $assertion ? new NCallback($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: 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 || $rule['assert']->__invoke($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: