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 CakeObject 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' => static::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 = $Component->settings['adapter'] + $this->options;
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: class PhpAco {
201:
202: 203: 204: 205: 206:
207: protected $_tree = array();
208:
209: 210: 211: 212: 213:
214: public static $modifiers = array(
215: '*' => '.*',
216: );
217:
218: 219: 220: 221: 222:
223: public function __construct(array $rules = array()) {
224: foreach (array('allow', 'deny') as $type) {
225: if (empty($rules[$type])) {
226: $rules[$type] = array();
227: }
228: }
229:
230: $this->build($rules['allow'], $rules['deny']);
231: }
232:
233: 234: 235: 236: 237: 238:
239: public function path($aco) {
240: $aco = $this->resolve($aco);
241: $path = array();
242: $level = 0;
243: $root = $this->_tree;
244: $stack = array(array($root, 0));
245:
246: while (!empty($stack)) {
247: list($root, $level) = array_pop($stack);
248:
249: if (empty($path[$level])) {
250: $path[$level] = array();
251: }
252:
253: foreach ($root as $node => $elements) {
254: $pattern = '/^' . str_replace(array_keys(static::$modifiers), array_values(static::$modifiers), $node) . '$/';
255:
256: if ($node == $aco[$level] || preg_match($pattern, $aco[$level])) {
257:
258: foreach (array('allow', 'deny') as $policy) {
259: if (!empty($elements[$policy])) {
260: if (empty($path[$level][$policy])) {
261: $path[$level][$policy] = array();
262: }
263: $path[$level][$policy] = array_merge($path[$level][$policy], $elements[$policy]);
264: }
265: }
266:
267:
268: if (!empty($elements['children']) && isset($aco[$level + 1])) {
269: array_push($stack, array($elements['children'], $level + 1));
270: }
271: }
272: }
273: }
274:
275: return $path;
276: }
277:
278: 279: 280: 281: 282: 283: 284: 285: 286:
287: public function access($aro, $aco, $action, $type = 'deny') {
288: $aco = $this->resolve($aco);
289: $depth = count($aco);
290: $root = $this->_tree;
291: $tree = &$root;
292:
293: foreach ($aco as $i => $node) {
294: if (!isset($tree[$node])) {
295: $tree[$node] = array(
296: 'children' => array(),
297: );
298: }
299:
300: if ($i < $depth - 1) {
301: $tree = &$tree[$node]['children'];
302: } else {
303: if (empty($tree[$node][$type])) {
304: $tree[$node][$type] = array();
305: }
306:
307: $tree[$node][$type] = array_merge(is_array($aro) ? $aro : array($aro), $tree[$node][$type]);
308: }
309: }
310:
311: $this->_tree = &$root;
312: }
313:
314: 315: 316: 317: 318: 319:
320: public function resolve($aco) {
321: if (is_array($aco)) {
322: return array_map('strtolower', $aco);
323: }
324:
325:
326: $aco = preg_replace('#/+#', '/', $aco);
327:
328: $aco = ltrim(strtolower($aco), '/');
329: return array_filter(array_map('trim', explode('/', $aco)));
330: }
331:
332: 333: 334: 335: 336: 337: 338:
339: public function build(array $allow, array $deny = array()) {
340: $this->_tree = array();
341:
342: foreach ($allow as $dotPath => $aros) {
343: if (is_string($aros)) {
344: $aros = array_map('trim', explode(',', $aros));
345: }
346:
347: $this->access($aros, $dotPath, null, 'allow');
348: }
349:
350: foreach ($deny as $dotPath => $aros) {
351: if (is_string($aros)) {
352: $aros = array_map('trim', explode(',', $aros));
353: }
354:
355: $this->access($aros, $dotPath, null, 'deny');
356: }
357: }
358:
359: }
360:
361: 362: 363:
364: class PhpAro {
365:
366: 367: 368: 369: 370: 371:
372: const DEFAULT_ROLE = 'Role/default';
373:
374: 375: 376: 377: 378: 379: 380: 381: 382: 383: 384: 385:
386: public $map = array(
387: 'User' => 'User/username',
388: 'Role' => 'User/role',
389: );
390:
391: 392: 393: 394: 395:
396: public $aliases = array();
397:
398: 399: 400: 401: 402:
403: protected $_tree = array();
404:
405: 406: 407: 408: 409: 410: 411:
412: public function __construct(array $aro = array(), array $map = array(), array $aliases = array()) {
413: if (!empty($map)) {
414: $this->map = $map;
415: }
416:
417: $this->aliases = $aliases;
418: $this->build($aro);
419: }
420:
421: 422: 423: 424: 425: 426: 427: 428: 429: 430:
431: public function roles($aro) {
432: $aros = array();
433: $aro = $this->resolve($aro);
434: $stack = array(array($aro, 0));
435:
436: while (!empty($stack)) {
437: list($element, $depth) = array_pop($stack);
438: $aros[$depth][] = $element;
439:
440: foreach ($this->_tree as $node => $children) {
441: if (in_array($element, $children)) {
442: array_push($stack, array($node, $depth + 1));
443: }
444: }
445: }
446:
447: return array_reverse($aros);
448: }
449:
450: 451: 452: 453: 454: 455: 456:
457: public function resolve($aro) {
458: foreach ($this->map as $aroGroup => $map) {
459: list ($model, $field) = explode('/', $map, 2);
460: $mapped = '';
461:
462: if (is_array($aro)) {
463: if (isset($aro['model']) && isset($aro['foreign_key']) && $aro['model'] === $aroGroup) {
464: $mapped = $aroGroup . '/' . $aro['foreign_key'];
465: } elseif (isset($aro[$model][$field])) {
466: $mapped = $aroGroup . '/' . $aro[$model][$field];
467: } elseif (isset($aro[$field])) {
468: $mapped = $aroGroup . '/' . $aro[$field];
469: }
470: } elseif (is_string($aro)) {
471: $aro = ltrim($aro, '/');
472:
473: if (strpos($aro, '/') === false) {
474: $mapped = $aroGroup . '/' . $aro;
475: } else {
476: list($aroModel, $aroValue) = explode('/', $aro, 2);
477:
478: $aroModel = Inflector::camelize($aroModel);
479:
480: if ($aroModel === $model || $aroModel === $aroGroup) {
481: $mapped = $aroGroup . '/' . $aroValue;
482: }
483: }
484: }
485:
486: if (isset($this->_tree[$mapped])) {
487: return $mapped;
488: }
489:
490:
491: if (!empty($this->aliases[$mapped])) {
492: return $this->aliases[$mapped];
493: }
494: }
495: return static::DEFAULT_ROLE;
496: }
497:
498: 499: 500: 501: 502: 503:
504: public function addRole(array $aro) {
505: foreach ($aro as $role => $inheritedRoles) {
506: if (!isset($this->_tree[$role])) {
507: $this->_tree[$role] = array();
508: }
509:
510: if (!empty($inheritedRoles)) {
511: if (is_string($inheritedRoles)) {
512: $inheritedRoles = array_map('trim', explode(',', $inheritedRoles));
513: }
514:
515: foreach ($inheritedRoles as $dependency) {
516:
517: $roles = $this->roles($dependency);
518:
519: if (in_array($role, Hash::flatten($roles))) {
520: $path = '';
521:
522: foreach ($roles as $roleDependencies) {
523: $path .= implode('|', (array)$roleDependencies) . ' -> ';
524: }
525:
526: trigger_error(__d('cake_dev', 'cycle detected when inheriting %s from %s. Path: %s', $role, $dependency, $path . $role));
527: continue;
528: }
529:
530: if (!isset($this->_tree[$dependency])) {
531: $this->_tree[$dependency] = array();
532: }
533:
534: $this->_tree[$dependency][] = $role;
535: }
536: }
537: }
538: }
539:
540: 541: 542: 543: 544: 545:
546: public function addAlias(array $alias) {
547: $this->aliases = $alias + $this->aliases;
548: }
549:
550: 551: 552: 553: 554: 555:
556: public function build(array $aros) {
557: $this->_tree = array();
558: $this->addRole($aros);
559: }
560:
561: }
562: