Skip to content
Browse files

PHP configuration file base Acl implementation

  • Loading branch information...
1 parent 3f24dd7 commit 153152642c5565109cf1eb790781b6de1d347c7d @0x20h 0x20h committed
View
516 lib/Cake/Controller/Component/Acl/PhpAcl.php
@@ -0,0 +1,516 @@
+<?php
+
+/**
+ * PhpAcl implements an access control system using a plain PHP configuration file.
+ * An example file can be found in app/Config/acl.php
+ *
+ * @package Cake.Controller.Component.Acl
+ */
+class PhpAcl extends Object implements AclInterface {
+
+ const DENY = false;
+ const ALLOW = true;
+
+/**
+ * Options:
+ * - policy: determines behavior of the check method. Deny policy needs explicit allow rules, allow policy needs explicit deny rules
+ * - config: absolute path to config file that contains the acl rules (@see app/Config/acl.php)
+ *
+ * @var array
+ */
+ public $options = array();
+
+
+/**
+ * Aro Object
+ *
+ * @var PhpAro
+ */
+ public $Aro = null;
+
+/**
+ * Aco Object
+ *
+ * @var PhpAco
+ */
+ public $Aco = null;
+
+
+ public function __construct() {
+ $this->options = array(
+ 'policy' => self::DENY,
+ 'config' => APP . 'Config' . DS . 'acl.php',
+ );
+ }
+/**
+ * Phptialize method
+ *
+ * @param AclBase $component
+ * @return void
+ */
+ public function initialize($Component) {
+ if (!empty($Component->settings['ini_acl'])) {
+ $this->options = array_merge($this->options, $Component->settings['ini_acl']);
+ }
+
+ App::uses('PhpReader', 'Configure');
+ $Reader = new PhpReader(dirname($this->options['config']) . DS);
+ $config = $Reader->read(basename($this->options['config']));
+ $this->build($config);
+ $Component->Aco = $this->Aco;
+ $Component->Aro = $this->Aro;
+ }
+
+
+ public function build($config) {
+ if ($config instanceOf ConfigReaderInterface) {
+ $config = $config->read(basename($this->options['config']));
+ }
+
+ if (empty($config['roles'])) {
+ throw new AclException(__d('cake_dev','"roles" section not found in configuration.'));
+ }
+
+ if (empty($config['rules']['allow']) && empty($config['rules']['deny'])) {
+ throw new AclException(__d('cake_dev','Neither "allow" nor "deny" rules were provided in configuration.'));
+ }
+
+ $rules['allow'] = !empty($config['rules']['allow']) ? $config['rules']['allow'] : array();
+ $rules['deny'] = !empty($config['rules']['deny']) ? $config['rules']['deny'] : array();
+ $roles = !empty($config['roles']) ? $config['roles'] : array();
+ $map = !empty($config['map']) ? $config['map'] : array();
+ $alias = !empty($config['alias']) ? $config['alias'] : array();
+
+ $this->Aro = new PhpAro($roles, $map, $alias);
+ $this->Aco = new PhpAco($rules);
+ }
+
+/**
+ * No op method, allow cannot be done with PhpAcl
+ *
+ * @param string $aro ARO The requesting object identifier.
+ * @param string $aco ACO The controlled object identifier.
+ * @param string $action Action (defaults to *)
+ * @return boolean Success
+ */
+ public function allow($aro, $aco, $action = "*") {
+ return $this->Aco->access($this->Aro->resolve($aro), $aco, $action, 'allow');
+ }
+
+/**
+ * deny ARO access to ACO
+ *
+ * @param string $aro ARO The requesting object identifier.
+ * @param string $aco ACO The controlled object identifier.
+ * @param string $action Action (defaults to *)
+ * @return boolean Success
+ */
+ public function deny($aro, $aco, $action = "*") {
+ return $this->Aco->access($this->Aro->resolve($aro), $aco, $action, 'deny');
+ }
+
+/**
+ * No op method, inherit cannot be done with PhpAcl
+ *
+ * @param string $aro ARO The requesting object identifier.
+ * @param string $aco ACO The controlled object identifier.
+ * @param string $action Action (defaults to *)
+ * @return boolean Success
+ */
+ public function inherit($aro, $aco, $action = "*") {
+ }
+
+/**
+ * Main ACL check function. Checks to see if the ARO (access request object) has access to the
+ * ACO (access control object).
+ *
+ * @param string $aro ARO
+ * @param string $aco ACO
+ * @param string $aco_action Action
+ * @return boolean true if access is granted, false otherwise
+ */
+ public function check($aro, $aco, $aco_action = null) {
+ $allow = $this->options['policy'];
+ $prioritizedAros = $this->Aro->roles($aro);
+
+ if ($aco_action) {
+ $aco .= (strpos($aco, '.') ? '.' : '/') . $aco_action;
+ }
+
+ $path = $this->Aco->path($aco);
+
+ if (empty($path)) {
+ return $allow;
+ }
+
+ foreach ($path as $depth => $node) {
+ foreach ($prioritizedAros as $aros) {
+ if (!empty($node['allow'])) {
+ $allow = $allow || count(array_intersect($node['allow'], $aros)) > 0;
+ }
+
+ if (!empty($node['deny'])) {
+ $allow = $allow && count(array_intersect($node['deny'], $aros)) == 0;
+ }
+ }
+ }
+
+ return $allow;
+ }
+}
+
+/**
+ * Access Control Object
+ *
+ */
+class PhpAco {
+
+/**
+ * holds internal ACO representation
+ *
+ * @var array
+ */
+ protected $tree = array();
+
+/**
+ * map modifiers for ACO paths to their respective PCRE pattern
+ *
+ * @var array
+ */
+ public static $modifiers = array(
+ '*' => '.*',
+ );
+
+ public function __construct(array $rules = array()) {
+ foreach (array('allow', 'deny') as $type) {
+ if(empty($rules[$type])) {
+ $rules[$type] = array();
+ }
+ }
+
+ $this->build($rules['allow'], $rules['deny']);
+ }
+
+/**
+ * return path to the requested ACO with allow and deny rules for each level
+ *
+ * @return array
+ */
+ public function path($aco) {
+ $aco = $this->resolve($aco);
+ $path = array();
+ $level = 0;
+ $root = $this->tree;
+ $stack = array(array($root, 0));
+
+ while (!empty($stack)) {
+ list($root, $level) = array_pop($stack);
+
+ if (empty($path[$level])) {
+ $path[$level] = array();
+ }
+
+ foreach ($root as $node => $elements) {
+ if (strpos($node, '*') === false && $node != $aco[$level]) {
+ continue;
+ }
+
+ $pattern = '#^'.str_replace(array_keys(self::$modifiers), array_values(self::$modifiers), $node).'$#';
+
+ if ($node == $aco[$level] || preg_match($pattern, $aco[$level])) {
+ // merge allow/denies with $path of current level
+ foreach (array('allow', 'deny') as $policy) {
+ if (!empty($elements[$policy])) {
+ if (empty($path[$level][$policy])) {
+ $path[$level][$policy] = array();
+ }
+
+ $path[$level][$policy] = array_merge($path[$level][$policy], $elements[$policy]);
+ }
+ }
+
+ // traverse
+ if (!empty($elements['children']) && isset($aco[$level + 1])) {
+ array_push($stack, array($elements['children'], $level + 1));
+ }
+ }
+ }
+ }
+
+ return $path;
+ }
+
+
+/**
+ * allow/deny ARO access to ARO
+ *
+ * @return void
+ */
+ public function access($aro, $aco, $action, $type = 'deny') {
+ $aco = $this->resolve($aco);
+ $depth = count($aco);
+ $root = $this->tree;
+ $tree = &$root;
+
+ foreach ($aco as $i => $node) {
+ if (!isset($tree[$node])) {
+ $tree[$node] = array(
+ 'children' => array(),
+ );
+ }
+
+ if ($i < $depth - 1) {
+ $tree = &$tree[$node]['children'];
+ } else {
+ if (empty($tree[$node][$type])) {
+ $tree[$node][$type] = array();
+ }
+
+ $tree[$node][$type] = array_merge(is_array($aro) ? $aro : array($aro), $tree[$node][$type]);
+ }
+ }
+
+ $this->tree = &$root;
+ }
+
+/**
+ * resolve given ACO string to a path
+ *
+ * @param string $aco ACO string
+ * @return array path
+ */
+ public function resolve($aco) {
+ if (is_array($aco)) {
+ return array_map('strtolower', $aco);
+ }
+
+ return array_map('trim', explode('/', ltrim(strtolower($aco), '/')));
+ }
+
+/**
+ * build a tree representation from the given allow/deny informations for ACO paths
+ *
+ * @param array $allow ACO allow rules
+ * @param array $deny ACO deny rules
+ * @return void
+ */
+ public function build(array $allow, array $deny = array()) {
+ $stack = array();
+ $this->tree = array();
+ $tree = array();
+ $root = &$tree;
+
+ foreach ($allow as $dotPath => $commaSeparatedAros) {
+ $aros = array_map('trim', explode(',', $commaSeparatedAros));
+ $this->access($aros, $dotPath, null, 'allow');
+ }
+
+ foreach ($deny as $dotPath => $commaSeparatedAros) {
+ $aros = array_map('trim', explode(',', $commaSeparatedAros));
+ $this->access($aros, $dotPath, null, 'deny');
+ }
+ }
+
+
+}
+
+/**
+ * Access Request Object
+ *
+ */
+class PhpAro {
+
+/**
+ * role to resolve to when a provided ARO is not listed in
+ * the internal tree
+ *
+ * @var string
+ */
+ const DEFAULT_ROLE = 'Role/default';
+
+/**
+ * map external identifiers. E.g. if
+ *
+ * array('User' => array('username' => 'jeff', 'role' => 'editor'))
+ *
+ * is passed as an ARO to one of the methods of AclComponent, PhpAcl
+ * will check if it can be resolved to an User or a Role defined in the
+ * configuration file.
+ *
+ * @var array
+ * @see app/Config/acl.php
+ */
+ public $map = array(
+ 'User' => 'User/username',
+ 'Role' => 'User/role',
+ );
+
+/**
+ * aliases to map
+ *
+ * @var array
+ */
+ public $aliases = array();
+
+/**
+ * internal ARO representation
+ *
+ * @var array
+ */
+ protected $tree = array();
+
+ public function __construct(array $aro = array(), array $map = array(), array $aliases = array()) {
+ !empty($map) && $this->map = $map;
+ $this->aliases = $aliases;
+ $this->build($aro);
+ }
+
+
+/**
+ * From the perspective of the given ARO, walk down the tree and
+ * collect all inherited AROs levelwise such that AROs from different
+ * branches with equal distance to the requested ARO will be collected at the same
+ * index. The resulting array will contain a prioritized list of (list of) roles ordered from
+ * the most distant AROs to the requested one itself.
+ *
+ * @param mixed $aro An ARO identifier
+ * @return array prioritized AROs
+ */
+ public function roles($aro) {
+ $aros = array();
+ $aro = $this->resolve($aro);
+ $stack = array(array($aro, 0));
+
+ while (!empty($stack)) {
+ list($element, $depth) = array_pop($stack);
+ $aros[$depth][] = $element;
+
+ foreach ($this->tree as $node => $children) {
+ if (in_array($element, $children)) {
+ array_push($stack, array($node, $depth + 1));
+ }
+ }
+ }
+
+ // everybody inherits from the default role
+ if ($aro != self::DEFAULT_ROLE) {
+ $aros[]= array(self::DEFAULT_ROLE);
+ }
+ return array_reverse($aros);
+ }
+
+
+/**
+ * resolve an ARO identifier to an internal ARO string using
+ * the internal mapping information.
+ *
+ * @param mixed $aro ARO identifier (User.jeff, array('User' => ...), etc)
+ * @return string internal aro string (e.g. User/jeff, Role/default)
+ */
+ public function resolve($aro) {
+ foreach ($this->map as $aroGroup => $map) {
+ list ($model, $field) = explode('/', $map);
+ $mapped = '';
+
+ if (is_array($aro)) {
+ if (isset($aro['model']) && isset($aro['foreign_key']) && $aro['model'] == $aroGroup) {
+ $mapped = $aroGroup . '/' . $aro['foreign_key'];
+ } elseif (isset($aro[$model][$field])) {
+ $mapped = $aroGroup . '/' . $aro[$model][$field];
+ } elseif (isset($aro[$field])) {
+ $mapped = $aroGroup . '/' . $aro[$field];
+ }
+ } elseif (is_string($aro)) {
+ $aro = ltrim($aro, '/');
+
+ if (strpos($aro, '/') === false) {
+ $mapped = $aroGroup . '/' . $aro;
+ } else {
+ list($aroModel, $aroValue) = explode('/', $aro);
+
+ $aroModel = Inflector::camelize($aroModel);
+
+ if ($aroModel == $model || $aroModel == $aroGroup) {
+ $mapped = $aroGroup . '/' . $aroValue;
+ }
+ }
+ }
+
+ if (isset($this->tree[$mapped])) {
+ return $mapped;
+ }
+
+ // is there a matching alias defined (e.g. Role/1 => Role/admin)?
+ if (!empty($this->aliases[$mapped])) {
+ return $this->aliases[$mapped];
+ }
+
+ }
+
+ return self::DEFAULT_ROLE;
+ }
+
+
+/**
+ * adds a new ARO to the tree
+ *
+ * @param array $aro one or more ARO records
+ * @return void
+ */
+ public function addRole(array $aro) {
+ foreach ($aro as $role => $inheritedRoles) {
+ if (!isset($this->tree[$role])) {
+ $this->tree[$role] = array();
+ }
+
+ if (!empty($inheritedRoles)) {
+ if (is_string($inheritedRoles)) {
+ $inheritedRoles = array_map('trim', explode(',', $inheritedRoles));
+ }
+
+ foreach ($inheritedRoles as $dependency) {
+ // detect cycles
+ $roles = $this->roles($dependency);
+
+ if (in_array($role, Set::flatten($roles))) {
+ $path = '';
+
+ foreach ($roles as $roleDependencies) {
+ $path .= implode('|', (array)$roleDependencies) . ' -> ';
+ }
+
+ trigger_error(__d('cake_dev', 'cycle detected when inheriting %s from %s. Path: %s', $role, $dependency, $path.$role));
+ continue;
+ }
+
+ if (!isset($this->tree[$dependency])) {
+ $this->tree[$dependency] = array();
+ }
+
+ $this->tree[$dependency][] = $role;
+ }
+ }
+ }
+ }
+
+/**
+ * adds one or more aliases to the internal map. Overwrites existing entries.
+ *
+ * @param array $alias alias from => to (e.g. Role/13 -> Role/editor)
+ * @return void
+ */
+ public function addAlias(array $alias) {
+ $this->aliases = array_merge($this->aliases, $alias);
+ }
+
+/**
+ * build an ARO tree structure for internal processing
+ *
+ * @param array $aros array of AROs as key and their inherited AROs as values
+ * @return void
+ */
+ public function build(array $aros) {
+ $this->tree = array();
+ $this->addRole($aros);
+ }
+}
View
12 lib/Cake/Error/exceptions.php
@@ -226,7 +226,6 @@ public function __construct($message, $code = 404) {
parent::__construct($message, $code);
}
}
-
/**
* Private Action exception - used when a controller action
* starts with a `_`.
@@ -355,7 +354,7 @@ class MissingDatasourceException extends CakeException {
* @package Cake.Error
*/
class MissingTableException extends CakeException {
- protected $_messageTemplate = 'Table %s for model %s was not found in datasource %s.';
+ protected $_messageTemplate = 'Database table %s for model %s was not found.';
}
/**
@@ -386,6 +385,13 @@ class MissingPluginException extends CakeException {
}
/**
+ * Exception class for AclComponent and Interface implementations.
+ *
+ * @package Cake.Error
+ */
+class AclException extends CakeException { }
+
+/**
* Exception class for Cache. This exception will be thrown from Cache when it
* encounters an error.
*
@@ -427,7 +433,7 @@ class ConfigureException extends CakeException { }
/**
* Exception class for Socket. This exception will be thrown from CakeSocket, CakeEmail, HttpSocket
- * SmtpTransport, MailTransport and HttpResponse when it encounters an error.
+ * SmtpTransport and HttpResponse when it encounters an error.
*
* @package Cake.Error
*/
View
315 lib/Cake/Test/Case/Controller/Component/Acl/PhpAclTest.php
@@ -0,0 +1,315 @@
+<?php
+/**
+ * PhpAclTest file.
+ *
+ * PHP 5
+ *
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link http://cakephp.org CakePHP(tm) Project
+ * @package Cake.Test.Case.Controller.Component
+ * @since CakePHP(tm) v 2.0
+ * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+
+App::uses('AclComponent', 'Controller/Component');
+App::uses('PhpAcl', 'Controller/Component/Acl');
+class_exists('AclComponent');
+
+/**
+ * Test case for the PhpAcl implementation
+ *
+ * @package Cake.Test.Case.Controller.Component.Acl
+ */
+class PhpAclTest extends CakeTestCase {
+
+ public function setUp() {
+ Configure::write('Acl.classname', 'PhpAcl');
+ $Collection = new ComponentCollection();
+ $this->PhpAcl = new PhpAcl();
+ $this->Acl = new AclComponent($Collection, array(
+ 'ini_acl' => array(
+ 'config' => CAKE . 'Test' . DS . 'test_app' . DS . 'Config'. DS . 'acl.php',
+ ),
+ ));
+ $this->Acl->adapter($this->PhpAcl);
+ }
+
+
+ public function testRoleInheritance() {
+ $roles = $this->Acl->Aro->roles('User/peter');
+ $this->assertEquals(array('Role/default'), $roles[0]);
+ $this->assertEquals(array('Role/accounting'), $roles[1]);
+ $this->assertEquals(array('User/peter'), $roles[2]);
+
+ $roles = $this->Acl->Aro->roles('hardy');
+ $this->assertEquals(array('Role/default'), $roles[0]);
+ $this->assertEquals(array('Role/database_manager', 'Role/data_acquirer'), $roles[1]);
+ $this->assertEquals(array('Role/accounting', 'Role/data_analyst'), $roles[2]);
+ $this->assertEquals(array('Role/accounting_manager', 'Role/reports'), $roles[3]);
+ $this->assertEquals(array('User/hardy'), $roles[4]);
+ }
+
+ public function testAddRole() {
+ $this->assertEquals(array(array(PhpAro::DEFAULT_ROLE)), $this->Acl->Aro->roles('foobar'));
+ $this->Acl->Aro->addRole(array('User/foobar' => 'Role/accounting'));
+ $this->assertEquals(array(array('Role/default'), array('Role/accounting'), array('User/foobar')), $this->Acl->Aro->roles('foobar'));
+ }
+
+ public function testAroResolve() {
+ $map = $this->Acl->Aro->map;
+ $this->Acl->Aro->map = array(
+ 'User' => 'FooModel/nickname',
+ 'Role' => 'FooModel/role',
+ );
+
+ $this->assertEquals('Role/default', $this->Acl->Aro->resolve('Foo.bar'));
+ $this->assertEquals('User/hardy', $this->Acl->Aro->resolve('FooModel/hardy'));
+ $this->assertEquals('User/hardy', $this->Acl->Aro->resolve('hardy'));
+ $this->assertEquals('User/hardy', $this->Acl->Aro->resolve(array('FooModel' => array('nickname' => 'hardy'))));
+ $this->assertEquals('Role/admin', $this->Acl->Aro->resolve(array('FooModel' => array('role' => 'admin'))));
+ $this->assertEquals('Role/admin', $this->Acl->Aro->resolve('Role/admin'));
+
+ $this->assertEquals('Role/admin', $this->Acl->Aro->resolve('admin'));
+ $this->assertEquals('Role/admin', $this->Acl->Aro->resolve('FooModel/admin'));
+ $this->assertEquals('Role/accounting', $this->Acl->Aro->resolve('accounting'));
+
+ $this->assertEquals(PhpAro::DEFAULT_ROLE, $this->Acl->Aro->resolve('bla'));
+ $this->assertEquals(PhpAro::DEFAULT_ROLE, $this->Acl->Aro->resolve(array('FooModel' => array('role' => 'hardy'))));
+ }
+
+
+ public function testAroAliases() {
+ $this->Acl->Aro->map = array(
+ 'User' => 'User/username',
+ 'Role' => 'User/group_id',
+ );
+
+ $this->Acl->Aro->aliases = array(
+ 'Role/1' => 'Role/admin',
+ 'Role/24' => 'Role/accounting',
+ );
+
+ $user = array(
+ 'User' => array(
+ 'username' => 'unknown_user',
+ 'group_id' => '1',
+ ),
+ );
+ // group/1
+ $this->assertEquals('Role/admin', $this->Acl->Aro->resolve($user));
+ // group/24
+ $this->assertEquals('Role/accounting', $this->Acl->Aro->resolve('Role/24'));
+ $this->assertEquals('Role/accounting', $this->Acl->Aro->resolve('24'));
+
+ // check department
+ $user = array(
+ 'User' => array(
+ 'username' => 'foo',
+ 'group_id' => '25',
+ ),
+ );
+
+ $this->Acl->Aro->addRole(array('Role/IT' => null));
+ $this->Acl->Aro->addAlias(array('Role/25' => 'Role/IT'));
+ $this->Acl->allow('Role/IT', '/rules/debugging/*');
+
+ $this->assertEquals(array(array('Role/default'), array('Role/IT', )), $this->Acl->Aro->roles($user));
+ $this->assertTrue($this->Acl->check($user, '/rules/debugging/stats/pageload'));
+ $this->assertTrue($this->Acl->check($user, '/rules/debugging/sql/queries'));
+ // Role/default is allowed users dashboard, so is Role/IT
+ $this->assertTrue($this->Acl->check($user, '/controllers/users/dashboard'));
+
+ $this->assertFalse($this->Acl->check($user, '/controllers/invoices/send'));
+ // wee add an more specific entry for user foo to also inherit from Role/accounting
+ $this->Acl->Aro->addRole(array('User/foo' => 'Role/IT, Role/accounting'));
+ $this->assertTrue($this->Acl->check($user, '/controllers/invoices/send'));
+ }
+
+/**
+ * testPhpCheck method
+ *
+ * @return void
+ */
+ public function testCheck() {
+ $this->assertTrue($this->Acl->check('db_manager_2', '/controllers/users/Dashboard'));
+ $this->assertTrue($this->Acl->check('jan', '/controllers/users/Dashboard'));
+ $this->assertTrue($this->Acl->check('some_unknown_role', '/controllers/users/Dashboard'));
+ $this->assertTrue($this->Acl->check('Role/admin', 'foo/bar'));
+ $this->assertTrue($this->Acl->check('role/admin', '/foo/bar'));
+ $this->assertTrue($this->Acl->check('jan', 'foo/bar'));
+ $this->assertTrue($this->Acl->check('user/jan', 'foo/bar'));
+ $this->assertTrue($this->Acl->check('Role/admin', 'controllers/bar'));
+ $this->assertTrue($this->Acl->check(array('User' => array('username' =>'jan')), '/controlers/bar/bll'));
+ $this->assertTrue($this->Acl->check('Role/database_manager', 'controllers/db/create'));
+ $this->assertTrue($this->Acl->check('User/db_manager_2', 'controllers/db/create'));
+
+ // inheritance: hardy -> reports -> data_analyst -> database_manager
+ $this->assertTrue($this->Acl->check('User/hardy', 'controllers/db/create'));
+ $this->assertFalse($this->Acl->check('User/jeff', 'controllers/db/create'));
+
+ $this->assertTrue($this->Acl->check('Role/database_manager', 'controllers/db/select'));
+ $this->assertTrue($this->Acl->check('User/db_manager_2', 'controllers/db/select'));
+ $this->assertFalse($this->Acl->check('User/jeff', 'controllers/db/select'));
+
+ $this->assertTrue($this->Acl->check('Role/database_manager', 'controllers/db/drop'));
+ $this->assertTrue($this->Acl->check('User/db_manager_1', 'controllers/db/drop'));
+ $this->assertFalse($this->Acl->check('db_manager_2', 'controllers/db/drop'));
+
+ $this->assertTrue($this->Acl->check('db_manager_2', 'controllers/invoices/edit'));
+ $this->assertFalse($this->Acl->check('database_manager', 'controllers/invoices/edit'));
+ $this->assertFalse($this->Acl->check('db_manager_1', 'controllers/invoices/edit'));
+
+ // Role/manager is allowed /controllers/*/*_manager
+ $this->assertTrue($this->Acl->check('stan', 'controllers/invoices/manager_edit'));
+ $this->assertTrue($this->Acl->check('Role/manager', 'controllers/baz/manager_foo'));
+ $this->assertFalse($this->Acl->check('User/stan', 'custom/foo/manager_edit'));
+ $this->assertFalse($this->Acl->check('stan', 'bar/baz/manager_foo'));
+ $this->assertFalse($this->Acl->check('Role/accounting', 'bar/baz/manager_foo'));
+ $this->assertFalse($this->Acl->check('accounting', 'controllers/baz/manager_foo'));
+
+ $this->assertTrue($this->Acl->check('User/stan', 'controllers/articles/edit'));
+ $this->assertTrue($this->Acl->check('stan', 'controllers/articles/add'));
+ $this->assertTrue($this->Acl->check('stan', 'controllers/articles/publish'));
+ $this->assertFalse($this->Acl->check('User/stan', 'controllers/articles/delete'));
+ $this->assertFalse($this->Acl->check('accounting', 'controllers/articles/edit'));
+ $this->assertFalse($this->Acl->check('accounting', 'controllers/articles/add'));
+ $this->assertFalse($this->Acl->check('role/accounting', 'controllers/articles/publish'));
+ }
+
+
+ public function testCheckIsCaseInsensitive() {
+ $this->assertTrue($this->Acl->check('hardy', 'controllers/forms/new'));
+ $this->assertTrue($this->Acl->check('Role/data_acquirer', 'controllers/forms/new'));
+ $this->assertTrue($this->Acl->check('hardy', 'controllers/FORMS/NEW'));
+ $this->assertTrue($this->Acl->check('Role/data_acquirer', 'controllers/FORMS/NEW'));
+ }
+
+
+ public function testAllow() {
+ $this->assertFalse($this->Acl->check('jeff', 'foo/bar'));
+
+ $this->Acl->allow('jeff', 'foo/bar');
+
+ $this->assertTrue($this->Acl->check('jeff', 'foo/bar'));
+ $this->assertFalse($this->Acl->check('peter', 'foo/bar'));
+ $this->assertFalse($this->Acl->check('hardy', 'foo/bar'));
+
+ $this->Acl->allow('Role/accounting', 'foo/bar');
+
+ $this->assertTrue($this->Acl->check('peter', 'foo/bar'));
+ $this->assertTrue($this->Acl->check('hardy', 'foo/bar'));
+
+ $this->assertFalse($this->Acl->check('Role/reports', 'foo/bar'));
+ }
+
+
+ public function testDeny() {
+ $this->assertTrue($this->Acl->check('stan', 'controllers/baz/manager_foo'));
+
+ $this->Acl->deny('stan', 'controllers/baz/manager_foo');
+
+ $this->assertFalse($this->Acl->check('stan', 'controllers/baz/manager_foo'));
+ $this->assertTrue($this->Acl->check('Role/manager', 'controllers/baz/manager_foo'));
+ $this->assertTrue($this->Acl->check('stan', 'controllers/baz/manager_bar'));
+ $this->assertTrue($this->Acl->check('stan', 'controllers/baz/manager_foooooo'));
+ }
+
+
+ public function testDenyRuleIsStrongerThanAllowRule() {
+ $this->assertFalse($this->Acl->check('peter', 'baz/bam'));
+ $this->Acl->allow('peter', 'baz/bam');
+ $this->assertTrue($this->Acl->check('peter', 'baz/bam'));
+ $this->Acl->deny('peter', 'baz/bam');
+ $this->assertFalse($this->Acl->check('peter', 'baz/bam'));
+
+ $this->assertTrue($this->Acl->check('stan', 'controllers/reports/foo'));
+ // stan is denied as he's sales and sales is denied /controllers/*/delete
+ $this->assertFalse($this->Acl->check('stan', 'controllers/reports/delete'));
+ $this->Acl->allow('stan', 'controllers/reports/delete');
+ $this->assertFalse($this->Acl->check('Role/sales', 'controllers/reports/delete'));
+ $this->assertTrue($this->Acl->check('stan', 'controllers/reports/delete'));
+ $this->Acl->deny('stan', 'controllers/reports/delete');
+ $this->assertFalse($this->Acl->check('stan', 'controllers/reports/delete'));
+
+ // there is already an equally specific deny rule that will win
+ $this->Acl->allow('stan', 'controllers/reports/delete');
+ $this->assertFalse($this->Acl->check('stan', 'controllers/reports/delete'));
+ }
+
+
+ public function testInvalidConfigWithAroMissing() {
+ $this->setExpectedException(
+ 'AclException',
+ '"roles" section not found in configuration'
+ );
+ $config = array('aco' => array('allow' => array('foo' => '')));
+ $this->PhpAcl->build($config);
+ }
+
+
+ public function testInvalidConfigWithAcosMissing() {
+ $this->setExpectedException(
+ 'AclException',
+ 'Neither "allow" nor "deny" rules were provided in configuration.'
+ );
+
+ $config = array(
+ 'roles' => array('Role/foo' => null),
+ );
+
+ $this->PhpAcl->build($config);
+ }
+
+ public function testAcoResolve() {
+ $this->assertEquals(array('foo', 'bar'), $this->Acl->Aco->resolve('foo/bar'));
+ $this->assertEquals(array('foo', 'bar'), $this->Acl->Aco->resolve('foo/bar'));
+ $this->assertEquals(array('foo', 'bar', 'baz'), $this->Acl->Aco->resolve('foo/bar/baz'));
+ $this->assertEquals(array('foo', '*-bar', '?-baz'), $this->Acl->Aco->resolve('foo/*-bar/?-baz'));
+ }
+
+ public function testAroDeclarationContainsCycles() {
+ $config = array(
+ 'roles' => array(
+ 'Role/a' => null,
+ 'Role/b' => 'User/b',
+ 'User/a' => 'Role/a, Role/b',
+ 'User/b' => 'User/a',
+
+ ),
+ 'rules' => array(
+ 'allow' => array(
+ '*' => 'Role/a',
+ ),
+ ),
+ );
+
+ $this->expectError('PHPUnit_Framework_Error', 'cycle detected' /* ... */);
+ $this->PhpAcl->build($config);
+ }
+
+
+/**
+ * test that with policy allow, only denies count
+ */
+ public function testPolicy() {
+ // allow by default
+ $this->Acl->settings['ini_acl']['policy'] = PhpAcl::ALLOW;
+ $this->PhpAcl->initialize($this->Acl);
+
+ $this->assertTrue($this->Acl->check('Role/sales', 'foo'));
+ $this->assertTrue($this->Acl->check('Role/sales', 'controllers/bla/create'));
+ $this->assertTrue($this->Acl->check('Role/default', 'foo'));
+ // undefined user, undefined aco
+ $this->assertTrue($this->Acl->check('foobart', 'foo/bar'));
+
+ // deny rule: Role.sales -> controllers.*.delete
+ $this->assertFalse($this->Acl->check('Role/sales', 'controllers/bar/delete'));
+ $this->assertFalse($this->Acl->check('Role/sales', 'controllers/bar', 'delete'));
+ }
+}
View
72 lib/Cake/Test/test_app/Config/acl.php
@@ -0,0 +1,72 @@
+<?php
+/*
+ * Test App PHP Based Acl Config File
+ *
+ *
+ * PHP 5
+ *
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link http://cakephp.org CakePHP(tm) Project
+ * @package Cake.Test.test_app.Config
+ * @since CakePHP(tm) v 0.10.0.1076
+ * @license MIT License (http://www/opensource/org/licenses/mit-license.php)
+ */
+
+
+// -------------------------------------
+// AROs
+// -------------------------------------
+$config['roles'] = array(
+ 'Role/admin' => null,
+ 'Role/data_acquirer' => null,
+ 'Role/accounting' => null,
+ 'Role/database_manager' => null,
+ 'Role/sales' => null,
+ 'Role/data_analyst' => 'Role/data_acquirer, Role/database_manager',
+ 'Role/reports' => 'Role/data_analyst',
+ 'Role/manager' => array(
+ 'Role/accounting',
+ 'Role/sales',
+ ),
+ 'Role/accounting_manager' => 'Role/accounting',
+ // managers
+ 'User/hardy' => 'Role/accounting_manager, Role/reports',
+ 'User/stan' => 'Role/manager',
+ // accountants
+ 'User/peter' => 'Role/accounting',
+ 'User/jeff' => 'Role/accounting',
+ // admins
+ 'User/jan' => 'Role/admin',
+ // database
+ 'User/db_manager_1' => 'Role/database_manager',
+ 'User/db_manager_2' => 'Role/database_manager',
+);
+
+//-------------------------------------
+// ACOs
+//-------------------------------------
+$config['rules']['allow'] = array(
+ '/*' => 'Role/admin',
+ '/controllers/*/manager_*' => 'Role/manager',
+ '/controllers/reports/*' => 'Role/sales',
+ '/controllers/invoices/*' => 'Role/accounting',
+ '/controllers/invoices/edit'=> 'User/db_manager_2',
+ '/controllers/db/*' => 'Role/database_manager',
+ '/controllers/*/add' => 'User/stan',
+ '/controllers/*/edit' => 'User/stan',
+ '/controllers/*/publish' => 'User/stan',
+ '/controllers/users/dashboard' => 'Role/default',
+ // test for case insensitivity
+ 'controllers/Forms/NEW' => 'Role/data_acquirer',
+);
+$config['rules']['deny'] = array(
+ // accountants and sales should not delete anything
+ '/controllers/*/delete' => 'Role/sales, Role/accounting',
+ '/controllers/db/drop' => 'User/db_manager_2',
+);

0 comments on commit 1531526

Please sign in to comment.
Something went wrong with that request. Please try again.