source: Dev/branches/cakephp/cake/libs/controller/components/acl.php @ 126

Last change on this file since 126 was 126, checked in by fpvanagthoven, 14 years ago

Cakephp branch.

File size: 17.5 KB
Line 
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-2011, 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-2011, 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/view/1242/Access-Control-Lists
33 */
34class 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 */
172class 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 */
226class 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/view/1249/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/view/1248/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/view/1248/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 */
505class 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}
Note: See TracBrowser for help on using the repository browser.