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 PhpAcl extends Object implements AclInterface {
26:
27: 28: 29: 30: 31:
32: const DENY = false;
33:
34: 35: 36: 37: 38:
39: const ALLOW = true;
40:
41: 42: 43: 44: 45: 46: 47:
48: public $options = array();
49:
50: 51: 52: 53: 54:
55: public $Aro = null;
56:
57: 58: 59: 60: 61:
62: public $Aco = null;
63:
64: 65: 66: 67: 68:
69: public function __construct() {
70: $this->options = array(
71: 'policy' => self::DENY,
72: 'config' => APP . 'Config' . DS . 'acl.php',
73: );
74: }
75:
76: 77: 78: 79: 80: 81:
82: public function initialize(Component $Component) {
83: if (!empty($Component->settings['adapter'])) {
84: $this->options = array_merge($this->options, $Component->settings['adapter']);
85: }
86:
87: App::uses('PhpReader', 'Configure');
88: $Reader = new PhpReader(dirname($this->options['config']) . DS);
89: $config = $Reader->read(basename($this->options['config']));
90: $this->build($config);
91: $Component->Aco = $this->Aco;
92: $Component->Aro = $this->Aro;
93: }
94:
95: 96: 97: 98: 99: 100: 101:
102: public function build(array $config) {
103: if (empty($config['roles'])) {
104: throw new AclException(__d('cake_dev', '"roles" section not found in configuration.'));
105: }
106:
107: if (empty($config['rules']['allow']) && empty($config['rules']['deny'])) {
108: throw new AclException(__d('cake_dev', 'Neither "allow" nor "deny" rules were provided in configuration.'));
109: }
110:
111: $rules['allow'] = !empty($config['rules']['allow']) ? $config['rules']['allow'] : array();
112: $rules['deny'] = !empty($config['rules']['deny']) ? $config['rules']['deny'] : array();
113: $roles = !empty($config['roles']) ? $config['roles'] : array();
114: $map = !empty($config['map']) ? $config['map'] : array();
115: $alias = !empty($config['alias']) ? $config['alias'] : array();
116:
117: $this->Aro = new PhpAro($roles, $map, $alias);
118: $this->Aco = new PhpAco($rules);
119: }
120:
121: 122: 123: 124: 125: 126: 127: 128:
129: public function allow($aro, $aco, $action = "*") {
130: return $this->Aco->access($this->Aro->resolve($aro), $aco, $action, 'allow');
131: }
132:
133: 134: 135: 136: 137: 138: 139: 140:
141: public function deny($aro, $aco, $action = "*") {
142: return $this->Aco->access($this->Aro->resolve($aro), $aco, $action, 'deny');
143: }
144:
145: 146: 147: 148: 149: 150: 151: 152:
153: public function inherit($aro, $aco, $action = "*") {
154: return false;
155: }
156:
157: 158: 159: 160: 161: 162: 163: 164: 165:
166: public function check($aro, $aco, $action = "*") {
167: $allow = $this->options['policy'];
168: $prioritizedAros = $this->Aro->roles($aro);
169:
170: if ($action && $action !== "*") {
171: $aco .= '/' . $action;
172: }
173:
174: $path = $this->Aco->path($aco);
175:
176: if (empty($path)) {
177: return $allow;
178: }
179:
180: foreach ($path as $node) {
181: foreach ($prioritizedAros as $aros) {
182: if (!empty($node['allow'])) {
183: $allow = $allow || count(array_intersect($node['allow'], $aros));
184: }
185:
186: if (!empty($node['deny'])) {
187: $allow = $allow && !count(array_intersect($node['deny'], $aros));
188: }
189: }
190: }
191:
192: return $allow;
193: }
194:
195: }
196:
197: 198: 199: 200:
201: class PhpAco {
202:
203: 204: 205: 206: 207:
208: protected $_tree = array();
209:
210: 211: 212: 213: 214:
215: public static $modifiers = array(
216: '*' => '.*',
217: );
218:
219: 220: 221: 222: 223:
224: public function __construct(array $rules = array()) {
225: foreach (array('allow', 'deny') as $type) {
226: if (empty($rules[$type])) {
227: $rules[$type] = array();
228: }
229: }
230:
231: $this->build($rules['allow'], $rules['deny']);
232: }
233:
234: 235: 236: 237: 238: 239:
240: public function path($aco) {
241: $aco = $this->resolve($aco);
242: $path = array();
243: $level = 0;
244: $root = $this->_tree;
245: $stack = array(array($root, 0));
246:
247: while (!empty($stack)) {
248: list($root, $level) = array_pop($stack);
249:
250: if (empty($path[$level])) {
251: $path[$level] = array();
252: }
253:
254: foreach ($root as $node => $elements) {
255: $pattern = '/^' . str_replace(array_keys(self::$modifiers), array_values(self::$modifiers), $node) . '$/';
256:
257: if ($node == $aco[$level] || preg_match($pattern, $aco[$level])) {
258:
259: foreach (array('allow', 'deny') as $policy) {
260: if (!empty($elements[$policy])) {
261: if (empty($path[$level][$policy])) {
262: $path[$level][$policy] = array();
263: }
264: $path[$level][$policy] = array_merge($path[$level][$policy], $elements[$policy]);
265: }
266: }
267:
268:
269: if (!empty($elements['children']) && isset($aco[$level + 1])) {
270: array_push($stack, array($elements['children'], $level + 1));
271: }
272: }
273: }
274: }
275:
276: return $path;
277: }
278:
279: 280: 281: 282: 283: 284: 285: 286: 287:
288: public function access($aro, $aco, $action, $type = 'deny') {
289: $aco = $this->resolve($aco);
290: $depth = count($aco);
291: $root = $this->_tree;
292: $tree = &$root;
293:
294: foreach ($aco as $i => $node) {
295: if (!isset($tree[$node])) {
296: $tree[$node] = array(
297: 'children' => array(),
298: );
299: }
300:
301: if ($i < $depth - 1) {
302: $tree = &$tree[$node]['children'];
303: } else {
304: if (empty($tree[$node][$type])) {
305: $tree[$node][$type] = array();
306: }
307:
308: $tree[$node][$type] = array_merge(is_array($aro) ? $aro : array($aro), $tree[$node][$type]);
309: }
310: }
311:
312: $this->_tree = &$root;
313: }
314:
315: 316: 317: 318: 319: 320:
321: public function resolve($aco) {
322: if (is_array($aco)) {
323: return array_map('strtolower', $aco);
324: }
325:
326:
327: $aco = preg_replace('#/+#', '/', $aco);
328:
329: $aco = ltrim(strtolower($aco), '/');
330: return array_filter(array_map('trim', explode('/', $aco)));
331: }
332:
333: 334: 335: 336: 337: 338: 339:
340: public function build(array $allow, array $deny = array()) {
341: $this->_tree = array();
342:
343: foreach ($allow as $dotPath => $aros) {
344: if (is_string($aros)) {
345: $aros = array_map('trim', explode(',', $aros));
346: }
347:
348: $this->access($aros, $dotPath, null, 'allow');
349: }
350:
351: foreach ($deny as $dotPath => $aros) {
352: if (is_string($aros)) {
353: $aros = array_map('trim', explode(',', $aros));
354: }
355:
356: $this->access($aros, $dotPath, null, 'deny');
357: }
358: }
359:
360: }
361:
362: 363: 364: 365:
366: class PhpAro {
367:
368: 369: 370: 371: 372: 373:
374: const DEFAULT_ROLE = 'Role/default';
375:
376: 377: 378: 379: 380: 381: 382: 383: 384: 385: 386: 387:
388: public $map = array(
389: 'User' => 'User/username',
390: 'Role' => 'User/role',
391: );
392:
393: 394: 395: 396: 397:
398: public $aliases = array();
399:
400: 401: 402: 403: 404:
405: protected $_tree = array();
406:
407: 408: 409: 410: 411: 412: 413:
414: public function __construct(array $aro = array(), array $map = array(), array $aliases = array()) {
415: if (!empty($map)) {
416: $this->map = $map;
417: }
418:
419: $this->aliases = $aliases;
420: $this->build($aro);
421: }
422:
423: 424: 425: 426: 427: 428: 429: 430: 431: 432:
433: public function roles($aro) {
434: $aros = array();
435: $aro = $this->resolve($aro);
436: $stack = array(array($aro, 0));
437:
438: while (!empty($stack)) {
439: list($element, $depth) = array_pop($stack);
440: $aros[$depth][] = $element;
441:
442: foreach ($this->_tree as $node => $children) {
443: if (in_array($element, $children)) {
444: array_push($stack, array($node, $depth + 1));
445: }
446: }
447: }
448:
449: return array_reverse($aros);
450: }
451:
452: 453: 454: 455: 456: 457: 458:
459: public function resolve($aro) {
460: foreach ($this->map as $aroGroup => $map) {
461: list ($model, $field) = explode('/', $map, 2);
462: $mapped = '';
463:
464: if (is_array($aro)) {
465: if (isset($aro['model']) && isset($aro['foreign_key']) && $aro['model'] === $aroGroup) {
466: $mapped = $aroGroup . '/' . $aro['foreign_key'];
467: } elseif (isset($aro[$model][$field])) {
468: $mapped = $aroGroup . '/' . $aro[$model][$field];
469: } elseif (isset($aro[$field])) {
470: $mapped = $aroGroup . '/' . $aro[$field];
471: }
472: } elseif (is_string($aro)) {
473: $aro = ltrim($aro, '/');
474:
475: if (strpos($aro, '/') === false) {
476: $mapped = $aroGroup . '/' . $aro;
477: } else {
478: list($aroModel, $aroValue) = explode('/', $aro, 2);
479:
480: $aroModel = Inflector::camelize($aroModel);
481:
482: if ($aroModel === $model || $aroModel === $aroGroup) {
483: $mapped = $aroGroup . '/' . $aroValue;
484: }
485: }
486: }
487:
488: if (isset($this->_tree[$mapped])) {
489: return $mapped;
490: }
491:
492:
493: if (!empty($this->aliases[$mapped])) {
494: return $this->aliases[$mapped];
495: }
496: }
497: return self::DEFAULT_ROLE;
498: }
499:
500: 501: 502: 503: 504: 505:
506: public function addRole(array $aro) {
507: foreach ($aro as $role => $inheritedRoles) {
508: if (!isset($this->_tree[$role])) {
509: $this->_tree[$role] = array();
510: }
511:
512: if (!empty($inheritedRoles)) {
513: if (is_string($inheritedRoles)) {
514: $inheritedRoles = array_map('trim', explode(',', $inheritedRoles));
515: }
516:
517: foreach ($inheritedRoles as $dependency) {
518:
519: $roles = $this->roles($dependency);
520:
521: if (in_array($role, Hash::flatten($roles))) {
522: $path = '';
523:
524: foreach ($roles as $roleDependencies) {
525: $path .= implode('|', (array)$roleDependencies) . ' -> ';
526: }
527:
528: trigger_error(__d('cake_dev', 'cycle detected when inheriting %s from %s. Path: %s', $role, $dependency, $path . $role));
529: continue;
530: }
531:
532: if (!isset($this->_tree[$dependency])) {
533: $this->_tree[$dependency] = array();
534: }
535:
536: $this->_tree[$dependency][] = $role;
537: }
538: }
539: }
540: }
541:
542: 543: 544: 545: 546: 547:
548: public function addAlias(array $alias) {
549: $this->aliases = array_merge($this->aliases, $alias);
550: }
551:
552: 553: 554: 555: 556: 557:
558: public function build(array $aros) {
559: $this->_tree = array();
560: $this->addRole($aros);
561: }
562:
563: }
564: