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: $this->_tree = array();
322: $tree = array();
323:
324: foreach ($allow as $dotPath => $aros) {
325: if (is_string($aros)) {
326: $aros = array_map('trim', explode(',', $aros));
327: }
328:
329: $this->access($aros, $dotPath, null, 'allow');
330: }
331:
332: foreach ($deny as $dotPath => $aros) {
333: if (is_string($aros)) {
334: $aros = array_map('trim', explode(',', $aros));
335: }
336:
337: $this->access($aros, $dotPath, null, 'deny');
338: }
339: }
340:
341: }
342:
343: 344: 345: 346:
347: class PhpAro {
348:
349: 350: 351: 352: 353: 354:
355: const DEFAULT_ROLE = 'Role/default';
356:
357: 358: 359: 360: 361: 362: 363: 364: 365: 366: 367: 368:
369: public $map = array(
370: 'User' => 'User/username',
371: 'Role' => 'User/role',
372: );
373:
374: 375: 376: 377: 378:
379: public $aliases = array();
380:
381: 382: 383: 384: 385:
386: protected $_tree = array();
387:
388: public function __construct(array $aro = array(), array $map = array(), array $aliases = array()) {
389: if (!empty($map)) {
390: $this->map = $map;
391: }
392:
393: $this->aliases = $aliases;
394: $this->build($aro);
395: }
396:
397: 398: 399: 400: 401: 402: 403: 404: 405: 406:
407: public function roles($aro) {
408: $aros = array();
409: $aro = $this->resolve($aro);
410: $stack = array(array($aro, 0));
411:
412: while (!empty($stack)) {
413: list($element, $depth) = array_pop($stack);
414: $aros[$depth][] = $element;
415:
416: foreach ($this->_tree as $node => $children) {
417: if (in_array($element, $children)) {
418: array_push($stack, array($node, $depth + 1));
419: }
420: }
421: }
422:
423: return array_reverse($aros);
424: }
425:
426: 427: 428: 429: 430: 431: 432:
433: public function resolve($aro) {
434: foreach ($this->map as $aroGroup => $map) {
435: list ($model, $field) = explode('/', $map, 2);
436: $mapped = '';
437:
438: if (is_array($aro)) {
439: if (isset($aro['model']) && isset($aro['foreign_key']) && $aro['model'] == $aroGroup) {
440: $mapped = $aroGroup . '/' . $aro['foreign_key'];
441: } elseif (isset($aro[$model][$field])) {
442: $mapped = $aroGroup . '/' . $aro[$model][$field];
443: } elseif (isset($aro[$field])) {
444: $mapped = $aroGroup . '/' . $aro[$field];
445: }
446: } elseif (is_string($aro)) {
447: $aro = ltrim($aro, '/');
448:
449: if (strpos($aro, '/') === false) {
450: $mapped = $aroGroup . '/' . $aro;
451: } else {
452: list($aroModel, $aroValue) = explode('/', $aro, 2);
453:
454: $aroModel = Inflector::camelize($aroModel);
455:
456: if ($aroModel == $model || $aroModel == $aroGroup) {
457: $mapped = $aroGroup . '/' . $aroValue;
458: }
459: }
460: }
461:
462: if (isset($this->_tree[$mapped])) {
463: return $mapped;
464: }
465:
466:
467: if (!empty($this->aliases[$mapped])) {
468: return $this->aliases[$mapped];
469: }
470: }
471: return self::DEFAULT_ROLE;
472: }
473:
474: 475: 476: 477: 478: 479:
480: public function addRole(array $aro) {
481: foreach ($aro as $role => $inheritedRoles) {
482: if (!isset($this->_tree[$role])) {
483: $this->_tree[$role] = array();
484: }
485:
486: if (!empty($inheritedRoles)) {
487: if (is_string($inheritedRoles)) {
488: $inheritedRoles = array_map('trim', explode(',', $inheritedRoles));
489: }
490:
491: foreach ($inheritedRoles as $dependency) {
492:
493: $roles = $this->roles($dependency);
494:
495: if (in_array($role, Hash::flatten($roles))) {
496: $path = '';
497:
498: foreach ($roles as $roleDependencies) {
499: $path .= implode('|', (array)$roleDependencies) . ' -> ';
500: }
501:
502: trigger_error(__d('cake_dev', 'cycle detected when inheriting %s from %s. Path: %s', $role, $dependency, $path . $role));
503: continue;
504: }
505:
506: if (!isset($this->_tree[$dependency])) {
507: $this->_tree[$dependency] = array();
508: }
509:
510: $this->_tree[$dependency][] = $role;
511: }
512: }
513: }
514: }
515:
516: 517: 518: 519: 520: 521:
522: public function addAlias(array $alias) {
523: $this->aliases = array_merge($this->aliases, $alias);
524: }
525:
526: 527: 528: 529: 530: 531:
532: public function build(array $aros) {
533: $this->_tree = array();
534: $this->addRole($aros);
535: }
536:
537: }
538: