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