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: class Permission extends Nette\Object implements IAuthorizator
27: {
28:
29: private $roles = array();
30:
31:
32: private $resources = array();
33:
34:
35: private $rules = array(
36: 'allResources' => array(
37: 'allRoles' => array(
38: 'allPrivileges' => array(
39: 'type' => self::DENY,
40: 'assert' => NULL,
41: ),
42: 'byPrivilege' => array(),
43: ),
44: 'byRole' => array(),
45: ),
46: 'byResource' => array(),
47: );
48:
49:
50: private $queriedRole, $queriedResource;
51:
52:
53:
54:
55:
56:
57:
58: 59: 60: 61: 62: 63: 64: 65: 66:
67: public function addRole($role, $parents = NULL)
68: {
69: $this->checkRole($role, FALSE);
70: if (isset($this->roles[$role])) {
71: throw new Nette\InvalidStateException("Role '$role' already exists in the list.");
72: }
73:
74: $roleParents = array();
75:
76: if ($parents !== NULL) {
77: if (!is_array($parents)) {
78: $parents = array($parents);
79: }
80:
81: foreach ($parents as $parent) {
82: $this->checkRole($parent);
83: $roleParents[$parent] = TRUE;
84: $this->roles[$parent]['children'][$role] = TRUE;
85: }
86: }
87:
88: $this->roles[$role] = array(
89: 'parents' => $roleParents,
90: 'children' => array(),
91: );
92:
93: return $this;
94: }
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:
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: 134:
135: public function getRoleParents($role)
136: {
137: $this->checkRole($role);
138: return array_keys($this->roles[$role]['parents']);
139: }
140:
141:
142:
143: 144: 145: 146: 147: 148: 149: 150: 151:
152: public function roleInheritsFrom($role, $inherit, $onlyParents = FALSE)
153: {
154: $this->checkRole($role);
155: $this->checkRole($inherit);
156:
157: $inherits = isset($this->roles[$role]['parents'][$inherit]);
158:
159: if ($inherits || $onlyParents) {
160: return $inherits;
161: }
162:
163: foreach ($this->roles[$role]['parents'] as $parent => $foo) {
164: if ($this->roleInheritsFrom($parent, $inherit)) {
165: return TRUE;
166: }
167: }
168:
169: return FALSE;
170: }
171:
172:
173:
174: 175: 176: 177: 178: 179: 180:
181: public function removeRole($role)
182: {
183: $this->checkRole($role);
184:
185: foreach ($this->roles[$role]['children'] as $child => $foo) {
186: unset($this->roles[$child]['parents'][$role]);
187: }
188:
189: foreach ($this->roles[$role]['parents'] as $parent => $foo) {
190: unset($this->roles[$parent]['children'][$role]);
191: }
192:
193: unset($this->roles[$role]);
194:
195: foreach ($this->rules['allResources']['byRole'] as $roleCurrent => $rules) {
196: if ($role === $roleCurrent) {
197: unset($this->rules['allResources']['byRole'][$roleCurrent]);
198: }
199: }
200:
201: foreach ($this->rules['byResource'] as $resourceCurrent => $visitor) {
202: if (isset($visitor['byRole'])) {
203: foreach ($visitor['byRole'] as $roleCurrent => $rules) {
204: if ($role === $roleCurrent) {
205: unset($this->rules['byResource'][$resourceCurrent]['byRole'][$roleCurrent]);
206: }
207: }
208: }
209: }
210:
211: return $this;
212: }
213:
214:
215:
216: 217: 218: 219: 220:
221: public function removeAllRoles()
222: {
223: $this->roles = array();
224:
225: foreach ($this->rules['allResources']['byRole'] as $roleCurrent => $rules) {
226: unset($this->rules['allResources']['byRole'][$roleCurrent]);
227: }
228:
229: foreach ($this->rules['byResource'] as $resourceCurrent => $visitor) {
230: foreach ($visitor['byRole'] as $roleCurrent => $rules) {
231: unset($this->rules['byResource'][$resourceCurrent]['byRole'][$roleCurrent]);
232: }
233: }
234:
235: return $this;
236: }
237:
238:
239:
240:
241:
242:
243:
244: 245: 246: 247: 248: 249: 250: 251: 252:
253: public function addResource($resource, $parent = NULL)
254: {
255: $this->checkResource($resource, FALSE);
256:
257: if (isset($this->resources[$resource])) {
258: throw new Nette\InvalidStateException("Resource '$resource' already exists in the list.");
259: }
260:
261: if ($parent !== NULL) {
262: $this->checkResource($parent);
263: $this->resources[$parent]['children'][$resource] = TRUE;
264: }
265:
266: $this->resources[$resource] = array(
267: 'parent' => $parent,
268: 'children' => array()
269: );
270:
271: return $this;
272: }
273:
274:
275:
276: 277: 278: 279: 280:
281: public function hasResource($resource)
282: {
283: $this->checkResource($resource, FALSE);
284: return isset($this->resources[$resource]);
285: }
286:
287:
288:
289: 290: 291: 292: 293: 294: 295:
296: private function checkResource($resource, $need = TRUE)
297: {
298: if (!is_string($resource) || $resource === '') {
299: throw new Nette\InvalidArgumentException("Resource must be a non-empty string.");
300:
301: } elseif ($need && !isset($this->resources[$resource])) {
302: throw new Nette\InvalidStateException("Resource '$resource' does not exist.");
303: }
304: }
305:
306:
307:
308: 309: 310: 311: 312: 313: 314: 315: 316: 317:
318: public function resourceInheritsFrom($resource, $inherit, $onlyParent = FALSE)
319: {
320: $this->checkResource($resource);
321: $this->checkResource($inherit);
322:
323: if ($this->resources[$resource]['parent'] === NULL) {
324: return FALSE;
325: }
326:
327: $parent = $this->resources[$resource]['parent'];
328: if ($inherit === $parent) {
329: return TRUE;
330:
331: } elseif ($onlyParent) {
332: return FALSE;
333: }
334:
335: while ($this->resources[$parent]['parent'] !== NULL) {
336: $parent = $this->resources[$parent]['parent'];
337: if ($inherit === $parent) {
338: return TRUE;
339: }
340: }
341:
342: return FALSE;
343: }
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:
387: public function removeAllResources()
388: {
389: foreach ($this->resources as $resource => $foo) {
390: foreach ($this->rules['byResource'] as $resourceCurrent => $rules) {
391: if ($resource === $resourceCurrent) {
392: unset($this->rules['byResource'][$resourceCurrent]);
393: }
394: }
395: }
396:
397: $this->resources = array();
398: return $this;
399: }
400:
401:
402:
403:
404:
405:
406:
407: 408: 409: 410: 411: 412: 413: 414: 415: 416:
417: public function allow($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL, $assertion = NULL)
418: {
419: $this->setRule(TRUE, self::ALLOW, $roles, $resources, $privileges, $assertion);
420: return $this;
421: }
422:
423:
424:
425: 426: 427: 428: 429: 430: 431: 432: 433: 434:
435: public function deny($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL, $assertion = NULL)
436: {
437: $this->setRule(TRUE, self::DENY, $roles, $resources, $privileges, $assertion);
438: return $this;
439: }
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:
467: public function removeDeny($roles = self::ALL, $resources = self::ALL, $privileges = self::ALL)
468: {
469: $this->setRule(FALSE, self::DENY, $roles, $resources, $privileges);
470: return $this;
471: }
472:
473:
474:
475: 476: 477: 478: 479: 480: 481: 482: 483: 484: 485:
486: protected function setRule($toAdd, $type, $roles, $resources, $privileges, $assertion = NULL)
487: {
488:
489: if ($roles === self::ALL) {
490: $roles = array(self::ALL);
491:
492: } else {
493: if (!is_array($roles)) {
494: $roles = array($roles);
495: }
496:
497: foreach ($roles as $role) {
498: $this->checkRole($role);
499: }
500: }
501:
502:
503: if ($resources === self::ALL) {
504: $resources = array(self::ALL);
505:
506: } else {
507: if (!is_array($resources)) {
508: $resources = array($resources);
509: }
510:
511: foreach ($resources as $resource) {
512: $this->checkResource($resource);
513: }
514: }
515:
516:
517: if ($privileges === self::ALL) {
518: $privileges = array();
519:
520: } elseif (!is_array($privileges)) {
521: $privileges = array($privileges);
522: }
523:
524: $assertion = $assertion ? callback($assertion) : NULL;
525:
526: if ($toAdd) {
527: foreach ($resources as $resource) {
528: foreach ($roles as $role) {
529: $rules = & $this->getRules($resource, $role, TRUE);
530: if (count($privileges) === 0) {
531: $rules['allPrivileges']['type'] = $type;
532: $rules['allPrivileges']['assert'] = $assertion;
533: if (!isset($rules['byPrivilege'])) {
534: $rules['byPrivilege'] = array();
535: }
536: } else {
537: foreach ($privileges as $privilege) {
538: $rules['byPrivilege'][$privilege]['type'] = $type;
539: $rules['byPrivilege'][$privilege]['assert'] = $assertion;
540: }
541: }
542: }
543: }
544:
545: } else {
546: foreach ($resources as $resource) {
547: foreach ($roles as $role) {
548: $rules = & $this->getRules($resource, $role);
549: if ($rules === NULL) {
550: continue;
551: }
552: if (count($privileges) === 0) {
553: if ($resource === self::ALL && $role === self::ALL) {
554: if ($type === $rules['allPrivileges']['type']) {
555: $rules = array(
556: 'allPrivileges' => array(
557: 'type' => self::DENY,
558: 'assert' => NULL
559: ),
560: 'byPrivilege' => array()
561: );
562: }
563: continue;
564: }
565: if ($type === $rules['allPrivileges']['type']) {
566: unset($rules['allPrivileges']);
567: }
568: } else {
569: foreach ($privileges as $privilege) {
570: if (isset($rules['byPrivilege'][$privilege]) &&
571: $type === $rules['byPrivilege'][$privilege]['type']) {
572: unset($rules['byPrivilege'][$privilege]);
573: }
574: }
575: }
576: }
577: }
578: }
579: return $this;
580: }
581:
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:
659: public function getQueriedRole()
660: {
661: return $this->queriedRole;
662: }
663:
664:
665:
666: 667: 668: 669:
670: public function getQueriedResource()
671: {
672: return $this->queriedResource;
673: }
674:
675:
676:
677:
678:
679:
680:
681: 682: 683: 684: 685: 686: 687: 688: 689:
690: private function searchRolePrivileges($all, $role, $resource, $privilege)
691: {
692: $dfs = array(
693: 'visited' => array(),
694: 'stack' => array($role),
695: );
696:
697: while (NULL !== ($role = array_pop($dfs['stack']))) {
698: if (isset($dfs['visited'][$role])) {
699: continue;
700: }
701: if ($all) {
702: if ($rules = $this->getRules($resource, $role)) {
703: foreach ($rules['byPrivilege'] as $privilege2 => $rule) {
704: if (self::DENY === $this->getRuleType($resource, $role, $privilege2)) {
705: return self::DENY;
706: }
707: }
708: if (NULL !== ($type = $this->getRuleType($resource, $role, NULL))) {
709: return $type;
710: }
711: }
712: } else {
713: if (NULL !== ($type = $this->getRuleType($resource, $role, $privilege))) {
714: return $type;
715:
716: } elseif (NULL !== ($type = $this->getRuleType($resource, $role, NULL))) {
717: return $type;
718: }
719: }
720:
721: $dfs['visited'][$role] = TRUE;
722: foreach ($this->roles[$role]['parents'] as $roleParent => $foo) {
723: $dfs['stack'][] = $roleParent;
724: }
725: }
726: return NULL;
727: }
728:
729:
730:
731: 732: 733: 734: 735: 736: 737:
738: private function getRuleType($resource, $role, $privilege)
739: {
740: if (!$rules = $this->getRules($resource, $role)) {
741: return NULL;
742: }
743:
744: if ($privilege === self::ALL) {
745: if (isset($rules['allPrivileges'])) {
746: $rule = $rules['allPrivileges'];
747: } else {
748: return NULL;
749: }
750: } elseif (!isset($rules['byPrivilege'][$privilege])) {
751: return NULL;
752:
753: } else {
754: $rule = $rules['byPrivilege'][$privilege];
755: }
756:
757: if ($rule['assert'] === NULL || $rule['assert']->__invoke($this, $role, $resource, $privilege)) {
758: return $rule['type'];
759:
760: } elseif ($resource !== self::ALL || $role !== self::ALL || $privilege !== self::ALL) {
761: return NULL;
762:
763: } elseif (self::ALLOW === $rule['type']) {
764: return self::DENY;
765:
766: } else {
767: return self::ALLOW;
768: }
769: }
770:
771:
772:
773: 774: 775: 776: 777: 778: 779: 780:
781: private function & getRules($resource, $role, $create = FALSE)
782: {
783: $null = NULL;
784: if ($resource === self::ALL) {
785: $visitor = & $this->rules['allResources'];
786: } else {
787: if (!isset($this->rules['byResource'][$resource])) {
788: if (!$create) {
789: return $null;
790: }
791: $this->rules['byResource'][$resource] = array();
792: }
793: $visitor = & $this->rules['byResource'][$resource];
794: }
795:
796: if ($role === self::ALL) {
797: if (!isset($visitor['allRoles'])) {
798: if (!$create) {
799: return $null;
800: }
801: $visitor['allRoles']['byPrivilege'] = array();
802: }
803: return $visitor['allRoles'];
804: }
805:
806: if (!isset($visitor['byRole'][$role])) {
807: if (!$create) {
808: return $null;
809: }
810: $visitor['byRole'][$role]['byPrivilege'] = array();
811: }
812:
813: return $visitor['byRole'][$role];
814: }
815:
816: }
817: