1: <?php
2: /**
3: * Access Control List factory class.
4: *
5: * Permissions system.
6: *
7: * PHP versions 4 and 5
8: *
9: * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
10: * Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
11: *
12: * Licensed under The MIT License
13: * Redistributions of files must retain the above copyright notice.
14: *
15: * @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
16: * @link http://cakephp.org CakePHP(tm) Project
17: * @package cake
18: * @subpackage cake.cake.libs.controller.components
19: * @since CakePHP(tm) v 0.10.0.1076
20: * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
21: */
22:
23: /**
24: * Access Control List factory class.
25: *
26: * Uses a strategy pattern to allow custom ACL implementations to be used with the same component interface.
27: * You can define by changing `Configure::write('Acl.classname', 'DbAcl');` in your core.php. Concrete ACL
28: * implementations should extend `AclBase` and implement the methods it defines.
29: *
30: * @package cake
31: * @subpackage cake.cake.libs.controller.components
32: * @link http://book.cakephp.org/1.3/en/The-Manual/Core-Components/Access-Control-Lists.html
33: */
34: class AclComponent extends Object {
35:
36: /**
37: * Instance of an ACL class
38: *
39: * @var object
40: * @access protected
41: */
42: var $_Instance = null;
43:
44: /**
45: * Constructor. Will return an instance of the correct ACL class as defined in `Configure::read('Acl.classname')`
46: *
47: */
48: function __construct() {
49: $name = Inflector::camelize(strtolower(Configure::read('Acl.classname')));
50: if (!class_exists($name)) {
51: if (App::import('Component', $name)) {
52: list($plugin, $name) = pluginSplit($name);
53: $name .= 'Component';
54: } else {
55: trigger_error(sprintf(__('Could not find %s.', true), $name), E_USER_WARNING);
56: }
57: }
58: $this->_Instance =& new $name();
59: $this->_Instance->initialize($this);
60: }
61:
62: /**
63: * Startup is not used
64: *
65: * @param object $controller Controller using this component
66: * @return boolean Proceed with component usage (true), or fail (false)
67: * @access public
68: */
69: function startup(&$controller) {
70: return true;
71: }
72:
73: /**
74: * Empty class defintion, to be overridden in subclasses.
75: *
76: * @access protected
77: */
78: function _initACL() {
79: }
80:
81: /**
82: * Pass-thru function for ACL check instance. Check methods
83: * are used to check whether or not an ARO can access an ACO
84: *
85: * @param string $aro ARO The requesting object identifier.
86: * @param string $aco ACO The controlled object identifier.
87: * @param string $action Action (defaults to *)
88: * @return boolean Success
89: * @access public
90: */
91: function check($aro, $aco, $action = "*") {
92: return $this->_Instance->check($aro, $aco, $action);
93: }
94:
95: /**
96: * Pass-thru function for ACL allow instance. Allow methods
97: * are used to grant an ARO access to an ACO.
98: *
99: * @param string $aro ARO The requesting object identifier.
100: * @param string $aco ACO The controlled object identifier.
101: * @param string $action Action (defaults to *)
102: * @return boolean Success
103: * @access public
104: */
105: function allow($aro, $aco, $action = "*") {
106: return $this->_Instance->allow($aro, $aco, $action);
107: }
108:
109: /**
110: * Pass-thru function for ACL deny instance. Deny methods
111: * are used to remove permission from an ARO to access an ACO.
112: *
113: * @param string $aro ARO The requesting object identifier.
114: * @param string $aco ACO The controlled object identifier.
115: * @param string $action Action (defaults to *)
116: * @return boolean Success
117: * @access public
118: */
119: function deny($aro, $aco, $action = "*") {
120: return $this->_Instance->deny($aro, $aco, $action);
121: }
122:
123: /**
124: * Pass-thru function for ACL inherit instance. Inherit methods
125: * modify the permission for an ARO to be that of its parent object.
126: *
127: * @param string $aro ARO The requesting object identifier.
128: * @param string $aco ACO The controlled object identifier.
129: * @param string $action Action (defaults to *)
130: * @return boolean Success
131: * @access public
132: */
133: function inherit($aro, $aco, $action = "*") {
134: return $this->_Instance->inherit($aro, $aco, $action);
135: }
136:
137: /**
138: * Pass-thru function for ACL grant instance. An alias for AclComponent::allow()
139: *
140: * @param string $aro ARO The requesting object identifier.
141: * @param string $aco ACO The controlled object identifier.
142: * @param string $action Action (defaults to *)
143: * @return boolean Success
144: * @access public
145: */
146: function grant($aro, $aco, $action = "*") {
147: return $this->_Instance->grant($aro, $aco, $action);
148: }
149:
150: /**
151: * Pass-thru function for ACL grant instance. An alias for AclComponent::deny()
152: *
153: * @param string $aro ARO The requesting object identifier.
154: * @param string $aco ACO The controlled object identifier.
155: * @param string $action Action (defaults to *)
156: * @return boolean Success
157: * @access public
158: */
159: function revoke($aro, $aco, $action = "*") {
160: return $this->_Instance->revoke($aro, $aco, $action);
161: }
162: }
163:
164: /**
165: * Access Control List abstract class. Not to be instantiated.
166: * Subclasses of this class are used by AclComponent to perform ACL checks in Cake.
167: *
168: * @package cake
169: * @subpackage cake.cake.libs.controller.components
170: * @abstract
171: */
172: class AclBase extends Object {
173:
174: /**
175: * This class should never be instantiated, just subclassed.
176: *
177: */
178: function __construct() {
179: if (strcasecmp(get_class($this), "AclBase") == 0 || !is_subclass_of($this, "AclBase")) {
180: trigger_error(__("[acl_base] The AclBase class constructor has been called, or the class was instantiated. This class must remain abstract. Please refer to the Cake docs for ACL configuration.", true), E_USER_ERROR);
181: return NULL;
182: }
183: }
184:
185: /**
186: * Empty method to be overridden in subclasses
187: *
188: * @param string $aro ARO The requesting object identifier.
189: * @param string $aco ACO The controlled object identifier.
190: * @param string $action Action (defaults to *)
191: * @access public
192: */
193: function check($aro, $aco, $action = "*") {
194: }
195:
196: /**
197: * Empty method to be overridden in subclasses
198: *
199: * @param object $component Component
200: * @access public
201: */
202: function initialize(&$component) {
203: }
204: }
205:
206: /**
207: * DbAcl implements an ACL control system in the database. ARO's and ACO's are
208: * structured into trees and a linking table is used to define permissions. You
209: * can install the schema for DbAcl with the Schema Shell.
210: *
211: * `$aco` and `$aro` parameters can be slash delimited paths to tree nodes.
212: *
213: * eg. `controllers/Users/edit`
214: *
215: * Would point to a tree structure like
216: *
217: * {{{
218: * controllers
219: * Users
220: * edit
221: * }}}
222: *
223: * @package cake
224: * @subpackage cake.cake.libs.model
225: */
226: class DbAcl extends AclBase {
227:
228: /**
229: * Constructor
230: *
231: */
232: function __construct() {
233: parent::__construct();
234: if (!class_exists('AclNode')) {
235: require LIBS . 'model' . DS . 'db_acl.php';
236: }
237: $this->Aro =& ClassRegistry::init(array('class' => 'Aro', 'alias' => 'Aro'));
238: $this->Aco =& ClassRegistry::init(array('class' => 'Aco', 'alias' => 'Aco'));
239: }
240:
241: /**
242: * Initializes the containing component and sets the Aro/Aco objects to it.
243: *
244: * @param AclComponent $component
245: * @return void
246: * @access public
247: */
248: function initialize(&$component) {
249: $component->Aro =& $this->Aro;
250: $component->Aco =& $this->Aco;
251: }
252:
253: /**
254: * Checks if the given $aro has access to action $action in $aco
255: *
256: * @param string $aro ARO The requesting object identifier.
257: * @param string $aco ACO The controlled object identifier.
258: * @param string $action Action (defaults to *)
259: * @return boolean Success (true if ARO has access to action in ACO, false otherwise)
260: * @access public
261: * @link http://book.cakephp.org/1.3/en/The-Manual/Core-Components/Access-Control-Lists.html#checking-permissions-the-acl-component
262: */
263: function check($aro, $aco, $action = "*") {
264: if ($aro == null || $aco == null) {
265: return false;
266: }
267:
268: $permKeys = $this->_getAcoKeys($this->Aro->Permission->schema());
269: $aroPath = $this->Aro->node($aro);
270: $acoPath = $this->Aco->node($aco);
271:
272: if (empty($aroPath) || empty($acoPath)) {
273: trigger_error(__("DbAcl::check() - Failed ARO/ACO node lookup in permissions check. Node references:\nAro: ", true) . print_r($aro, true) . "\nAco: " . print_r($aco, true), E_USER_WARNING);
274: return false;
275: }
276:
277: if ($acoPath == null || $acoPath == array()) {
278: trigger_error(__("DbAcl::check() - Failed ACO node lookup in permissions check. Node references:\nAro: ", true) . print_r($aro, true) . "\nAco: " . print_r($aco, true), E_USER_WARNING);
279: return false;
280: }
281:
282: $aroNode = $aroPath[0];
283: $acoNode = $acoPath[0];
284:
285: if ($action != '*' && !in_array('_' . $action, $permKeys)) {
286: trigger_error(sprintf(__("ACO permissions key %s does not exist in DbAcl::check()", true), $action), E_USER_NOTICE);
287: return false;
288: }
289:
290: $inherited = array();
291: $acoIDs = Set::extract($acoPath, '{n}.' . $this->Aco->alias . '.id');
292:
293: $count = count($aroPath);
294: for ($i = 0 ; $i < $count; $i++) {
295: $permAlias = $this->Aro->Permission->alias;
296:
297: $perms = $this->Aro->Permission->find('all', array(
298: 'conditions' => array(
299: "{$permAlias}.aro_id" => $aroPath[$i][$this->Aro->alias]['id'],
300: "{$permAlias}.aco_id" => $acoIDs
301: ),
302: 'order' => array($this->Aco->alias . '.lft' => 'desc'),
303: 'recursive' => 0
304: ));
305:
306: if (empty($perms)) {
307: continue;
308: } else {
309: $perms = Set::extract($perms, '{n}.' . $this->Aro->Permission->alias);
310: foreach ($perms as $perm) {
311: if ($action == '*') {
312:
313: foreach ($permKeys as $key) {
314: if (!empty($perm)) {
315: if ($perm[$key] == -1) {
316: return false;
317: } elseif ($perm[$key] == 1) {
318: $inherited[$key] = 1;
319: }
320: }
321: }
322:
323: if (count($inherited) === count($permKeys)) {
324: return true;
325: }
326: } else {
327: switch ($perm['_' . $action]) {
328: case -1:
329: return false;
330: case 0:
331: continue;
332: break;
333: case 1:
334: return true;
335: break;
336: }
337: }
338: }
339: }
340: }
341: return false;
342: }
343:
344: /**
345: * Allow $aro to have access to action $actions in $aco
346: *
347: * @param string $aro ARO The requesting object identifier.
348: * @param string $aco ACO The controlled object identifier.
349: * @param string $actions Action (defaults to *)
350: * @param integer $value Value to indicate access type (1 to give access, -1 to deny, 0 to inherit)
351: * @return boolean Success
352: * @access public
353: * @link http://book.cakephp.org/1.3/en/The-Manual/Core-Components/Access-Control-Lists.html#assigning-permissions
354: */
355: function allow($aro, $aco, $actions = "*", $value = 1) {
356: $perms = $this->getAclLink($aro, $aco);
357: $permKeys = $this->_getAcoKeys($this->Aro->Permission->schema());
358: $save = array();
359:
360: if ($perms == false) {
361: trigger_error(__('DbAcl::allow() - Invalid node', true), E_USER_WARNING);
362: return false;
363: }
364: if (isset($perms[0])) {
365: $save = $perms[0][$this->Aro->Permission->alias];
366: }
367:
368: if ($actions == "*") {
369: $permKeys = $this->_getAcoKeys($this->Aro->Permission->schema());
370: $save = array_combine($permKeys, array_pad(array(), count($permKeys), $value));
371: } else {
372: if (!is_array($actions)) {
373: $actions = array('_' . $actions);
374: }
375: if (is_array($actions)) {
376: foreach ($actions as $action) {
377: if ($action{0} != '_') {
378: $action = '_' . $action;
379: }
380: if (in_array($action, $permKeys)) {
381: $save[$action] = $value;
382: }
383: }
384: }
385: }
386: list($save['aro_id'], $save['aco_id']) = array($perms['aro'], $perms['aco']);
387:
388: if ($perms['link'] != null && !empty($perms['link'])) {
389: $save['id'] = $perms['link'][0][$this->Aro->Permission->alias]['id'];
390: } else {
391: unset($save['id']);
392: $this->Aro->Permission->id = null;
393: }
394: return ($this->Aro->Permission->save($save) !== false);
395: }
396:
397: /**
398: * Deny access for $aro to action $action in $aco
399: *
400: * @param string $aro ARO The requesting object identifier.
401: * @param string $aco ACO The controlled object identifier.
402: * @param string $actions Action (defaults to *)
403: * @return boolean Success
404: * @access public
405: * @link http://book.cakephp.org/1.3/en/The-Manual/Core-Components/Access-Control-Lists.html#assigning-permissions
406: */
407: function deny($aro, $aco, $action = "*") {
408: return $this->allow($aro, $aco, $action, -1);
409: }
410:
411: /**
412: * Let access for $aro to action $action in $aco be inherited
413: *
414: * @param string $aro ARO The requesting object identifier.
415: * @param string $aco ACO The controlled object identifier.
416: * @param string $actions Action (defaults to *)
417: * @return boolean Success
418: * @access public
419: */
420: function inherit($aro, $aco, $action = "*") {
421: return $this->allow($aro, $aco, $action, 0);
422: }
423:
424: /**
425: * Allow $aro to have access to action $actions in $aco
426: *
427: * @param string $aro ARO The requesting object identifier.
428: * @param string $aco ACO The controlled object identifier.
429: * @param string $actions Action (defaults to *)
430: * @return boolean Success
431: * @see allow()
432: * @access public
433: */
434: function grant($aro, $aco, $action = "*") {
435: return $this->allow($aro, $aco, $action);
436: }
437:
438: /**
439: * Deny access for $aro to action $action in $aco
440: *
441: * @param string $aro ARO The requesting object identifier.
442: * @param string $aco ACO The controlled object identifier.
443: * @param string $actions Action (defaults to *)
444: * @return boolean Success
445: * @see deny()
446: * @access public
447: */
448: function revoke($aro, $aco, $action = "*") {
449: return $this->deny($aro, $aco, $action);
450: }
451:
452: /**
453: * Get an array of access-control links between the given Aro and Aco
454: *
455: * @param string $aro ARO The requesting object identifier.
456: * @param string $aco ACO The controlled object identifier.
457: * @return array Indexed array with: 'aro', 'aco' and 'link'
458: * @access public
459: */
460: function getAclLink($aro, $aco) {
461: $obj = array();
462: $obj['Aro'] = $this->Aro->node($aro);
463: $obj['Aco'] = $this->Aco->node($aco);
464:
465: if (empty($obj['Aro']) || empty($obj['Aco'])) {
466: return false;
467: }
468:
469: return array(
470: 'aro' => Set::extract($obj, 'Aro.0.'.$this->Aro->alias.'.id'),
471: 'aco' => Set::extract($obj, 'Aco.0.'.$this->Aco->alias.'.id'),
472: 'link' => $this->Aro->Permission->find('all', array('conditions' => array(
473: $this->Aro->Permission->alias . '.aro_id' => Set::extract($obj, 'Aro.0.'.$this->Aro->alias.'.id'),
474: $this->Aro->Permission->alias . '.aco_id' => Set::extract($obj, 'Aco.0.'.$this->Aco->alias.'.id')
475: )))
476: );
477: }
478:
479: /**
480: * Get the keys used in an ACO
481: *
482: * @param array $keys Permission model info
483: * @return array ACO keys
484: * @access protected
485: */
486: function _getAcoKeys($keys) {
487: $newKeys = array();
488: $keys = array_keys($keys);
489: foreach ($keys as $key) {
490: if (!in_array($key, array('id', 'aro_id', 'aco_id'))) {
491: $newKeys[] = $key;
492: }
493: }
494: return $newKeys;
495: }
496: }
497:
498: /**
499: * IniAcl implements an access control system using an INI file. An example
500: * of the ini file used can be found in /config/acl.ini.php.
501: *
502: * @package cake
503: * @subpackage cake.cake.libs.model.iniacl
504: */
505: class IniAcl extends AclBase {
506:
507: /**
508: * Array with configuration, parsed from ini file
509: *
510: * @var array
511: * @access public
512: */
513: var $config = null;
514:
515: /**
516: * The constructor must be overridden, as AclBase is abstract.
517: *
518: */
519: function __construct() {
520: }
521:
522: /**
523: * Main ACL check function. Checks to see if the ARO (access request object) has access to the
524: * ACO (access control object).Looks at the acl.ini.php file for permissions
525: * (see instructions in /config/acl.ini.php).
526: *
527: * @param string $aro ARO
528: * @param string $aco ACO
529: * @param string $aco_action Action
530: * @return boolean Success
531: * @access public
532: */
533: function check($aro, $aco, $aco_action = null) {
534: if ($this->config == null) {
535: $this->config = $this->readConfigFile(CONFIGS . 'acl.ini.php');
536: }
537: $aclConfig = $this->config;
538:
539: if (isset($aclConfig[$aro]['deny'])) {
540: $userDenies = $this->arrayTrim(explode(",", $aclConfig[$aro]['deny']));
541:
542: if (array_search($aco, $userDenies)) {
543: return false;
544: }
545: }
546:
547: if (isset($aclConfig[$aro]['allow'])) {
548: $userAllows = $this->arrayTrim(explode(",", $aclConfig[$aro]['allow']));
549:
550: if (array_search($aco, $userAllows)) {
551: return true;
552: }
553: }
554:
555: if (isset($aclConfig[$aro]['groups'])) {
556: $userGroups = $this->arrayTrim(explode(",", $aclConfig[$aro]['groups']));
557:
558: foreach ($userGroups as $group) {
559: if (array_key_exists($group, $aclConfig)) {
560: if (isset($aclConfig[$group]['deny'])) {
561: $groupDenies=$this->arrayTrim(explode(",", $aclConfig[$group]['deny']));
562:
563: if (array_search($aco, $groupDenies)) {
564: return false;
565: }
566: }
567:
568: if (isset($aclConfig[$group]['allow'])) {
569: $groupAllows = $this->arrayTrim(explode(",", $aclConfig[$group]['allow']));
570:
571: if (array_search($aco, $groupAllows)) {
572: return true;
573: }
574: }
575: }
576: }
577: }
578: return false;
579: }
580:
581: /**
582: * Parses an INI file and returns an array that reflects the INI file's section structure. Double-quote friendly.
583: *
584: * @param string $fileName File
585: * @return array INI section structure
586: * @access public
587: */
588: function readConfigFile($fileName) {
589: $fileLineArray = file($fileName);
590:
591: foreach ($fileLineArray as $fileLine) {
592: $dataLine = trim($fileLine);
593: $firstChar = substr($dataLine, 0, 1);
594:
595: if ($firstChar != ';' && $dataLine != '') {
596: if ($firstChar == '[' && substr($dataLine, -1, 1) == ']') {
597: $sectionName = preg_replace('/[\[\]]/', '', $dataLine);
598: } else {
599: $delimiter = strpos($dataLine, '=');
600:
601: if ($delimiter > 0) {
602: $key = strtolower(trim(substr($dataLine, 0, $delimiter)));
603: $value = trim(substr($dataLine, $delimiter + 1));
604:
605: if (substr($value, 0, 1) == '"' && substr($value, -1) == '"') {
606: $value = substr($value, 1, -1);
607: }
608:
609: $iniSetting[$sectionName][$key]=stripcslashes($value);
610: } else {
611: if (!isset($sectionName)) {
612: $sectionName = '';
613: }
614:
615: $iniSetting[$sectionName][strtolower(trim($dataLine))]='';
616: }
617: }
618: }
619: }
620:
621: return $iniSetting;
622: }
623:
624: /**
625: * Removes trailing spaces on all array elements (to prepare for searching)
626: *
627: * @param array $array Array to trim
628: * @return array Trimmed array
629: * @access public
630: */
631: function arrayTrim($array) {
632: foreach ($array as $key => $value) {
633: $array[$key] = trim($value);
634: }
635: array_unshift($array, "");
636: return $array;
637: }
638: }
639: