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