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