├── tests ├── fixture │ ├── model │ │ └── blog │ │ │ ├── Group.php │ │ │ ├── Post.php │ │ │ └── User.php │ └── source │ │ ├── blog │ │ ├── UserFixture.php │ │ ├── GroupFixture.php │ │ └── PostFixtures.php │ │ └── db_acl │ │ ├── AcoFixture.php │ │ ├── AroFixture.php │ │ └── PermissionFixture.php ├── cases │ └── security │ │ ├── access │ │ └── adapter │ │ │ ├── SimpleTest.php │ │ │ ├── DbAclTest.php │ │ │ └── RulesTest.php │ │ └── AccessTest.php ├── ci_bootstrap.php └── integration │ └── security │ └── access │ └── model │ └── db_acl │ ├── AclNodeTest.php │ └── PermissionTest.php ├── security ├── access │ └── model │ │ └── db_acl │ │ ├── Aco.php │ │ ├── Aro.php │ │ ├── AclNode.php │ │ └── Permission.php └── Access.php ├── extensions └── adapter │ └── security │ └── access │ ├── Simple.php │ ├── DbAcl.php │ └── Rules.php ├── .travis.yml ├── composer.json ├── LICENSE.txt └── README.md /tests/fixture/model/blog/Group.php: -------------------------------------------------------------------------------- 1 | 'id']; 14 | 15 | public $hasMany = ['User']; 16 | } -------------------------------------------------------------------------------- /tests/fixture/model/blog/Post.php: -------------------------------------------------------------------------------- 1 | 'id']; 14 | 15 | public $belongsTo = ['User']; 16 | 17 | } -------------------------------------------------------------------------------- /tests/fixture/model/blog/User.php: -------------------------------------------------------------------------------- 1 | 'id']; 14 | 15 | public $hasMany = ['Post']; 16 | public $belongsTo = ['Group']; 17 | } -------------------------------------------------------------------------------- /tests/fixture/source/blog/UserFixture.php: -------------------------------------------------------------------------------- 1 | ['type' => 'id'], 17 | 'name' => ['type' => 'string'] 18 | ]; 19 | } 20 | 21 | ?> -------------------------------------------------------------------------------- /tests/fixture/source/blog/GroupFixture.php: -------------------------------------------------------------------------------- 1 | ['type' => 'id'], 17 | 'name' => ['type' => 'string'] 18 | ]; 19 | } 20 | 21 | ?> -------------------------------------------------------------------------------- /tests/fixture/source/blog/PostFixtures.php: -------------------------------------------------------------------------------- 1 | ['type' => 'id'], 17 | 'title' => ['type' => 'string'], 18 | 'body' => ['type' => 'text'] 19 | ]; 20 | } 21 | 22 | ?> -------------------------------------------------------------------------------- /security/access/model/db_acl/Aco.php: -------------------------------------------------------------------------------- 1 | [ 15 | 'key' => 'parent_id', 16 | 'to' => 'li3_access\security\access\model\db_acl\Aco' 17 | ]]; 18 | 19 | public $hasMany = ['Permission' => ['key' => 'aco_id']]; 20 | 21 | public $hasAndBelongsToMany = ['Aro' => ['via' => 'Permission']]; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /security/access/model/db_acl/Aro.php: -------------------------------------------------------------------------------- 1 | [ 15 | 'key' => 'parent_id', 16 | 'to' => 'li3_access\security\access\model\db_acl\Aro' 17 | ]]; 18 | 19 | public $hasMany = ['Permission' => ['key' => 'aro_id']]; 20 | 21 | public $hasAndBelongsToMany = ['Aco' => ['via' => 'Permission']]; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /extensions/adapter/security/access/Simple.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 5.4 4 | 5 | before_script: 6 | - mkdir config 7 | - cp tests/ci_bootstrap.php config/bootstrap.php 8 | - mkdir ../libraries 9 | - git clone --branch=master --depth=100 --quiet git://github.com/UnionOfRAD/lithium.git ../libraries/lithium 10 | - git clone --branch=master --depth=100 --quiet git://github.com/UnionOfRAD/li3_fixtures.git ../libraries/li3_fixtures 11 | - git clone --branch=master --depth=100 --quiet git://github.com/jails/li3_behaviors.git ../libraries/li3_behaviors 12 | - git clone --branch=master --depth=100 --quiet git://github.com/jails/li3_tree.git ../libraries/li3_tree 13 | - mysql -e 'create database li3access_test;' 14 | 15 | script: ../libraries/lithium/console/li3 test --filters=Profiler tests -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jails/li3_access", 3 | "type": "lithium-library", 4 | "description": "Access control (DbAcl, Rules, Simple) for the Lithium PHP framework", 5 | "keywords": ["php", "acl", "access", "control", "lithium", "li3"], 6 | "homepage": "https://github.com/jails/li3_behaviors", 7 | "license": "BSD-3-Clause", 8 | "minimum-stability": "dev", 9 | "authors": [ 10 | { 11 | "name": "Simon JAILLET", 12 | "homepage": "https://github.com/jails" 13 | }, 14 | { 15 | "name": "Community", 16 | "homepage": "http://github.com/jails/li3_access/graphs/contributors" 17 | } 18 | ], 19 | "require": { 20 | "php": ">=5.4", 21 | "composer/installers": "dev-master", 22 | "jails/li3_behaviors": "dev-master", 23 | "jails/li3_tree": "dev-master" 24 | } 25 | } -------------------------------------------------------------------------------- /tests/cases/security/access/adapter/SimpleTest.php: -------------------------------------------------------------------------------- 1 | _adapter = new Simple(); 19 | } 20 | 21 | public function tearDown() {} 22 | 23 | public function testCheck() { 24 | $result = $this->_adapter->check(['username' => 'Max']); 25 | $this->assertTrue($result); 26 | 27 | $result = $this->_adapter->check(false); 28 | $this->assertFalse($result); 29 | } 30 | 31 | public function testFilter() { 32 | $set = ['key1' => 'val1', 'key2' => 'val2', 'key3' => 'val3']; 33 | } 34 | } 35 | 36 | ?> -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, Union of RAD http://union-of-rad.org 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | * Neither the name of Lithium, Union of Rad, nor the names of its contributors 13 | may be used to endorse or promote products derived from this software 14 | without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19 | IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 20 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 21 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 23 | OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 24 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 25 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /tests/ci_bootstrap.php: -------------------------------------------------------------------------------- 1 | true]); 31 | 32 | /** 33 | * Load test dependencies 34 | */ 35 | Libraries::add('li3_tree'); 36 | Libraries::add('li3_behaviors'); 37 | Libraries::add('li3_fixtures'); 38 | 39 | /** 40 | * Setup test database 41 | */ 42 | Connections::add('test', [ 43 | 'type' => 'database', 44 | 'adapter' => 'MySql', 45 | 'host' => 'localhost', 46 | 'login' => 'root', 47 | 'password' => '', 48 | 'database' => 'li3access_test', 49 | 'encoding' => 'UTF-8' 50 | ]); 51 | 52 | ?> -------------------------------------------------------------------------------- /tests/cases/security/AccessTest.php: -------------------------------------------------------------------------------- 1 | _adapter = new MockCallable(); 21 | Access::config([ 22 | 'test_access' => [ 23 | 'object' => $this->_adapter 24 | ], 25 | 'test_access_with_filters' => [ 26 | 'object' => $this->_adapter, 27 | 'filters' => [ 28 | function($self, $params, $chain) { 29 | return $chain->next($self, $params, $chain); 30 | }, 31 | function($self, $params, $chain) { 32 | return 'Filter executed.'; 33 | } 34 | ] 35 | ] 36 | ]); 37 | } 38 | 39 | public function tearDown() {Access::reset();} 40 | 41 | public function testCheck() { 42 | $result = Access::check('test_access', ['username' => 'Gwoo'], false); 43 | extract($result); 44 | $this->assertEqual('check', $method); 45 | $this->assertEqual([['username' => 'Gwoo'], false, []], $params); 46 | } 47 | 48 | public function testFilters() { 49 | $result = Access::check('test_access_with_filters', false, false, []); 50 | $this->assertEqual('Filter executed.', $result); 51 | } 52 | 53 | public function testNoConfigurations() { 54 | Access::reset(); 55 | $this->assertIdentical([], Access::config()); 56 | $this->expectException("Configuration `test_no_config` has not been defined."); 57 | Access::check('test_no_config', false, false); 58 | } 59 | } 60 | 61 | ?> 62 | -------------------------------------------------------------------------------- /tests/fixture/source/db_acl/AcoFixture.php: -------------------------------------------------------------------------------- 1 | ['type' => 'id'], 17 | 'parent_id' => ['type' => 'integer', 'length' => 10, 'null' => true], 18 | 'class' => ['type' => 'string', 'null' => true], 19 | 'fk_id' => ['type' => 'integer', 'length' => 10, 'null' => true], 20 | 'alias' => ['type' => 'string', 'default' => ''], 21 | 'lft' => ['type' => 'integer', 'length' => 10, 'null' => true], 22 | 'rght' => ['type' => 'integer', 'length' => 10, 'null' => true] 23 | ]; 24 | 25 | protected $_records = [ 26 | ['id' => 1, 'parent_id' => null, 'class' => null, 'fk_id' => null, 'alias' => 'root', 'lft' => 1, 'rght' => 20], 27 | ['id' => 2, 'parent_id' => 1, 'class' => null, 'fk_id' => null, 'alias' => 'tpsReports', 'lft' => 2, 'rght' => 9], 28 | ['id' => 3, 'parent_id' => 2, 'class' => null, 'fk_id' => null, 'alias' => 'view', 'lft' => 3, 'rght' => 6], 29 | ['id' => 4, 'parent_id' => 3, 'class' => null, 'fk_id' => null, 'alias' => 'current', 'lft' => 4, 'rght' => 5], 30 | ['id' => 5, 'parent_id' => 2, 'class' => null, 'fk_id' => null, 'alias' => 'update', 'lft' => 7, 'rght' => 8], 31 | ['id' => 6, 'parent_id' => 1, 'class' => null, 'fk_id' => null, 'alias' => 'printers', 'lft' => 10, 'rght' => 19], 32 | ['id' => 7, 'parent_id' => 6, 'class' => null, 'fk_id' => null, 'alias' => 'print', 'lft' => 11, 'rght' => 14], 33 | ['id' => 8, 'parent_id' => 7, 'class' => null, 'fk_id' => null, 'alias' => 'lettersize', 'lft' => 12, 'rght' => 13], 34 | ['id' => 9, 'parent_id' => 6, 'class' => null, 'fk_id' => null, 'alias' => 'refill', 'lft' => 15, 'rght' => 16], 35 | ['id' => 10, 'parent_id' => 6, 'class' => null, 'fk_id' => null, 'alias' => 'smash', 'lft' => 17, 'rght' => 18], 36 | ]; 37 | } 38 | -------------------------------------------------------------------------------- /tests/cases/security/access/adapter/DbAclTest.php: -------------------------------------------------------------------------------- 1 | _adapter = new DbAcl([ 21 | 'classes' => [ 22 | 'permission' => $this->_model 23 | ] 24 | ]); 25 | } 26 | 27 | public function testCheck() { 28 | $this->_adapter->check('root/user/john', 'controller/post/add', ['read', 'create']); 29 | $model = $this->_model; 30 | extract($model::$callStatic); 31 | $this->assertEqual('check', $method); 32 | $this->assertEqual([ 33 | 'root/user/john', 'controller/post/add', ['read', 'create'] 34 | ], $params); 35 | } 36 | 37 | public function testGet() { 38 | $this->_adapter->get('param1', 'param2'); 39 | $model = $this->_model; 40 | extract($model::$callStatic); 41 | $this->assertEqual('get', $method); 42 | $this->assertEqual(['param1', 'param2'], $params); 43 | } 44 | 45 | public function testAllow() { 46 | $this->_adapter->allow('param1', 'param2', 'param3'); 47 | $model = $this->_model; 48 | extract($model::$callStatic); 49 | $this->assertEqual('allow', $method); 50 | $this->assertEqual(['param1', 'param2', 'param3', 1], $params); 51 | } 52 | 53 | public function testDeny() { 54 | $this->_adapter->deny('param1', 'param2', 'param3'); 55 | $model = $this->_model; 56 | extract($model::$callStatic); 57 | $this->assertEqual('allow', $method); 58 | $this->assertEqual(['param1', 'param2', 'param3', -1], $params); 59 | } 60 | 61 | public function testInherit() { 62 | $this->_adapter->inherit('param1', 'param2', 'param3'); 63 | $model = $this->_model; 64 | extract($model::$callStatic); 65 | $this->assertEqual('allow', $method); 66 | $this->assertEqual(['param1', 'param2', 'param3', 0], $params); 67 | } 68 | 69 | public function testError() { 70 | $this->_adapter->error('param1', 'param2', 'param3'); 71 | $model = $this->_model; 72 | extract($model::$callStatic); 73 | $this->assertEqual('error', $method); 74 | $this->assertEqual([], $params); 75 | } 76 | } 77 | 78 | ?> -------------------------------------------------------------------------------- /security/Access.php: -------------------------------------------------------------------------------- 1 | check($user, $request, $options); 48 | }; 49 | $params = compact('user', 'request', 'options'); 50 | return static::_filter(__FUNCTION__, $params, $filter, (array) $config['filters']); 51 | } 52 | 53 | /** 54 | * Static calls are transfered to adapters. 55 | * 56 | * @param string $method Method name caught by `__call()`. 57 | * @param array $params Arguments given to the above `$method` call. 58 | * @return mixed 59 | */ 60 | 61 | public static function __callStatic($method, $params) { 62 | $name = array_shift($params); 63 | if (($config = static::_config($name)) === null) { 64 | throw new ConfigException("Configuration `{$name}` has not been defined."); 65 | } 66 | $filter = function($self, $params) use ($name, $method) { 67 | return call_user_func_array([$self::adapter($name), $method], $params); 68 | }; 69 | return static::_filter(__FUNCTION__, $params, $filter, (array) $config['filters']); 70 | } 71 | } 72 | 73 | ?> -------------------------------------------------------------------------------- /tests/fixture/source/db_acl/AroFixture.php: -------------------------------------------------------------------------------- 1 | ['type' => 'id'], 17 | 'parent_id' => ['type' => 'integer', 'length' => 10, 'null' => true], 18 | 'class' => ['type' => 'string', 'null' => true], 19 | 'fk_id' => ['type' => 'integer', 'length' => 10, 'null' => true], 20 | 'alias' => ['type' => 'string', 'default' => ''], 21 | 'lft' => ['type' => 'integer', 'length' => 10, 'null' => true], 22 | 'rght' => ['type' => 'integer', 'length' => 10, 'null' => true] 23 | ]; 24 | /** 25 | * records property 26 | * 27 | * @var array 28 | */ 29 | protected $_records = [ 30 | ['id' => 1, 'parent_id' => null, 'class' => null, 'fk_id' => null, 'alias' => 'root', 'lft' => '1', 'rght' => '20'], 31 | ['id' => 2, 'parent_id' => 1, 'class' => 'li3_access\tests\fixture\model\blog\Group', 'fk_id' => '1', 'alias' => 'admin', 'lft' => '2', 'rght' => '5'], 32 | ['id' => 3, 'parent_id' => 1, 'class' => 'li3_access\tests\fixture\model\blog\Group', 'fk_id' => '2', 'alias' => 'managers', 'lft' => '6', 'rght' => '9'], 33 | ['id' => 4, 'parent_id' => 1, 'class' => 'li3_access\tests\fixture\model\blog\Group', 'fk_id' => '3', 'alias' => 'users', 'lft' => '10', 'rght' => '19'], 34 | ['id' => 5, 'parent_id' => 2, 'class' => 'li3_access\tests\fixture\model\blog\User', 'fk_id' => '1', 'alias' => 'Bob', 'lft' => '3', 'rght' => '4'], 35 | ['id' => 6, 'parent_id' => 3, 'class' => 'li3_access\tests\fixture\model\blog\User', 'fk_id' => '2', 'alias' => 'Lumbergh', 'lft' => '7', 'rght' => '8'], 36 | ['id' => 7, 'parent_id' => 4, 'class' => 'li3_access\tests\fixture\model\blog\User', 'fk_id' => '3', 'alias' => 'Samantha', 'lft' => '11', 'rght' => '12'], 37 | ['id' => 8, 'parent_id' => 4, 'class' => 'li3_access\tests\fixture\model\blog\User', 'fk_id' => '4', 'alias' => 'Micheal', 'lft' => '13', 'rght' => '14'], 38 | ['id' => 9, 'parent_id' => 4, 'class' => 'li3_access\tests\fixture\model\blog\User', 'fk_id' => '5', 'alias' => 'Peter', 'lft' => '15', 'rght' => '16'], 39 | ['id' => 10, 'parent_id' => 4, 'class' => 'li3_access\tests\fixture\model\blog\User', 'fk_id' => '6', 'alias' => 'Milton', 'lft' => '17', 'rght' => '18'], 40 | ]; 41 | } 42 | -------------------------------------------------------------------------------- /tests/fixture/source/db_acl/PermissionFixture.php: -------------------------------------------------------------------------------- 1 | ['type' => 'id'], 17 | 'aro_id' => ['type' => 'integer', 'length' => 10, 'null' => false], 18 | 'aco_id' => ['type' => 'integer', 'length' => 10, 'null' => false], 19 | 'privileges' => ['type' => 'text'] 20 | ]; 21 | 22 | protected $_records = [ 23 | ['id' => 1, 'aro_id' => '1', 'aco_id' => '1', 'privileges' => '{"create":0,"read":0,"update":0,"delete":0}'], 24 | ['id' => 2, 'aro_id' => '2', 'aco_id' => '1', 'privileges' => '{"read":1,"update":1,"delete":1}'], 25 | ['id' => 3, 'aro_id' => '3', 'aco_id' => '2', 'privileges' => '{"read":1}', ], 26 | ['id' => 4, 'aro_id' => '4', 'aco_id' => '2', 'privileges' => '{"create":1,"read":1,"delete":0}'], 27 | ['id' => 5, 'aro_id' => '4', 'aco_id' => '6', 'privileges' => '{"create":1,"read":1}'], 28 | ['id' => 6, 'aro_id' => '5', 'aco_id' => '1', 'privileges' => '{"create":1,"read":1,"update":1,"delete":1}'], 29 | ['id' => 7, 'aro_id' => '6', 'aco_id' => '3', 'privileges' => '{"create":0,"read":1,"update":0,"delete":0}'], 30 | ['id' => 8, 'aro_id' => '6', 'aco_id' => '4', 'privileges' => '{"create":0,"read":1,"update":0,"delete":1}'], 31 | ['id' => 9, 'aro_id' => '6', 'aco_id' => '6', 'privileges' => '{"create":0,"read":1,"update":1,"delete":0}'], 32 | ['id' => 10, 'aro_id' => '7', 'aco_id' => '2', 'privileges' => '{"create":0,"read":0,"update":0,"delete":0}'], 33 | ['id' => 11, 'aro_id' => '7', 'aco_id' => '7', 'privileges' => '{"create":1,"read":1,"update":1}'], 34 | ['id' => 12, 'aro_id' => '7', 'aco_id' => '8', 'privileges' => '{"create":1,"read":1,"update":1}'], 35 | ['id' => 13, 'aro_id' => '7', 'aco_id' => '9', 'privileges' => '{"create":1,"read":1,"update":1,"delete":1}'], 36 | ['id' => 14, 'aro_id' => '7', 'aco_id' => '10', 'privileges' => '{"delete":1}'], 37 | ['id' => 15, 'aro_id' => '8', 'aco_id' => '10', 'privileges' => '{"create":1,"read":1,"update":1,"delete":1}'], 38 | ['id' => 16, 'aro_id' => '8', 'aco_id' => '2', 'privileges' => '{"create":0,"read":0,"update":0,"delete":0}'], 39 | ['id' => 17, 'aro_id' => '9', 'aco_id' => '4', 'privileges' => '{"create":1,"read":1,"update":1,"delete":0}'], 40 | ['id' => 18, 'aro_id' => '9', 'aco_id' => '9', 'privileges' => '{"update":1,"delete":1}'], 41 | ['id' => 19, 'aro_id' => '10', 'aco_id' => '9', 'privileges' => '{"create":1,"read":1,"update":1,"delete":1}'], 42 | ['id' => 20, 'aro_id' => '10', 'aco_id' => '10', 'privileges' => '{"create":0,"read":0,"update":0,"delete":0}'] 43 | ]; 44 | } 45 | -------------------------------------------------------------------------------- /extensions/adapter/security/access/DbAcl.php: -------------------------------------------------------------------------------- 1 | 'li3_access\security\access\model\db_acl\Permission' 24 | ]; 25 | /** 26 | * @see lithium\data\Model::_autoConfig() 27 | * @var array 28 | */ 29 | protected $_autoConfig = ['classes']; 30 | 31 | /** 32 | * Check permission access 33 | * 34 | * @param string $requester The requester identifier (Aro). 35 | * @param string $controlled The controlled identifier (Aco). 36 | * @return boolean Success (true if Aro has access to action in Aco, false otherwise) 37 | */ 38 | public function check($requester, $request, $perms) { 39 | $permission = $this->_classes['permission']; 40 | return $permission::check($requester, $request, $perms); 41 | } 42 | 43 | /** 44 | * Get all permission access 45 | * 46 | * @param string $requester The requesting identifier (Aro). 47 | * @param string $controlled The controlled identifier (Aco). 48 | */ 49 | public function get($requester, $request) { 50 | $permission = $this->_classes['permission']; 51 | return $permission::get($requester, $request); 52 | } 53 | 54 | /** 55 | * Allow access 56 | * 57 | * @param string $requester The requesting identifier (Aro). 58 | * @param string $controlled The controlled identifier (Aco). 59 | * @param string $perms Perms to allow (defaults to `*`) 60 | * @param integer $value Access type (1 to allow, -1 to deny, 0 to inherit) 61 | * @return boolean Success 62 | */ 63 | public function allow($requester, $controlled, $perms = "*", $value = 1) { 64 | $permission = $this->_classes['permission']; 65 | return $permission::allow($requester, $controlled, $perms, $value); 66 | } 67 | 68 | /** 69 | * Deny access 70 | * 71 | * @param string $requester ARO The requesting object identifier. 72 | * @param string $request ACO The controlled object identifier. 73 | * @param string $perms Perms to deny (defaults to *) 74 | * @return boolean Success 75 | */ 76 | public function deny($requester, $controlled, $perms = "*") { 77 | $permission = $this->_classes['permission']; 78 | return $permission::allow($requester, $controlled, $perms, -1); 79 | } 80 | 81 | /** 82 | * Inherit access 83 | * 84 | * @param string $requester ARO The requesting object identifier. 85 | * @param string $request ACO The controlled object identifier. 86 | * @param string $perms Perms to inherit (defaults to *) 87 | * @return boolean Success 88 | */ 89 | public function inherit($requester, $controlled, $perms = "*") { 90 | $permission = $this->_classes['permission']; 91 | return $permission::allow($requester, $controlled, $perms, 0); 92 | } 93 | 94 | /** 95 | * Returns the last error array. 96 | * 97 | * @return array 98 | */ 99 | public function error() { 100 | $permission = $this->_classes['permission']; 101 | return $permission::error(); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /security/access/model/db_acl/AclNode.php: -------------------------------------------------------------------------------- 1 | ['class' => 'class', 'alias' => 'alias', 'key' => 'fk_id'] 30 | ]; 31 | 32 | /** 33 | * Retrieves the Acl node for this model. 34 | * 35 | * @param mixed $ref Array with 'model' and 'fk_id', model object, or string value. 36 | * @return array Nodes founded in database, `false` otherwise. 37 | */ 38 | public static function node($ref = null, $strict = true) { 39 | $db = static::connection(); 40 | $name = static::meta('name'); 41 | 42 | extract(static::meta('acl'), EXTR_SKIP); 43 | extract(static::actsAs('Tree', true), EXTR_SKIP); 44 | 45 | if (is_string($ref)) { 46 | $w = $with = 'Parent'; 47 | $i = 0; 48 | $paths = explode('/', $ref); 49 | $start = $paths[0]; 50 | unset($paths[0]); 51 | 52 | $query = [ 53 | 'alias' => $name, 54 | 'fields' => [$name], 55 | 'with' => [ 56 | $with => [ 57 | 'alias' => "{$name}0", 58 | 'constraints' => (object) [ 59 | "{$name}0.{$alias}" => (object) $db->value($start, [ 60 | 'type' => 'string' 61 | ]) 62 | ] 63 | ]], 64 | 'order' => "{$name}.{$left} DESC" 65 | ]; 66 | 67 | $conditions = [static::_wrapCond($db, $name, 0, $left, $right)]; 68 | 69 | foreach ($paths as $i => $path) { 70 | $j = $i - 1; 71 | $w .= ".{$with}"; 72 | $query['with'][$w] = [ 73 | 'alias' => "{$name}{$i}", 74 | 'constraints' => (object) [ 75 | "{$name}{$i}.{$left}" => ['>' => "{$name}{$j}.{$left}"], 76 | "{$name}{$i}.{$right}" => ['<' => "{$name}{$j}.{$right}"], 77 | "{$name}{$i}.{$alias}" => $db->value($path, ['type' => 'string']), 78 | "{$name}{$j}." . static::meta('key') => "{$name}{$i}.{$parent}" 79 | ] 80 | ]; 81 | $conditions[] = static::_wrapCond($db, $name, $i, $left, $right); 82 | } 83 | 84 | $query['conditions'] = ['OR' => $conditions]; 85 | 86 | $result = static::find('all', $query + ['return' => 'array']); 87 | 88 | $paths = array_values($paths); 89 | 90 | if (!isset($result[0])) { 91 | return false; 92 | } 93 | if ($strict && ( 94 | ($paths && $result[0][$alias] !== $paths[count($paths) - 1]) || 95 | (!$paths && $result[0][$alias] !== $start) 96 | )) { 97 | return false; 98 | } 99 | return $result; 100 | } elseif (is_object($ref)) { 101 | $self = static::_object(); 102 | if ($ref instanceof $self->_classes['entity']) { 103 | $model = $ref->model(); 104 | $id = $model::meta('key'); 105 | $ref = [$class => $model, $id => $ref->$id]; 106 | } 107 | } 108 | 109 | if ($ref && is_array($ref) && isset($ref[$class])) { 110 | $conditions = []; 111 | $model = $ref[$class]; 112 | $id = $model::meta('key'); 113 | if (isset($ref[$id])) { 114 | $conditions["{$name}0.{$key}"] = $ref[$id]; 115 | } elseif (isset($ref[$key])) { 116 | $conditions["{$name}0.{$key}"] = $ref[$key]; 117 | } else { 118 | return false; 119 | } 120 | $conditions["{$name}0.{$class}"] = $model; 121 | 122 | $constraints = (object) static::_wrapCond($db, $name, '0', $left, $right, false); 123 | 124 | $query = [ 125 | 'conditions' => $conditions, 126 | 'fields' => [$name], 127 | 'with' => [ 128 | 'Parent' => [ 129 | 'alias' => "{$name}0", 130 | 'constraints' => $constraints 131 | ] 132 | ], 133 | 'order' => "{$name}.{$left} DESC" 134 | ]; 135 | 136 | return static::find('all', $query + ['return' => 'array']); 137 | } 138 | return false; 139 | } 140 | 141 | /** 142 | * Helper method which create a conditionnal array used be `AclNode::node()` 143 | * 144 | * @param object $db The database connection 145 | * @param string $alias The alias of the model 146 | * @param string $i An index 147 | * @param string $left The left field name (MPTT) 148 | * @param string $right The right field name (MPTT) 149 | * @param boolean $escape If `true`, the field name will be escaped 150 | * @return array The conditionnal array 151 | */ 152 | protected static function _wrapCond($db, $alias, $i, $left, $right, $escape = true) { 153 | $cond1 = "{$alias}{$i}.{$left}"; 154 | $cond2 = "{$alias}{$i}.{$right}"; 155 | 156 | if ($escape) { 157 | $cond1 = $db->name($cond1); 158 | $cond2 = $db->name($cond2); 159 | } 160 | 161 | return [ 162 | 'AND' => [ 163 | "{$alias}.{$left}" => [ 164 | '<=' => (object) $cond1 165 | ], 166 | "{$alias}.{$right}" => [ 167 | '>=' => (object) $cond2 168 | ] 169 | ] 170 | ]; 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /extensions/adapter/security/access/Rules.php: -------------------------------------------------------------------------------- 1 | _rules += [ 65 | 'allowAll' => [ 66 | 'rule' => function() { 67 | return true; 68 | } 69 | ], 70 | 'denyAll' => [ 71 | 'rule' => function() { 72 | return false; 73 | } 74 | ], 75 | 'allowAnyUser' => [ 76 | 'message' => 'You must be logged in.', 77 | 'rule' => function($user) { 78 | return $user ? true : false; 79 | } 80 | ], 81 | 'allowIp' => [ 82 | 'message' => 'Your IP is not allowed to access this area.', 83 | 'rule' => function($user, $request, $options) { 84 | $options += ['ip' => false]; 85 | if (is_string($options['ip']) && strpos($options['ip'], '/') === 0) { 86 | return (boolean) preg_match($options['ip'], $request->env('REMOTE_ADDR')); 87 | } 88 | if (is_array($options['ip'])) { 89 | return in_array($request->env('REMOTE_ADDR'), $options['ip']); 90 | } 91 | return $request->env('REMOTE_ADDR') === $options['ip']; 92 | } 93 | ] 94 | ]; 95 | } 96 | 97 | /** 98 | * The check method 99 | * 100 | * @param mixed $user The user data array that holds all necessary information about 101 | * the user requesting access. If set to `null`, the default `Rules::_user()` 102 | * will be used. 103 | * @param object $request The requested object. 104 | * @param array $options Options array to pass to the rule closure. 105 | * @return boolean `true` if access is ok, `false` otherwise. 106 | */ 107 | public function check($user, $request, array $options = []) { 108 | $defaults = ['rules' => $this->_defaults, 'allowAny' => $this->_allowAny]; 109 | $options += $defaults; 110 | 111 | if (empty($options['rules'])) { 112 | throw new RuntimeException("Missing `'rules'` option."); 113 | } 114 | 115 | $rules = (array) $options['rules']; 116 | $this->_error = []; 117 | $params = array_diff_key($options, $defaults); 118 | 119 | if (!isset($user) && is_callable($this->_user)) { 120 | $user = $this->_user->__invoke(); 121 | } 122 | 123 | foreach ($rules as $name => $rule) { 124 | 125 | $result = $this->_check($user, $request, $name, $rule, $params); 126 | 127 | if ($result === false && !$options['allowAny']) { 128 | return false; 129 | } 130 | if ($result === true && $options['allowAny']) { 131 | return true; 132 | } 133 | } 134 | return !$options['allowAny']; 135 | } 136 | 137 | /** 138 | * Helper for `Rules::check()`. 139 | */ 140 | protected function _check($user, $request, $name, $rule, $params) { 141 | $message = 'You are not permitted to access this area.'; 142 | 143 | if (is_string($rule) && isset($this->_rules[$rule])) { 144 | $name = $rule; 145 | $closure = $this->_rules[$rule]; 146 | } elseif (is_array($rule)) { 147 | if (is_string($name) && isset($this->_rules[$name])) { 148 | $params += $rule; 149 | $closure = $this->_rules[$name]; 150 | } else { 151 | $closure = $rule; 152 | } 153 | } elseif (is_callable($rule)) { 154 | $closure = $rule; 155 | } else { 156 | throw new RuntimeException("Invalid rule."); 157 | } 158 | 159 | if (!is_callable($closure)) { 160 | $message = isset($closure['message']) ? $closure['message'] : $message; 161 | $closure = isset($closure['rule']) ? $closure['rule'] : $closure; 162 | } 163 | 164 | $params += compact('message'); 165 | if(!call_user_func($closure, $user, $request, $params)) { 166 | $this->_error[$name] = $params['message']; 167 | return false; 168 | } 169 | return true; 170 | } 171 | 172 | /** 173 | * Getter/setter for rules 174 | * 175 | * @param string $name The rule name to get/set. if `null` all rules are returned. 176 | * @param function $rule A Closure which be called with the following parameter : 177 | * - first parameter : the user 178 | * - second parameter : a `Request` instance 179 | * - third parameter : an options array 180 | * @return mixed Either an array of rule closures, a single rule closure, or `null`. 181 | */ 182 | public function rules($name = null, $rule = null, $options = []) { 183 | if (!$name) { 184 | return $this->_rules; 185 | } 186 | if ($rule) { 187 | $this->_rules[$name] = compact('rule') + $options; 188 | return; 189 | } 190 | return isset($this->_rules[$name]) ? $this->_rules[$name] : null; 191 | } 192 | 193 | /** 194 | * Returns the last error array. 195 | * 196 | * @return array 197 | */ 198 | public function error() { 199 | return $this->_error; 200 | } 201 | } 202 | 203 | ?> -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Access control adapters 2 | 3 | Don't use this in production. It's an early alpha release. 4 | 5 | ## Requirements 6 | 7 | - **PHP 5.4** 8 | - This plugin needs [li3_behaviors](https://github.com/jails/li3_behaviors) (only if you intend to use the DbAcl adapter). 9 | - This plugin needs [li3_tree](https://github.com/jails/li3_tree) (only if you intend to use the DbAcl adapter). 10 | - This plugin needs [li3_fixtures](https://github.com/UnionOfRAD/li3_fixtures) (only if you intend to run DbAcl adapter tests). 11 | 12 | ## Installation 13 | 14 | Checkout the code to either of your library directories: 15 | 16 | ``` 17 | cd libraries 18 | git clone git@github.com:jails/li3_access.git 19 | ``` 20 | 21 | Include the library in your `/app/config/bootstrap/libraries.php` 22 | 23 | ``` 24 | Libraries::add('li3_access'); 25 | ``` 26 | 27 | ## Presentation 28 | 29 | This plugin provide a couple of adapters for managing access control into your application. It can manage simple rule based system as well as access control lists system. Access control lists are a way to manage application permissions in a fine-grained. It's not as fast as rule based system but allow further control on your application/models. 30 | 31 | ## API 32 | 33 | ### Simple adapter: 34 | 35 | The simple adapter only checks that the passed data is not empty. 36 | 37 | ```php 38 | Access::config('simple' => ['adapter' => 'Simple']); 39 | Access::check('rules', ['username' => 'Max']); //return `true` 40 | Access::check('rules', true); //return `true` 41 | Access::check('rules', []); //return `false` 42 | ``` 43 | 44 | ### Rule adapter: 45 | 46 | The rule adapter check access from a predefinied/custom closure. To use this adapter configure `Access` like the following: 47 | 48 | ```php 49 | Access::config('rules', ['adapter' => 'Rules']); 50 | ``` 51 | 52 | The rules adpater already contains the following rules: `'allowAll'`, `'denyAll'`, `'allowAnyUser'`, `'allowIp'`. 53 | 54 | Example of use: 55 | 56 | ```php 57 | $user = Auth::check('auth_config_name'); 58 | Access::check('rules', $user, $request, ['rules' => ['allowAnyUser']]); 59 | 60 | $user = User::find('first', ['username' => 'psychic']); 61 | Access::check('rules', $user, $request, ['rules' => ['allowAnyUser']]); 62 | ``` 63 | 64 | Rule with parameters: 65 | 66 | ```php 67 | Access::check('rules', null, $request, [ 68 | 'rules' => [ 69 | 'allowIp' => [ 70 | 'ip' => '/10\.0\.1\.\d+/' //parameter to pass to the `'allowIp'` closure. 71 | ] 72 | ] 73 | ]); 74 | ``` 75 | 76 | You can add custom rule on `::config()`: 77 | 78 | ```php 79 | Access::config('rules' => [ 80 | 'adapter' => 'Rules', 81 | 'rules' => [ 82 | 'testDeny' => [ 83 | 'message' => 'Access denied.', 84 | 'rule' => function($requester) { 85 | return false; 86 | } 87 | ] 88 | ] 89 | ]); 90 | ``` 91 | 92 | or dynamically with: 93 | 94 | ```php 95 | Access::rules('rules', 'testDeny', function($requester) { return false; }, [ 96 | 'message' => 'Access denied.' 97 | ]); 98 | ``` 99 | 100 | ### DbAcl adapter: 101 | 102 | This adapter currently works for only SQL databases (i.e MySQL, PostgreSQL and Sqlite3). 103 | 104 | ```php 105 | Access::config('acl' => ['adapter' => 'DbAcl']); 106 | ``` 107 | 108 | Access control lists, or ACL, handle two main things: things that want stuff, and things that are wanted. This is usually represented by: 109 | 110 | - Access Control Object (Aco), i.e. something that is wanted 111 | - Access Request Object (Aro), i.e. Something that wants something 112 | 113 | And beetween Acos and Aros, there's permissions which define the access privileges beetween Aros and Acos. 114 | 115 | Above, the schema needed to makes things works out of the box for a MySQL database: 116 | 117 | ```sql 118 | DROP TABLE IF EXISTS `acos`; 119 | CREATE TABLE `acos` ( 120 | `id` int(11) NOT NULL AUTO_INCREMENT, 121 | `parent_id` int(10) DEFAULT NULL, 122 | `class` varchar(255) DEFAULT NULL, 123 | `fk_id` int(10) DEFAULT NULL, 124 | `alias` varchar(255) DEFAULT NULL, 125 | `lft` int(10) DEFAULT NULL, 126 | `rght` int(10) DEFAULT NULL, 127 | PRIMARY KEY (`id`) 128 | ); 129 | 130 | 131 | DROP TABLE IF EXISTS `aros`; 132 | CREATE TABLE `aros` ( 133 | `id` int(11) NOT NULL AUTO_INCREMENT, 134 | `parent_id` int(10) DEFAULT NULL, 135 | `class` varchar(255) DEFAULT NULL, 136 | `fk_id` int(10) DEFAULT NULL, 137 | `alias` varchar(255) DEFAULT NULL, 138 | `lft` int(10) DEFAULT NULL, 139 | `rght` int(10) DEFAULT NULL, 140 | PRIMARY KEY (`id`) 141 | ); 142 | 143 | 144 | DROP TABLE IF EXISTS `permissions`; 145 | CREATE TABLE `permissions` ( 146 | `id` int(11) NOT NULL AUTO_INCREMENT, 147 | `aro_id` int(10) NOT NULL, 148 | `aco_id` int(10) NOT NULL, 149 | `privileges` text, 150 | PRIMARY KEY (`id`) 151 | ); 152 | 153 | ``` 154 | 155 | Of course you need to adapt this schema according your own SQL database. 156 | 157 | Once Acos and Aros are correctly defined (see test's fixtures for a better understanding of what Acos and Aros looks like). 158 | 159 | You can add privileges: 160 | 161 | ```php 162 | Access::allow('acl', 'admin/max', 'controller/backend', ['read', 'create', 'update', 'delete']); 163 | //or: 164 | Access::allow('acl', 'admin/max', 'controller/backend', 'publish'); 165 | //or: 166 | $user = User::find('first', ['username' => 'max']); 167 | Access::allow('acl', $user, 'controller/backend', ['read', 'create', 'update', 'publish']); 168 | ``` 169 | 170 | You can remove privileges: 171 | 172 | ```php 173 | Access::deny('acl', 'user/joe', 'controller/backend', ['delete']); 174 | ``` 175 | 176 | Use `Access::check()` to check some privileges: 177 | 178 | ```php 179 | Access::check('acl', 'user/joe', 'controller/backend', ['delete']); 180 | ``` 181 | 182 | Or `Access::get()` for recovering all privileges for an Aro/Aco: 183 | 184 | ```php 185 | Access::get('acl', 'user/joe', 'controller/backend'); 186 | ``` 187 | 188 | ## Greetings 189 | 190 | The li3 team, Tom Maiaroto, Weluse, rich97, CakePHP's ACL, Pamela Anderson and all others which make that possible. 191 | 192 | ## Build status 193 | [![Build Status](https://secure.travis-ci.org/jails/li3_access.png?branch=master)](http://travis-ci.org/jails/li3_access) -------------------------------------------------------------------------------- /tests/integration/security/access/model/db_acl/AclNodeTest.php: -------------------------------------------------------------------------------- 1 | 'li3_access\tests\fixture\model\blog\User', 24 | '_group' => 'li3_access\tests\fixture\model\blog\Group' 25 | ]; 26 | 27 | protected $_fixtures = [ 28 | 'user' => 'li3_access\tests\fixture\source\blog\UserFixture', 29 | 'group' => 'li3_access\tests\fixture\source\blog\GroupFixture', 30 | 'aco' => 'li3_access\tests\fixture\source\db_acl\AcoFixture', 31 | 'aro' => 'li3_access\tests\fixture\source\db_acl\AroFixture' 32 | ]; 33 | 34 | /** 35 | * Skip the test if no test database connection available. 36 | */ 37 | public function skip() { 38 | $dbConfig = Connections::get($this->_connection, ['config' => true]); 39 | $isAvailable = ( 40 | $dbConfig && 41 | Connections::get($this->_connection)->isConnected(['autoConnect' => true]) 42 | ); 43 | $this->skipIf(!$isAvailable, "No {$this->_connection} connection available."); 44 | 45 | $db = Connections::get($this->_connection); 46 | 47 | $this->skipIf( 48 | !($db instanceof Database), 49 | "The {$this->_connection} connection is not a relational database." 50 | ); 51 | } 52 | 53 | public function setUp() { 54 | Fixtures::config([ 55 | 'db' => [ 56 | 'adapter' => 'Connection', 57 | 'connection' => $this->_connection, 58 | 'fixtures' => $this->_fixtures 59 | ] 60 | ]); 61 | } 62 | 63 | public function tearDown() { 64 | foreach($this->_models as $key => $class){ 65 | $class::reset(); 66 | } 67 | Fixtures::clear('db'); 68 | } 69 | 70 | public function testCreate() { 71 | Fixtures::save('db'); 72 | 73 | $aro = Aro::create(); 74 | $aro->set(['alias' => 'Chotchkey']); 75 | $this->assertTrue($aro->save()); 76 | 77 | $key = Aro::key(); 78 | $parent = $aro->$key; 79 | 80 | $aro = Aro::create(); 81 | $aro->set(['parent_id' => $parent, 'alias' => 'Joanna']); 82 | $this->assertTrue($aro->save()); 83 | 84 | $aro = Aro::create(); 85 | $aro->set(['parent_id' => $parent, 'alias' => 'Stapler']); 86 | $this->assertTrue($aro->save()); 87 | 88 | $root = Aro::node('root'); 89 | $parent = $root[0][Aco::key()]; 90 | 91 | $aco = Aco::create(); 92 | $aco->set(['parent_id' => $parent, 'alias' => 'Drinks']); 93 | $this->assertTrue($aco->save()); 94 | 95 | $aco = Aco::create(); 96 | $aco->set(['parent_id' => $parent, 'alias' => 'PiecesOfFlair']); 97 | $this->assertTrue($aco->save()); 98 | } 99 | 100 | public function testCreateWithParent() { 101 | Fixtures::save('db'); 102 | 103 | $parent = Aro::find('first', ['conditions' => ['alias' => 'Peter']]); 104 | $key = Aro::key(); 105 | $aro = Aro::create(); 106 | $aro->set([ 107 | 'alias' => 'Subordinate', 108 | 'model' => 'User', 109 | 'fk_id' => 7, 110 | 'parent_id' => $parent->$key 111 | ]); 112 | $aro->save(); 113 | 114 | $result = Aro::find('first', ['conditions' => ['alias' => 'Subordinate']]); 115 | $this->assertEqual(16, $result->lft); 116 | $this->assertEqual(17, $result->rght); 117 | } 118 | 119 | public function testNode() { 120 | Fixtures::save('db'); 121 | extract($this->_models); 122 | $result1 = Set::extract(Aco::node('root/printers/refill'), '/id'); 123 | $result2 = Set::extract(Aco::node('printers/refill'), '/id'); 124 | $result3 = Set::extract(Aco::node('refill'), '/id'); 125 | 126 | $expected = ['9', '6', '1']; 127 | $this->assertEqual($expected, $result1); 128 | $this->assertEqual($expected, $result2); 129 | $this->assertEqual($expected, $result3); 130 | 131 | $result = Aco::node('root/refill'); 132 | $this->assertFalse($result); 133 | 134 | $result = Aco::node(''); 135 | $this->assertFalse($result); 136 | 137 | $result = Aro::node('root/users/Samantha'); 138 | $expected = [ 139 | [ 140 | 'id' => '7', 'parent_id' => '4', 'class' => $_user, 141 | 'fk_id' => 3, 'alias' => 'Samantha', 'lft' => 11, 'rght' => 12 142 | ], 143 | [ 144 | 'id' => '4', 'parent_id' => '1', 'class' => $_group, 145 | 'fk_id' => 3, 'alias' => 'users', 'lft' => 10, 'rght' => 19 146 | ], 147 | [ 148 | 'id' => '1', 'parent_id' => null, 'class' => null, 149 | 'fk_id' => null, 'alias' => 'root', 'lft' => 1, 'rght' => 20 150 | ] 151 | ]; 152 | $this->assertEqual($expected, $result); 153 | 154 | $result = Aco::node('root/tpsReports/view/current'); 155 | $expected = [ 156 | [ 157 | 'id' => '4', 'parent_id' => '3', 'class' => null, 158 | 'fk_id' => null, 'alias' => 'current', 'lft' => 4, 'rght' => 5 159 | ], 160 | [ 161 | 'id' => '3', 'parent_id' => '2', 'class' => null, 162 | 'fk_id' => null, 'alias' => 'view', 'lft' => 3, 'rght' => 6 163 | ], 164 | [ 165 | 'id' => '2', 'parent_id' => '1', 'class' => null, 166 | 'fk_id' => null, 'alias' => 'tpsReports', 'lft' => 2, 'rght' => 9 167 | ], 168 | [ 169 | 'id' => '1', 'parent_id' => null, 'class' => null, 170 | 'fk_id' => null, 'alias' => 'root', 'lft' => 1, 'rght' => 20 171 | ] 172 | ]; 173 | $this->assertEqual($expected, $result); 174 | } 175 | 176 | public function testNodeArrayFind() { 177 | Fixtures::save('db'); 178 | extract($this->_models); 179 | $result = Set::extract(Aro::node(['class' => $_user, 'id' => '1']), '/id'); 180 | $expected = ['5', '2', '1']; 181 | $this->assertEqual($expected, $result); 182 | 183 | $result = Set::extract(Aro::node(['class' => $_user, 'fk_id' => '1']), '/id'); 184 | $expected = ['5', '2', '1']; 185 | $this->assertEqual($expected, $result); 186 | 187 | $result = Set::extract(Aro::node(['class' => $_group, 'id' => '1']), '/id'); 188 | $expected = ['2', '1']; 189 | $this->assertEqual($expected, $result); 190 | 191 | $result = Set::extract(Aro::node(['class' => $_group, 'fk_id' => '1']), '/id'); 192 | $expected = ['2', '1']; 193 | $this->assertEqual($expected, $result); 194 | } 195 | 196 | public function testNodeEntity() { 197 | Fixtures::save('db'); 198 | extract($this->_models); 199 | $user = $_user::create(); 200 | $user->id = 1; 201 | $result = Set::extract(Aro::node($user), '/id'); 202 | $expected = ['5', '2', '1']; 203 | $this->assertEqual($expected, $result); 204 | 205 | $group = $_group::create(); 206 | $group->id = 1; 207 | $result = Set::extract(Aro::node($group), '/id'); 208 | $expected = ['2', '1']; 209 | $this->assertEqual($expected, $result); 210 | } 211 | 212 | public function testNodeWithNonStrictMode() { 213 | Fixtures::save('db'); 214 | extract($this->_models); 215 | $result = Set::extract(Aco::node('root/printers/refill/unexisting', false), '/id'); 216 | $expected = ['9', '6', '1']; 217 | $this->assertEqual($expected, $result); 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /security/access/model/db_acl/Permission.php: -------------------------------------------------------------------------------- 1 | 'li3_access\security\access\model\db_acl\Aro', 39 | 'aco' => 'li3_access\security\access\model\db_acl\Aco' 40 | ]; 41 | 42 | /** 43 | * Model belongsTo relations. 44 | * 45 | * @var array 46 | */ 47 | public $belongsTo = ['Aro', 'Aco']; 48 | 49 | /** 50 | * Forbid error message 51 | * @var string 52 | */ 53 | protected $_forbid = 'You are not permitted to access this area.'; 54 | 55 | /** 56 | * @var array $_error Last error message 57 | */ 58 | protected $_error = []; 59 | 60 | /** 61 | * Get the acl array between an Aro and an Aco 62 | * 63 | * @param string $requester The requesting identifier (Aro). 64 | * @param string $controlled The controlled identifier (Aco). 65 | * @return array The permissions 66 | */ 67 | public static function acl($requester, $controlled) { 68 | $self = static::_object(); 69 | $aro = $self->_classes['aro']; 70 | $aco = $self->_classes['aco']; 71 | 72 | if (!(($aroNode = $aro::node($requester)) && ($acoNode = $aco::node($controlled)))) { 73 | return false; 74 | } 75 | 76 | $acl = static::find('first', [ 77 | 'conditions' => [ 78 | key(static::relations('Aro')->key()) => $aroNode[0]['id'], 79 | key(static::relations('Aco')->key()) => $acoNode[0]['id'] 80 | ], 81 | 'return' => 'array' 82 | ]); 83 | if (isset($acl['privileges'])) { 84 | $acl['privileges'] = json_decode($acl['privileges'], true); 85 | } 86 | 87 | return [ 88 | 'aro' => $aroNode[0]['id'], 89 | 'aco' => $acoNode[0]['id'], 90 | 'acl' => isset($acl) ? $acl: [] 91 | ]; 92 | } 93 | 94 | /** 95 | * Checks permission access 96 | * 97 | * @param string $requester The requester identifier (Aro). 98 | * @param string $controlled The controlled identifier (Aco). 99 | * @return boolean Success (true if Aro has access to action in Aco, false otherwise) 100 | */ 101 | public static function check($requester, $controlled, $privileges) { 102 | $self = static::_object(); 103 | $aro = $self->_classes['aro']; 104 | $aco = $self->_classes['aco']; 105 | 106 | if (!( 107 | ($aroNodes = $aro::node($requester, false)) && 108 | ($acoNodes = $aco::node($controlled, false)) 109 | )) { 110 | return false; 111 | } 112 | 113 | $inherited = []; 114 | $required = (array) $privileges; 115 | $count = count($required); 116 | $aro_id = key(static::relations('Aro')->key()); 117 | $aco_id = key(static::relations('Aco')->key()); 118 | $ids = Set::extract($acoNodes, '/'.$aco::meta('key')); 119 | $left = $aco::actsAs('Tree', true, 'left'); 120 | 121 | foreach ($aroNodes as $node) { 122 | $id = $node[$aro::meta('key')]; 123 | if ($datas = static::_permQuery($id, $ids, $aro_id, $aco_id, $left)) { 124 | foreach ($datas as $data) { 125 | if (!$privileges = json_decode($data['privileges'], true)) { 126 | break; 127 | } 128 | foreach ($required as $key) { 129 | if (isset($privileges[$key])) { 130 | if(!$privileges[$key]) { 131 | $self->_error = ['message' => $self->_forbid]; 132 | return false; 133 | } else { 134 | $inherited[$key] = 1; 135 | } 136 | } 137 | } 138 | if (count($inherited) === $count) { 139 | $self->_error = []; 140 | return true; 141 | } 142 | } 143 | } 144 | } 145 | $self->_error = ['message' => $self->_forbid]; 146 | return false; 147 | } 148 | 149 | /** 150 | * Get all permission access 151 | * 152 | * @param string $requester The requesting identifier (Aro). 153 | * @param string $controlled The controlled identifier (Aco). 154 | */ 155 | public static function get($requester, $controlled) { 156 | $self = static::_object(); 157 | $aro = $self->_classes['aro']; 158 | $aco = $self->_classes['aco']; 159 | 160 | if (!( 161 | ($aroNodes = $aro::node($requester, false)) && 162 | ($acoNodes = $aco::node($controlled, false)) 163 | )) { 164 | return false; 165 | } 166 | 167 | $privileges = []; 168 | $aro_id = key(static::relations('Aro')->key()); 169 | $aco_id = key(static::relations('Aco')->key()); 170 | $left = $aco::actsAs('Tree', true, 'left'); 171 | $ids = Set::extract($acoNodes, '/'.$aco::meta('key')); 172 | 173 | foreach($aroNodes as $node) { 174 | $id = $node[$aro::meta('key')]; 175 | if ($datas = static::_permQuery($id, $ids, $aro_id, $aco_id, $left)) { 176 | foreach ($datas as $data) { 177 | $privileges = $privileges + (array) json_decode($data['privileges'], true); 178 | } 179 | } 180 | } 181 | return $privileges; 182 | } 183 | 184 | /** 185 | * Load permissions query 186 | * 187 | * @param mixed $id The Aro id 188 | * @param array $ids The Aco ids 189 | * @param string $aro_id The name of the Aro foreign key 190 | * @param string $aco_id The name of the Aco foreign key 191 | * @param string $left The name of the left field name (Tree behavior config) 192 | * @return array Loaded permissions 193 | */ 194 | protected static function _permQuery($id, $ids, $aro_id, $aco_id, $left) { 195 | return static::find('all', [ 196 | 'alias' => 'Permission', 197 | 'fields' => 'Permission', 198 | 'conditions' => [ 199 | 'Permission.' . $aro_id => $id, 200 | 'Permission.' . $aco_id => $ids 201 | ], 202 | 'order' => "Aco.{$left} DESC", 203 | 'with' => ['Aco' => ['alias' => 'Aco']], 204 | 'return' => 'array' 205 | ]); 206 | } 207 | 208 | /** 209 | * Allow access 210 | * 211 | * @param string $requester The requesting identifier (Aro). 212 | * @param string $controlled The controlled identifier (Aco). 213 | * @param string $privileges Privileges to allow 214 | * @param integer $value Access type (1 to allow, 0 to deny, null to inherit) 215 | * @return boolean Success 216 | */ 217 | public static function allow($requester, $controlled, $privileges, $value = 1) { 218 | if (!$acl = static::acl($requester, $controlled)) { 219 | throw new RuntimeException("Invalid acl node."); 220 | } 221 | 222 | $datas = []; 223 | $privileges = (array) $privileges; 224 | $privileges = array_fill_keys($privileges, $value); 225 | 226 | $datas[key(static::relations('Aro')->key())] = $acl['aro']; 227 | $datas[key(static::relations('Aco')->key())] = $acl['aco']; 228 | 229 | $options = []; 230 | if ($acl['acl']) { 231 | $options = ['exists' => true]; 232 | $datas += $acl['acl']; 233 | $privileges += $datas['privileges']; 234 | } 235 | $privileges = array_filter($privileges, function($val) {return $val !== null;}); 236 | $datas['privileges'] = json_encode($privileges, JSON_FORCE_OBJECT); 237 | $entity = static::create([], $options); 238 | $entity->set($datas); 239 | return $entity->save(); 240 | } 241 | 242 | /** 243 | * Deny access 244 | * 245 | * @param string $requester ARO The requesting object identifier. 246 | * @param string $request ACO The controlled object identifier. 247 | * @param string $privileges Privileges to deny 248 | * @return boolean Success 249 | */ 250 | public static function deny($requester, $request, $privileges) { 251 | return static::allow($requester, $request, $privileges, 0); 252 | } 253 | 254 | /** 255 | * Inherit access 256 | * 257 | * @param string $requester ARO The requesting object identifier. 258 | * @param string $request ACO The controlled object identifier. 259 | * @param string $privileges Privileges to inherit 260 | * @return boolean Success 261 | */ 262 | public static function inherit($requester, $request, $privileges) { 263 | return static::allow($requester, $request, $privileges, null); 264 | } 265 | 266 | /** 267 | * Returns the last error array or an empty array if no error. 268 | * 269 | * @return array 270 | */ 271 | public static function error() { 272 | return $this->_error; 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /tests/cases/security/access/adapter/RulesTest.php: -------------------------------------------------------------------------------- 1 | _adapter = new Rules(); 20 | } 21 | 22 | public function tearDown() {} 23 | 24 | public function testCheck() { 25 | $request = new Request(['env' => ['REMOTE_ADDR' => '10.0.1.1']]); 26 | 27 | $rules = ['allowAnyUser', 'allowAll', 'allowIp' => [ 28 | 'ip' => '10.0.1.1' 29 | ]]; 30 | 31 | $result = $this->_adapter->check(['username' => 'Nate'], $request, compact('rules')); 32 | $this->assertTrue($result); 33 | 34 | $expected = [ 35 | 'denyAll' => 'You are not permitted to access this area.', 36 | ]; 37 | $result = $this->_adapter->check(['username' => 'Gwoo'], $request, ['rules' => 'denyAll']); 38 | $this->assertFalse($result); 39 | $result = $this->_adapter->error(); 40 | $this->assertEqual($expected, $result); 41 | 42 | $rules = ['allowAnyUser']; 43 | $expected = [ 44 | 'allowAnyUser' => 'You must be logged in.' 45 | ]; 46 | $result = $this->_adapter->check([], null, compact('rules')); 47 | $this->assertFalse($result); 48 | $result = $this->_adapter->error(); 49 | $this->assertEqual($expected, $result); 50 | 51 | $result = $this->_adapter->check(false, null, compact('rules')); 52 | $this->assertFalse($result); 53 | $result = $this->_adapter->error(); 54 | $this->assertEqual($expected, $result); 55 | } 56 | 57 | public function testCheckOverrideMessage() { 58 | $expected = [ 59 | 'denyAll' => 'Gwoo are not permitted to access this area.', 60 | ]; 61 | $result = $this->_adapter->check(['username' => 'Gwoo'], null, [ 62 | 'rules' => ['denyAll' => ['message' => $expected['denyAll']]] 63 | ]); 64 | $this->assertFalse($result); 65 | $result = $this->_adapter->error(); 66 | $this->assertEqual($expected, $result); 67 | } 68 | 69 | public function testCheckSimpleClosureOnTheFly() { 70 | $rules = [ 71 | function($user) { 72 | return $user['username'] === 'Nate'; 73 | } 74 | ]; 75 | $result = $this->_adapter->check(['username' => 'Nate'], null, [ 76 | 'rules' => $rules 77 | ]); 78 | $this->assertTrue($result); 79 | } 80 | 81 | public function testCheckClosureOnTheFly() { 82 | $rules = [ 83 | [ 84 | 'message' => 'Access denied.', 85 | 'rule' => function($user) { 86 | return $user['username'] === 'Nate'; 87 | } 88 | ] 89 | ]; 90 | $result = $this->_adapter->check(['username' => 'Nate'], null, [ 91 | 'rules' => $rules 92 | ]); 93 | $this->assertTrue($result); 94 | } 95 | 96 | public function testNoRules() { 97 | $this->expectException("Missing `'rules'` option."); 98 | $result = $this->_adapter->check([], null); 99 | } 100 | 101 | public function testInvalidRule() { 102 | $this->expectException('Invalid rule.'); 103 | $result = $this->_adapter->check([], null, ['rules' => 'invalid']); 104 | } 105 | 106 | public function testGetSetRules() { 107 | $this->_adapter->rules('testDeny', function() { return false;}, [ 108 | 'message' => 'Access denied.' 109 | ]); 110 | 111 | $expected = ['testDeny' => 'Access denied.']; 112 | $result = $this->_adapter->check(['username' => 'Tom'], null, [ 113 | 'rules' => 'testDeny' 114 | ]); 115 | $this->assertFalse($result); 116 | $result = $this->_adapter->error(); 117 | $this->assertEqual($expected, $result); 118 | 119 | $rule = $this->_adapter->rules('testDeny'); 120 | $this->assertTrue(is_callable($rule['rule'])); 121 | $this->assertTrue(is_array($this->_adapter->rules())); 122 | } 123 | 124 | public function testGlobalOptionsPassedToRule() { 125 | $adapter = new Rules([ 126 | 'rules' => [ 127 | 'foobar' => function($user, $request, $options) { 128 | return $options['foo'] === 'bar'; 129 | } 130 | ], 131 | 'defaults' => ['foobar'] 132 | ]); 133 | 134 | $result = $adapter->check(null, null, ['foo' => 'bar']); 135 | $this->assertTrue($result); 136 | 137 | $result = $adapter->check(null, null, ['foo' => 'baz']); 138 | $this->assertFalse($result); 139 | 140 | } 141 | 142 | public function testLocalOptionsPassedToRule() { 143 | $adapter = new Rules([ 144 | 'rules' => [ 145 | 'foobar' => function($user, $request, $options) { 146 | return $options['foo'] === 'bar'; 147 | } 148 | ], 149 | 'defaults' => ['foobar'] 150 | ]); 151 | 152 | $result = $adapter->check(null, null, ['rules' => ['foobar' => ['foo' => 'bar']]]); 153 | $this->assertTrue($result); 154 | 155 | $result = $adapter->check(null, null, ['rules' => ['foobar' => ['foo' => 'baz']]]); 156 | $this->assertFalse($result); 157 | 158 | } 159 | 160 | public function testGlobalAndLocalOptionsPassedToRule() { 161 | $adapter = new Rules([ 162 | 'rules' => [ 163 | 'foobar' => function($user, $request, $options) { 164 | return $options['foo'] === 'bar' && $options['bar'] === 'foo'; 165 | } 166 | ] 167 | ]); 168 | 169 | $result = $adapter->check(null, null, [ 170 | 'rules' => ['foobar' => ['foo' => 'bar']], 'bar' => 'foo' 171 | ]); 172 | $this->assertTrue($result); 173 | 174 | $result = $adapter->check(null, null, [ 175 | 'rules' => ['foobar' => ['foo' => 'bar']], 'bar' => 'fox' 176 | ]); 177 | $this->assertFalse($result); 178 | 179 | } 180 | 181 | public function testAutoUser() { 182 | $user = ['username' => 'Mehlah']; 183 | $adapter = new Rules([ 184 | 'rules' => [ 185 | 'isMehlah' => function($user, $request, $options) { 186 | return isset($user['username']) && $user['username'] == 'Mehlah'; 187 | } 188 | ], 189 | 'defaults' => ['isMehlah'], 190 | 'user' => function() use ($user) { return $user; } 191 | ]); 192 | 193 | $result = $adapter->check($user, null); 194 | $this->assertTrue($result); 195 | 196 | $result = $adapter->check(null, null); 197 | $this->assertTrue($result); 198 | 199 | $result = $adapter->check(['username' => 'Bob'], null); 200 | $this->assertFalse($result); 201 | 202 | $expected = ['isMehlah' => 'You are not permitted to access this area.']; 203 | $result = $adapter->error(); 204 | $this->assertEqual($expected, $result); 205 | } 206 | 207 | public function testAllowAny() { 208 | $rules = ['allowAll', 'allowAnyUser']; 209 | $allowAny = true; 210 | $result = $this->_adapter->check([], null, compact('rules', 'allowAny')); 211 | $this->assertTrue($result); 212 | 213 | $allowAny = false; 214 | $result = $this->_adapter->check([], null, compact('rules', 'allowAny')); 215 | $this->assertFalse($result); 216 | 217 | $rules = ['allowAnyUser', 'allowAll']; 218 | $allowAny = true; 219 | $result = $this->_adapter->check([], null, compact('rules', 'allowAny')); 220 | $this->assertTrue($result); 221 | 222 | $allowAny = false; 223 | $result = $this->_adapter->check([], null, compact('rules', 'allowAny')); 224 | $this->assertFalse($result); 225 | 226 | $rules = ['allowAnyUser']; 227 | $allowAny = true; 228 | $result = $this->_adapter->check([], null, compact('rules', 'allowAny')); 229 | $this->assertFalse($result); 230 | 231 | $allowAny = false; 232 | $result = $this->_adapter->check([], null, compact('rules', 'allowAny')); 233 | $this->assertFalse($result); 234 | 235 | $rules = ['allowAll']; 236 | $allowAny = true; 237 | $result = $this->_adapter->check([], null, compact('rules', 'allowAny')); 238 | $this->assertTrue($result); 239 | 240 | $allowAny = false; 241 | $result = $this->_adapter->check([], null, compact('rules', 'allowAny')); 242 | $this->assertTrue($result); 243 | } 244 | 245 | public function testPatternBasedIpMatching() { 246 | $request = new Request(['env' => ['REMOTE_ADDR' => '10.0.1.2']]); 247 | 248 | $rules = ['allowIp' => ['ip' => '/10\.0\.1\.\d+/']]; 249 | $result = $this->_adapter->check(null, $request, compact('rules')); 250 | $this->assertTrue($result); 251 | 252 | $request = new Request(['env' => ['REMOTE_ADDR' => '10.0.1.255']]); 253 | $result = $this->_adapter->check(null, $request, compact('rules')); 254 | $this->assertTrue($result); 255 | 256 | $request = new Request(['env' => ['REMOTE_ADDR' => '10.0.2.1']]); 257 | $result = $this->_adapter->check(null, $request, compact('rules')); 258 | $this->assertFalse($result); 259 | 260 | $result = $this->_adapter->error(); 261 | $this->assertEqual('Your IP is not allowed to access this area.', $result['allowIp']); 262 | } 263 | 264 | public function testArrayBasedIpMatching() { 265 | $rules = ['allowIp' => ['ip' => ['10.0.1.2', '10.0.1.3', '10.0.1.4']]]; 266 | 267 | foreach ([2, 3, 4] as $i) { 268 | $request = new Request(['env' => ['REMOTE_ADDR' => "10.0.1.{$i}"]]); 269 | $result = $this->_adapter->check(null, $request, compact('rules')); 270 | $this->assertTrue($result); 271 | } 272 | 273 | foreach ([1, 5, 255] as $i) { 274 | $request = new Request(['env' => ['REMOTE_ADDR' => "10.0.1.{$i}"]]); 275 | $result = $this->_adapter->check(null, $request, compact('rules')); 276 | $this->assertFalse($result); 277 | $result = $this->_adapter->error(); 278 | $this->assertEqual('Your IP is not allowed to access this area.', $result['allowIp']); 279 | } 280 | } 281 | } 282 | 283 | ?> 284 | -------------------------------------------------------------------------------- /tests/integration/security/access/model/db_acl/PermissionTest.php: -------------------------------------------------------------------------------- 1 | 'li3_access\tests\fixture\model\blog\User', 24 | '_group' => 'li3_access\tests\fixture\model\blog\Group' 25 | ]; 26 | 27 | protected $_fixtures = [ 28 | 'user' => 'li3_access\tests\fixture\source\blog\UserFixture', 29 | 'aco' => 'li3_access\tests\fixture\source\db_acl\AcoFixture', 30 | 'aro' => 'li3_access\tests\fixture\source\db_acl\AroFixture', 31 | 'permission' => 'li3_access\tests\fixture\source\db_acl\PermissionFixture' 32 | ]; 33 | 34 | protected $_privileges = ['create', 'read', 'update', 'delete']; 35 | 36 | /** 37 | * Skip the test if no test database connection available. 38 | */ 39 | public function skip() { 40 | $dbConfig = Connections::get($this->_connection, ['config' => true]); 41 | $isAvailable = ( 42 | $dbConfig && 43 | Connections::get($this->_connection)->isConnected(['autoConnect' => true]) 44 | ); 45 | $this->skipIf(!$isAvailable, "No {$this->_connection} connection available."); 46 | 47 | $db = Connections::get($this->_connection); 48 | 49 | $this->skipIf( 50 | !($db instanceof Database), 51 | "The {$this->_connection} connection is not a relational database." 52 | ); 53 | } 54 | 55 | public function setUp() { 56 | Fixtures::config([ 57 | 'db' => [ 58 | 'adapter' => 'Connection', 59 | 'connection' => $this->_connection, 60 | 'fixtures' => $this->_fixtures 61 | ] 62 | ]); 63 | } 64 | 65 | public function tearDown() { 66 | foreach($this->_models as $key => $class){ 67 | $class::reset(); 68 | } 69 | Fixtures::clear('db'); 70 | } 71 | 72 | public function testAcl() { 73 | Fixtures::save('db'); 74 | $result = Permission::acl('root/users/Peter', 'root/tpsReports/view/current'); 75 | $expected = [ 76 | 'aro' => '9', 77 | 'aco' => '4', 78 | 'acl' => [ 79 | 'id' => '17', 80 | 'aro_id' => '9', 81 | 'aco_id' => '4', 82 | 'privileges' => [ 83 | 'create' => '1', 84 | 'read' => '1', 85 | 'update' => '1', 86 | 'delete' => '0' 87 | ]]]; 88 | $this->assertEqual($expected, $result); 89 | } 90 | 91 | public function testCheck() { 92 | Fixtures::save('db'); 93 | 94 | $aro = 'root/users/Peter'; 95 | $aco = 'root/tpsReports/view/current'; 96 | $this->assertFalse(Permission::check($aro, $aco, $this->_privileges)); 97 | $this->assertTrue(Permission::check($aro, $aco, 'create')); 98 | $this->assertTrue(Permission::check($aro, $aco, 'read')); 99 | $this->assertTrue(Permission::check($aro, $aco, 'update')); 100 | $this->assertFalse(Permission::check($aro, $aco, 'delete')); 101 | 102 | $aro = 'root/users/Samantha'; 103 | $aco = 'root/printers/smash'; 104 | $this->assertFalse(Permission::check($aro, $aco, $this->_privileges)); 105 | $this->assertTrue(Permission::check($aro, $aco, 'create')); 106 | $this->assertTrue(Permission::check($aro, $aco, 'read')); 107 | $this->assertFalse(Permission::check($aro, $aco, 'update')); 108 | $this->assertTrue(Permission::check($aro, $aco, 'delete')); 109 | 110 | $this->assertTrue(Permission::check($aro, $aco, ['create', 'read', 'delete'])); 111 | 112 | $this->assertTrue(Permission::check('Samantha', 'print', 'read')); 113 | $this->assertTrue(Permission::check('Lumbergh', 'current', 'read')); 114 | $this->assertFalse(Permission::check('Milton', 'smash', 'read')); 115 | $this->assertFalse(Permission::check('Milton', 'current', 'update')); 116 | 117 | $this->assertTrue(Permission::check('Bob', 'root/tpsReports/view/current', 'read')); 118 | $this->assertFalse(Permission::check('Samantha', 'root/tpsReports/update', 'read')); 119 | 120 | $this->assertFalse(Permission::check('root/users/Milton', 'smash', 'delete')); 121 | 122 | $this->assertFalse(Permission::check(null, 'root/tpsReports/view/current', 'read')); 123 | $this->assertFalse(Permission::check('Bob', null, 'read')); 124 | $this->assertFalse(Permission::check('Invalid', 'tpsReports', 'read')); 125 | 126 | $this->assertFalse(Permission::check('Lumbergh', 'smash', 'foobar')); 127 | } 128 | 129 | public function testCheckOnUnexistingNode() { 130 | Fixtures::save('db'); 131 | 132 | $aro = 'root/users/Peter'; 133 | $aco = 'root/tpsReports/view/current/unexisting_node'; 134 | $this->assertFalse(Permission::check($aro, $aco, $this->_privileges)); 135 | $this->assertTrue(Permission::check($aro, $aco, 'create')); 136 | $this->assertTrue(Permission::check($aro, $aco, 'read')); 137 | $this->assertTrue(Permission::check($aro, $aco, 'update')); 138 | $this->assertFalse(Permission::check($aro, $aco, 'delete')); 139 | } 140 | 141 | function testGet() { 142 | Fixtures::save('db'); 143 | 144 | $aro = 'root/users/Samantha'; 145 | $aco = 'root/printers/smash'; 146 | $expected = ['create' => true, 'read' => true, 'update' => false, 'delete' => true]; 147 | $result = Permission::get($aro, $aco, $this->_privileges); 148 | $this->assertEqual($expected, $result); 149 | 150 | $aro = 'root/users/Peter'; 151 | $aco = 'root/tpsReports/view/current'; 152 | $expected = ['create' => true, 'read' => true, 'update' => true, 'delete' => false]; 153 | $result = Permission::get($aro, $aco, $this->_privileges); 154 | $this->assertEqual($expected, $result); 155 | } 156 | 157 | function testAliasAllow() { 158 | Fixtures::save('db'); 159 | 160 | $this->assertFalse(Permission::check('Micheal', 'tpsReports', ['read'])); 161 | $this->assertTrue(Permission::allow('Micheal', 'tpsReports', ['read', 'delete', 'update'])); 162 | $this->assertTrue(Permission::check('Micheal', 'tpsReports', ['update'])); 163 | $this->assertTrue(Permission::check('Micheal', 'tpsReports', ['read'])); 164 | $this->assertTrue(Permission::check('Micheal', 'tpsReports', ['delete'])); 165 | $this->assertFalse(Permission::check('Micheal', 'tpsReports', ['create'])); 166 | 167 | $this->assertTrue(Permission::allow('Micheal', 'root/tpsReports', ['create'])); 168 | $this->assertTrue(Permission::check('Micheal', 'tpsReports', ['create'])); 169 | $this->assertTrue(Permission::check('Micheal', 'tpsReports', ['delete'])); 170 | $this->assertTrue(Permission::allow('Micheal', 'printers', ['create'])); 171 | 172 | $this->assertTrue(Permission::check('Micheal', 'tpsReports', ['delete'])); 173 | $this->assertTrue(Permission::check('Micheal', 'printers', ['create'])); 174 | 175 | $this->assertFalse(Permission::check('root/users/Samantha', 'root/tpsReports/view', $this->_privileges)); 176 | $this->assertTrue(Permission::allow('root/users/Samantha', 'root/tpsReports/view', $this->_privileges)); 177 | $this->assertTrue(Permission::check('Samantha', 'view', 'read')); 178 | $this->assertTrue(Permission::check('root/users/Samantha', 'root/tpsReports/view', 'update')); 179 | 180 | $this->assertFalse(Permission::check('root/users/Samantha', 'root/tpsReports/update', $this->_privileges)); 181 | $this->assertTrue(Permission::allow('root/users/Samantha', 'root/tpsReports/update', $this->_privileges)); 182 | $this->assertTrue(Permission::check('Samantha', 'update', 'read')); 183 | $this->assertTrue(Permission::check('root/users/Samantha', 'root/tpsReports/update', ['update'])); 184 | $this->assertTrue(Permission::check('root/users/Samantha', 'root/tpsReports/view', ['update'])); 185 | 186 | $this->expectException('/Invalid acl node./'); 187 | $this->assertFalse(Permission::allow('Lumbergh', 'root/tpsReports/DoesNotExist', 'create')); 188 | } 189 | 190 | function testArrayAllow() { 191 | Fixtures::save('db'); 192 | extract($this->_models); 193 | $micheal = [ 194 | 'class' => $_user, 195 | 'id' => 4 196 | ]; 197 | $this->assertFalse(Permission::check($micheal, 'tpsReports', ['read'])); 198 | $this->assertTrue(Permission::allow($micheal, 'tpsReports', ['read', 'delete', 'update'])); 199 | $this->assertTrue(Permission::check($micheal, 'tpsReports', ['update'])); 200 | $this->assertTrue(Permission::check($micheal, 'tpsReports', ['read'])); 201 | $this->assertTrue(Permission::check($micheal, 'tpsReports', ['delete'])); 202 | $this->assertFalse(Permission::check($micheal, 'tpsReports', ['create'])); 203 | 204 | $this->assertTrue(Permission::allow($micheal, 'root/tpsReports', ['create'])); 205 | $this->assertTrue(Permission::check($micheal, 'tpsReports', ['create'])); 206 | $this->assertTrue(Permission::check($micheal, 'tpsReports', ['delete'])); 207 | $this->assertTrue(Permission::allow($micheal, 'printers', ['create'])); 208 | 209 | $this->assertTrue(Permission::check($micheal, 'tpsReports', ['delete'])); 210 | $this->assertTrue(Permission::check($micheal, 'printers', ['create'])); 211 | 212 | $samantha = [ 213 | 'class' => $_user, 214 | 'id' => 3 215 | ]; 216 | $this->assertFalse(Permission::check($samantha, 'root/tpsReports/view', $this->_privileges)); 217 | $this->assertTrue(Permission::allow($samantha, 'root/tpsReports/view', $this->_privileges)); 218 | $this->assertTrue(Permission::check($samantha, 'view', 'read')); 219 | $this->assertTrue(Permission::check($samantha, 'root/tpsReports/view', 'update')); 220 | 221 | $this->assertFalse(Permission::check($samantha, 'root/tpsReports/update', $this->_privileges)); 222 | $this->assertTrue(Permission::allow($samantha, 'root/tpsReports/update', $this->_privileges)); 223 | $this->assertTrue(Permission::check($samantha, 'update', 'read')); 224 | $this->assertTrue(Permission::check($samantha, 'root/tpsReports/update', ['update'])); 225 | $this->assertTrue(Permission::check($samantha, 'root/tpsReports/view', ['update'])); 226 | 227 | $this->expectException('/Invalid acl node./'); 228 | $this->assertFalse(Permission::allow('Lumbergh', 'root/tpsReports/DoesNotExist', 'create')); 229 | $_user::reset(); 230 | } 231 | 232 | function testEntityAllow() { 233 | Fixtures::save('db'); 234 | extract($this->_models); 235 | $micheal = $_user::create(); 236 | $micheal->id = 4; 237 | 238 | $this->assertFalse(Permission::check($micheal, 'tpsReports', ['read'])); 239 | $this->assertTrue(Permission::allow($micheal, 'tpsReports', ['read', 'delete', 'update'])); 240 | $this->assertTrue(Permission::check($micheal, 'tpsReports', ['update'])); 241 | $this->assertTrue(Permission::check($micheal, 'tpsReports', ['read'])); 242 | $this->assertTrue(Permission::check($micheal, 'tpsReports', ['delete'])); 243 | $this->assertFalse(Permission::check($micheal, 'tpsReports', ['create'])); 244 | 245 | $this->assertTrue(Permission::allow($micheal, 'root/tpsReports', ['create'])); 246 | $this->assertTrue(Permission::check($micheal, 'tpsReports', ['create'])); 247 | $this->assertTrue(Permission::check($micheal, 'tpsReports', ['delete'])); 248 | $this->assertTrue(Permission::allow($micheal, 'printers', ['create'])); 249 | 250 | $this->assertTrue(Permission::check($micheal, 'tpsReports', ['delete'])); 251 | $this->assertTrue(Permission::check($micheal, 'printers', ['create'])); 252 | 253 | $samantha = $_user::create(); 254 | $samantha->id = 3; 255 | $this->assertFalse(Permission::check($samantha, 'root/tpsReports/view', $this->_privileges)); 256 | $this->assertTrue(Permission::allow($samantha, 'root/tpsReports/view', $this->_privileges)); 257 | $this->assertTrue(Permission::check($samantha, 'view', 'read')); 258 | $this->assertTrue(Permission::check($samantha, 'root/tpsReports/view', 'update')); 259 | 260 | $this->assertFalse(Permission::check($samantha, 'root/tpsReports/update', $this->_privileges)); 261 | $this->assertTrue(Permission::allow($samantha, 'root/tpsReports/update', $this->_privileges)); 262 | $this->assertTrue(Permission::check($samantha, 'update', 'read')); 263 | $this->assertTrue(Permission::check($samantha, 'root/tpsReports/update', ['update'])); 264 | $this->assertTrue(Permission::check($samantha, 'root/tpsReports/view', ['update'])); 265 | 266 | $this->expectException('/Invalid acl node./'); 267 | $this->assertFalse(Permission::allow('Lumbergh', 'root/tpsReports/DoesNotExist', 'create')); 268 | $_user::reset(); 269 | } 270 | 271 | function testInherit() { 272 | Fixtures::save('db'); 273 | 274 | $this->assertFalse(Permission::check('Milton', 'smash', 'delete')); 275 | Permission::inherit('Milton', 'smash', 'delete'); 276 | $this->assertFalse(Permission::check('Milton', 'smash', 'delete')); 277 | 278 | $this->assertFalse(Permission::check('Milton', 'smash', 'read')); 279 | Permission::inherit('Milton', 'smash', 'read'); 280 | $this->assertTrue(Permission::check('Milton', 'smash', 'read')); 281 | } 282 | 283 | function testDeny() { 284 | Fixtures::save('db'); 285 | 286 | $this->assertTrue(Permission::check('Micheal', 'smash', 'delete')); 287 | Permission::deny('Micheal', 'smash', 'delete'); 288 | $this->assertFalse(Permission::check('Micheal', 'smash', 'delete')); 289 | $this->assertTrue(Permission::check('Micheal', 'smash', 'read')); 290 | $this->assertTrue(Permission::check('Micheal', 'smash', 'create')); 291 | $this->assertTrue(Permission::check('Micheal', 'smash', 'update')); 292 | $this->assertFalse(Permission::check('Micheal', 'smash', $this->_privileges)); 293 | 294 | $this->assertTrue(Permission::check('Samantha', 'refill', $this->_privileges)); 295 | Permission::deny('Samantha', 'refill', $this->_privileges); 296 | $this->assertFalse(Permission::check('Samantha', 'refill', 'create')); 297 | $this->assertFalse(Permission::check('Samantha', 'refill', 'update')); 298 | $this->assertFalse(Permission::check('Samantha', 'refill', 'read')); 299 | $this->assertFalse(Permission::check('Samantha', 'refill', 'delete')); 300 | 301 | $this->expectException('/Invalid acl node./'); 302 | $this->assertFalse(Permission::deny('Lumbergh', 'root/tpsReports/DoesNotExist', 'create')); 303 | } 304 | 305 | /** 306 | * Setup the acl permissions such that Bob inherits from admin. 307 | * deny Admin delete access to a specific resource, check the permisssions are inherited. 308 | */ 309 | function testCascadingDeny() { 310 | Fixtures::save('db'); 311 | 312 | Permission::inherit('Bob', 'root', $this->_privileges); 313 | $this->assertTrue(Permission::check('admin', 'tpsReports', 'delete')); 314 | $this->assertTrue(Permission::check('Bob', 'tpsReports', 'delete')); 315 | Permission::deny('admin', 'tpsReports', 'delete'); 316 | $this->assertFalse(Permission::check('admin', 'tpsReports', 'delete')); 317 | $this->assertFalse(Permission::check('Bob', 'tpsReports', 'delete')); 318 | } 319 | 320 | public function testAllowOnTheFlyPrivilege() { 321 | Fixtures::save('db'); 322 | 323 | $this->assertFalse(Permission::check('Micheal', 'tpsReports', ['publish'])); 324 | $this->assertTrue(Permission::allow('Micheal', 'tpsReports', ['publish'])); 325 | $this->assertTrue(Permission::check('Micheal', 'tpsReports', ['publish'])); 326 | $this->assertTrue(Permission::check('Micheal', 'root/tpsReports', ['publish'])); 327 | $this->assertTrue(Permission::check('Micheal', 'root/tpsReports/update', ['publish'])); 328 | } 329 | 330 | /** 331 | * debug function - to help editing/creating test cases for the ACL component 332 | * 333 | * To check the overall ACL status at any time call $this->__debug(); 334 | * Generates a list of the current aro and aco structures and a grid dump of the permissions that are defined 335 | * Only designed to work with the db based ACL 336 | * 337 | * @param bool $treesToo 338 | * @return void 339 | */ 340 | protected function _debug($printTreesToo = false) { 341 | Aro::meta('title', 'alias'); 342 | Aco::meta('title', 'alias'); 343 | $aros = Aro::find('list', ['order' => 'lft']); 344 | $acos = Aco::find('list', ['order' => 'lft']); 345 | $rights = [$this->_privileges, 'create', 'read', 'update', 'delete']; 346 | $permissions['Aros v Acos >'] = $acos; 347 | foreach ($aros as $aro) { 348 | $row = []; 349 | foreach ($acos as $aco) { 350 | $perms = ''; 351 | foreach ($rights as $right) { 352 | if (Permission::check($aro, $aco, $right)) { 353 | if ($right == $this->_privileges) { 354 | $perms .= '****'; 355 | break; 356 | } 357 | $perms .= $right[0]; 358 | } elseif ($right != $this->_privileges) { 359 | $perms .= ' '; 360 | } 361 | } 362 | $row[] = $perms; 363 | } 364 | $permissions[$aro] = $row; 365 | } 366 | foreach ($permissions as $key => $values) { 367 | array_unshift($values, $key); 368 | $values = array_map([&$this, '_pad'], $values); 369 | $permissions[$key] = implode (' ', $values); 370 | } 371 | $permissions = array_map([&$this, '_pad'], $permissions); 372 | array_unshift($permissions, 'Current Permissions :'); 373 | print_r(implode("\r\n", $permissions)); 374 | } 375 | 376 | /** 377 | * pad function 378 | * Used by debug to format strings used in the data dump 379 | * 380 | * @param string $string 381 | * @param integer $len 382 | * @return void 383 | */ 384 | protected function _pad($string = '', $len = 14) { 385 | return str_pad($string, $len); 386 | } 387 | } 388 | --------------------------------------------------------------------------------