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