diff --git a/Docs/Documentation/SimpleRbacAuthorize.md b/Docs/Documentation/SimpleRbacAuthorize.md index fbcda06e1..8a45eac61 100644 --- a/Docs/Documentation/SimpleRbacAuthorize.md +++ b/Docs/Documentation/SimpleRbacAuthorize.md @@ -52,25 +52,42 @@ The ```default_role``` will be used to set the role of the registered users by d Permission rules syntax ----------------- -* Rules are evaluated top-down, first matching rule will apply -* Each rule is defined: -```php -[ - 'role' => 'REQUIRED_NAME_OF_THE_ROLE_OR_[]_OR_*', - 'prefix' => 'OPTIONAL_PREFIX_USED_OR_[]_OR_*_DEFAULT_NULL', - 'extension' => 'OPTIONAL_PREFIX_USED_OR_[]_OR_*_DEFAULT_NULL', - 'plugin' => 'OPTIONAL_NAME_OF_THE_PLUGIN_OR_[]_OR_*_DEFAULT_NULL', - 'controller' => 'REQUIRED_NAME_OF_THE_CONTROLLER_OR_[]_OR_*' - 'action' => 'REQUIRED_NAME_OF_ACTION_OR_[]_OR_*', - 'allowed' => 'OPTIONAL_BOOLEAN_OR_CALLABLE_OR_INSTANCE_OF_RULE_DEFAULT_TRUE' -] -``` -* If no rule allowed = true is matched for a given user role and url, default return value will be false -* Note for Superadmin access (permission to access ALL THE THINGS in your app) there is a specific Authorize Object provided +* Permissions are evaluated top-down, first matching permission will apply +* Each permission is an associative array of rules with following structure: `'value_to_check' => 'expected_value'` +* `value_to_check` can be any key from user array or one of special keys: + * Routing params: + * `prefix` + * `plugin` + * `extension` + * `controller` + * `action` + * `role` - Alias/shortcut to field defined in `role_field` config value + * `allowed` - see below +* If you have a user field that overlaps with special keys (eg. `$user->allowed`) you can prepend `user.` to key to force matching from user array (eg. `user.allowed`) +* The keys can be placed in any order with exception of `allowed` which must be last one (see below) +* `value_to_check` can be prepended with `*` to match everything except `expected_value` +* `expected_value` can be one of following things: + * `*` will match absolutely everything + * A _string_/_integer_/_boolean_/etc - will match only the specified value + * An _array_ of strings/integers/booleans/etc (can be mixed). The rule will match if real value is equal to any of expected ones + * A callable/object (see below) +* If any of rules fail, the permission is discarded and the next one is evaluated +* A very special key `allowed` exists which has completely different behaviour: + * If `expected_value` is a callable/object then it's executed and the result is casted to boolean + * If `expected_value` is **not** a callable/object then it's simply casted to boolean + * The `*` is checked and if found the result is inverted + * The final boolean value is **the result of permission** checker. This means if it is `false` then no other permissions are checked and the user is denied access. + For this reason the `allowed` key must be placed at the end of permission since no other rules are executed after it -Permission Callbacks ------------------ -You could use a callback in your 'allowed' to process complex authentication, like +**Notes**: + +* For Superadmin access (permission to access ALL THE THINGS in your app) there is a specific Authorize Object provided +* Permissions that do not have `controller` and/or `action` keys (or the inverted versions) are automatically discarded in order to prevent errors. +If you need to match all controllers/actions you can explicitly do `'contoller' => '*'` +* Key `user` (or the inverted version) is illegal (as it's impossible to match an array) and any permission containing it will be discarded +* If the permission is discarded for the reasons stated above, a debug message will be logged + +**Permission Callbacks**: you could use a callback in your 'allowed' to process complex authentication, like - ownership - permissions stored in your database - permission based on an external service API call @@ -78,10 +95,10 @@ You could use a callback in your 'allowed' to process complex authentication, li Example *ownership* callback, to allow users to edit their own Posts: ```php - 'allowed' => function (array $user, $role, Request $request) { - $postId = Hash::get($request->params, 'pass.0'); - $post = TableRegistry::get('Posts')->get($postId); - $userId = Hash::get($user, 'id'); + 'allowed' => function (array $user, $role, \Cake\Network\Request $request) { + $postId = \Cake\Utility\Hash::get($request->params, 'pass.0'); + $post = \Cake\ORM\TableRegistry::get('Posts')->get($postId); + $userId = $user['id']; if (!empty($post->user_id) && !empty($userId)) { return $post->user_id === $userId; } @@ -89,12 +106,36 @@ Example *ownership* callback, to allow users to edit their own Posts: } ``` -Permission Rules ----------------- -You could use an instance of the \CakeDC\Users\Auth\Rules\Rule interface to reuse your custom rules -Examples: +**Permission Rules**: If you see that you are duplicating logic in your callables, you can create rule class to re-use the logic. +For example, the above ownership callback is included in CakeDC\Users as `Owner` rule ```php -'allowed' => new Owner() //will pick by default the post id from the first pass param +'allowed' => new \CakeDC\Users\Auth\Rules\Owner() //will pick by default the post id from the first pass param ``` Check the [Owner Rule](OwnerRule.md) documentation for more details +Creating rule classes +--------------------- + +The only requirement is to implement `\CakeDC\Users\Auth\Rules\Rule` interface which has one method: + +```php +class YourRule implements \CakeDC\Users\Auth\Rules\Rule +{ + /** + * Check the current entity is owned by the logged in user + * + * @param array $user Auth array with the logged in data + * @param string $role role of the user + * @param Request $request current request, used to get a default table if not provided + * @return bool + */ + public function allowed(array $user, $role, Request $request) + { + // Your logic here + } +} +``` + +This logic can be anything: database, external auth, etc. + +Also, if you are using DB, you can choose to extend `\CakeDC\Users\Auth\Rules\AbstractRule` since it provides convenience methods for reading from DB \ No newline at end of file diff --git a/src/Auth/Rules/AbstractRule.php b/src/Auth/Rules/AbstractRule.php index 17dacc8a5..0517e3d71 100644 --- a/src/Auth/Rules/AbstractRule.php +++ b/src/Auth/Rules/AbstractRule.php @@ -10,7 +10,10 @@ */ namespace CakeDC\Users\Auth\Rules; +use Cake\Core\InstanceConfigTrait; +use Cake\Datasource\ModelAwareTrait; use Cake\Network\Request; +use Cake\ORM\Locator\LocatorAwareTrait; use Cake\ORM\Table; use Cake\ORM\TableRegistry; use Cake\Utility\Hash; @@ -22,9 +25,9 @@ */ abstract class AbstractRule implements Rule { - use \Cake\Core\InstanceConfigTrait; - use \Cake\Datasource\ModelAwareTrait; - use \Cake\ORM\Locator\LocatorAwareTrait; + use InstanceConfigTrait; + use LocatorAwareTrait; + use ModelAwareTrait; /** * @var array default config @@ -46,7 +49,7 @@ public function __construct($config = []) * @param Request $request request * @param mixed $table table * @return \Cake\ORM\Table - * @throw OutOfBoundsException if table alias is empty + * @throws \OutOfBoundsException if table alias is empty */ protected function _getTable(Request $request, $table = null) { @@ -65,7 +68,7 @@ protected function _getTable(Request $request, $table = null) * * @param Request $request request * @return Table - * @throws OutOfBoundsException if table alias can't be extracted from request + * @throws \OutOfBoundsException if table alias can't be extracted from request */ protected function _getTableFromRequest(Request $request) { @@ -88,7 +91,7 @@ protected function _getTableFromRequest(Request $request) * @param string $role role of the user * @param Request $request current request, used to get a default table if not provided * @return bool - * @throws OutOfBoundsException if table is not found or it doesn't have the expected fields + * @throws \OutOfBoundsException if table is not found or it doesn't have the expected fields */ abstract public function allowed(array $user, $role, Request $request); } diff --git a/src/Auth/Rules/Rule.php b/src/Auth/Rules/Rule.php index 7ba6c438d..9acd999d3 100644 --- a/src/Auth/Rules/Rule.php +++ b/src/Auth/Rules/Rule.php @@ -21,7 +21,6 @@ interface Rule * @param string $role role of the user * @param Request $request current request, used to get a default table if not provided * @return bool - * @throws \OutOfBoundsException if table is not found or it doesn't have the expected fields */ public function allowed(array $user, $role, Request $request); } diff --git a/src/Auth/SimpleRbacAuthorize.php b/src/Auth/SimpleRbacAuthorize.php index d7e022e9f..120bdeaa7 100644 --- a/src/Auth/SimpleRbacAuthorize.php +++ b/src/Auth/SimpleRbacAuthorize.php @@ -120,7 +120,7 @@ public function __construct(ComponentRegistry $registry, array $config = []) parent::__construct($registry, $config); $autoload = $this->config('autoload_config'); if ($autoload) { - $loadedPermissions = $this->_loadPermissions($autoload, 'default'); + $loadedPermissions = $this->_loadPermissions($autoload); $this->config('permissions', $loadedPermissions); } } @@ -166,7 +166,7 @@ public function authorize($user, Request $request) $role = Hash::get($user, $roleField); } - $allowed = $this->_checkRules($user, $role, $request); + $allowed = $this->_checkPermissions($user, $role, $request); return $allowed; } @@ -180,11 +180,11 @@ public function authorize($user, Request $request) * @param Request $request request * @return bool true if there is a match in permissions */ - protected function _checkRules(array $user, $role, Request $request) + protected function _checkPermissions(array $user, $role, Request $request) { $permissions = $this->config('permissions'); foreach ($permissions as $permission) { - $allowed = $this->_matchRule($permission, $user, $role, $request); + $allowed = $this->_matchPermission($permission, $user, $role, $request); if ($allowed !== null) { return $allowed; } @@ -196,42 +196,77 @@ protected function _checkRules(array $user, $role, Request $request) /** * Match the rule for current permission * - * @param array $permission configuration - * @param array $user current user - * @param string $role effective user role - * @param Request $request request - * @return bool if rule matched, null if rule not matched + * @param array $permission The permission configuration + * @param array $user Current user data + * @param string $role Effective user's role + * @param \Cake\Network\Request $request Current request + * + * @return null|bool Null if permission is discarded, boolean if a final result is produced */ - protected function _matchRule($permission, $user, $role, $request) + protected function _matchPermission(array $permission, array $user, $role, Request $request) { - $plugin = $request->plugin; - $controller = $request->controller; - $action = $request->action; - $prefix = null; - $extension = null; - if (!empty($request->params['prefix'])) { - $prefix = $request->params['prefix']; + $issetController = isset($permission['controller']) || isset($permission['*controller']); + $issetAction = isset($permission['action']) || isset($permission['*action']); + $issetUser = isset($permission['user']) || isset($permission['*user']); + + if (!$issetController || !$issetAction) { + $this->log( + __d('CakeDC/Users', "Cannot evaluate permission when 'controller' and/or 'action' keys are absent"), + LogLevel::DEBUG + ); + + return false; } - if (!empty($request->params['_ext'])) { - $extension = $request->params['_ext']; + if ($issetUser) { + $this->log( + __d('CakeDC/Users', "Permission key 'user' is illegal, cannot evaluate the permission"), + LogLevel::DEBUG + ); + + return false; } - if ($this->_matchOrAsterisk($permission, 'role', $role) && - $this->_matchOrAsterisk($permission, 'prefix', $prefix, true) && - $this->_matchOrAsterisk($permission, 'plugin', $plugin, true) && - $this->_matchOrAsterisk($permission, 'extension', $extension, true) && - $this->_matchOrAsterisk($permission, 'controller', $controller) && - $this->_matchOrAsterisk($permission, 'action', $action)) { - $allowed = Hash::get($permission, 'allowed'); - if ($allowed === null) { - //allowed will be true by default - return true; - } elseif (is_callable($allowed)) { - return (bool)call_user_func($allowed, $user, $role, $request); - } elseif ($allowed instanceof Rule) { - return (bool)$allowed->allowed($user, $role, $request); + $permission += ['allowed' => true]; + $userArr = ['user' => $user]; + $reserved = [ + 'prefix' => Hash::get($request->params, 'prefix'), + 'plugin' => $request->plugin, + 'extension' => Hash::get($request->params, '_ext'), + 'controller' => $request->controller, + 'action' => $request->action, + 'role' => $role + ]; + + foreach ($permission as $key => $value) { + $inverse = $this->_startsWith($key, '*'); + if ($inverse) { + $key = ltrim($key, '*'); + } + + if (is_callable($value)) { + $return = (bool)call_user_func($value, $user, $role, $request); + } elseif ($value instanceof Rule) { + $return = (bool)$value->allowed($user, $role, $request); + } elseif ($key === 'allowed') { + $return = (bool)$value; + } elseif (array_key_exists($key, $reserved)) { + $return = $this->_matchOrAsterisk($value, $reserved[$key], true); } else { - return (bool)$allowed; + if (!$this->_startsWith($key, 'user.')) { + $key = 'user.' . $key; + } + + $return = $this->_matchOrAsterisk($value, Hash::get($userArr, $key)); + } + + if ($inverse) { + $return = !$return; + } + if ($key === 'allowed') { + return $return; + } + if (!$return) { + break; } } @@ -241,24 +276,42 @@ protected function _matchRule($permission, $user, $role, $request) /** * Check if rule matched or '*' present in rule matching anything * - * @param string $permission permission configuration - * @param string $key key to retrieve and check in permissions configuration - * @param string $value value to check with (coming from the request) We'll check the DASHERIZED value too - * @param bool $allowEmpty true if we allow + * @param string|array $possibleValues Values that are accepted (from permission config) + * @param string|mixed|null $value Value to check with. We'll check the DASHERIZED value too + * @param bool $allowEmpty If true and $value is null, the rule will pass + * * @return bool */ - protected function _matchOrAsterisk($permission, $key, $value, $allowEmpty = false) + protected function _matchOrAsterisk($possibleValues, $value, $allowEmpty = false) { - $possibleValues = (array)Hash::get($permission, $key); - if ($allowEmpty && empty($possibleValues) && $value === null) { + $possibleArray = (array)$possibleValues; + + if ($allowEmpty && empty($possibleArray) && $value === null) { return true; } - if (Hash::get($permission, $key) === '*' || - in_array($value, $possibleValues) || - in_array(Inflector::camelize($value, '-'), $possibleValues)) { + + if ($possibleValues === '*' || + in_array($value, $possibleArray) || + in_array(Inflector::camelize($value, '-'), $possibleArray) + ) { return true; } return false; } + + /** + * Checks if $heystack begins with $needle + * + * @see http://stackoverflow.com/a/7168986/2588539 + * + * @param string $haystack The whole string + * @param string $needle The beginning to check + * + * @return bool + */ + protected function _startsWith($haystack, $needle) + { + return substr($haystack, 0, strlen($needle)) === $needle; + } } diff --git a/tests/TestCase/Auth/SimpleRbacAuthorizeTest.php b/tests/TestCase/Auth/SimpleRbacAuthorizeTest.php index 205064230..0d4eb865b 100644 --- a/tests/TestCase/Auth/SimpleRbacAuthorizeTest.php +++ b/tests/TestCase/Auth/SimpleRbacAuthorizeTest.php @@ -14,12 +14,11 @@ use CakeDC\Users\Auth\Rules\Rule; use CakeDC\Users\Auth\SimpleRbacAuthorize; use Cake\Controller\ComponentRegistry; -use Cake\Controller\Controller; -use Cake\Event\EventManager; use Cake\Network\Request; use Cake\Network\Response; use Cake\TestSuite\TestCase; use Cake\Utility\Hash; +use Psr\Log\LogLevel; use ReflectionClass; class SimpleRbacAuthorizeTest extends TestCase @@ -98,8 +97,8 @@ public function testConstruct() public function testLoadPermissions() { $this->simpleRbacAuthorize = $this->getMockBuilder('CakeDC\Users\Auth\SimpleRbacAuthorize') - ->disableOriginalConstructor() - ->getMock(); + ->disableOriginalConstructor() + ->getMock(); $reflectedClass = new ReflectionClass($this->simpleRbacAuthorize); $loadPermissions = $reflectedClass->getMethod('_loadPermissions'); $loadPermissions->setAccessible(true); @@ -136,20 +135,22 @@ protected function assertConstructorPermissions($instance, $config, $permissions */ public function testConstructPermissionsFileHappy() { - $permissions = [[ - 'controller' => 'Test', - 'action' => 'test' - ]]; + $permissions = [ + [ + 'controller' => 'Test', + 'action' => 'test' + ] + ]; $className = 'CakeDC\Users\Auth\SimpleRbacAuthorize'; $this->simpleRbacAuthorize = $this->getMockBuilder($className) - ->setMethods(['_loadPermissions']) - ->disableOriginalConstructor() - ->getMock(); + ->setMethods(['_loadPermissions']) + ->disableOriginalConstructor() + ->getMock(); $this->simpleRbacAuthorize - ->expects($this->once()) - ->method('_loadPermissions') - ->with('permissions-happy') - ->will($this->returnValue($permissions)); + ->expects($this->once()) + ->method('_loadPermissions') + ->with('permissions-happy') + ->will($this->returnValue($permissions)); $this->assertConstructorPermissions($className, ['autoload_config' => 'permissions-happy'], $permissions); } @@ -157,9 +158,9 @@ protected function preparePermissions($permissions) { $className = 'CakeDC\Users\Auth\SimpleRbacAuthorize'; $simpleRbacAuthorize = $this->getMockBuilder($className) - ->setMethods(['_loadPermissions']) - ->disableOriginalConstructor() - ->getMock(); + ->setMethods(['_loadPermissions']) + ->disableOriginalConstructor() + ->getMock(); $simpleRbacAuthorize->config('permissions', $permissions); return $simpleRbacAuthorize; @@ -171,19 +172,7 @@ protected function preparePermissions($permissions) public function testAuthorize($permissions, $user, $requestParams, $expected, $msg = null) { $this->simpleRbacAuthorize = $this->preparePermissions($permissions); - $request = new Request(); - $request->plugin = Hash::get($requestParams, 'plugin'); - $request->controller = $requestParams['controller']; - $request->action = $requestParams['action']; - $prefix = Hash::get($requestParams, 'prefix'); - $request->params = []; - if ($prefix) { - $request->params['prefix'] = $prefix; - } - $extension = Hash::get($requestParams, '_ext'); - if ($extension) { - $request->params['_ext'] = $extension; - } + $request = $this->_requestFromArray($requestParams); $result = $this->simpleRbacAuthorize->authorize($user, $request); $this->assertSame($expected, $result, $msg); @@ -199,6 +188,209 @@ public function providerAuthorize() ->willReturn(true); return [ + 'discard-first' => [ + //permissions + [ + [ + 'role' => 'test', + 'controller' => 'Tests', + 'action' => 'three', // Discard here + function () { + throw new \Exception(); + } + ], + [ + 'plugin' => ['Tests'], + 'role' => ['test'], + 'controller' => ['Tests'], + 'action' => ['one', 'two'], + ], + ], + //user + [ + 'id' => 1, + 'username' => 'luke', + 'role' => 'test', + ], + //request + [ + 'plugin' => 'Tests', + 'controller' => 'Tests', + 'action' => 'one' + ], + //expected + true + ], + 'deny-first-discard-after' => [ + //permissions + [ + [ + 'role' => 'test', + 'controller' => 'Tests', + 'action' => 'one', + 'allowed' => function () { + return false; // Deny here since under 'allowed' key + } + ], + [ + // This permission isn't evaluated + function () { + throw new \Exception(); + }, + 'plugin' => ['Tests'], + 'role' => ['test'], + 'controller' => ['Tests'], + 'action' => ['one', 'two'], + ], + ], + //user + [ + 'id' => 1, + 'username' => 'luke', + 'role' => 'test', + ], + //request + [ + 'plugin' => 'Tests', + 'controller' => 'Tests', + 'action' => 'one' + ], + //expected + false + ], + 'star-invert' => [ + //permissions + [[ + '*plugin' => 'Tests', + '*role' => 'test', + '*controller' => 'Tests', + '*action' => 'test', + ]], + //user + [ + 'id' => 1, + 'username' => 'luke', + 'role' => 'something', + ], + //request + [ + 'plugin' => 'something', + 'controller' => 'something', + 'action' => 'something' + ], + //expected + true + ], + 'star-invert-deny' => [ + //permissions + [[ + '*plugin' => 'Tests', + '*role' => 'test', + '*controller' => 'Tests', + '*action' => 'test', + ]], + //user + [ + 'id' => 1, + 'username' => 'luke', + 'role' => 'something', + ], + //request + [ + 'plugin' => 'something', + 'controller' => 'something', + 'action' => 'test' + ], + //expected + false + ], + 'user-arr' => [ + //permissions + [ + [ + 'username' => 'luke', + 'user.id' => 1, + 'profile.id' => 256, + 'user.profile.signature' => "Hi I'm luke", + 'user.allowed' => false, + 'controller' => 'Tests', + 'action' => 'one' + ], + ], + //user + [ + 'id' => 1, + 'username' => 'luke', + 'role' => 'test', + 'profile' => [ + 'id' => 256, + 'signature' => "Hi I'm luke" + ], + 'allowed' => false + ], + //request + [ + 'controller' => 'Tests', + 'action' => 'one' + ], + //expected + true + ], + 'evaluate-order' => [ + //permissions + [ + [ + 'allowed' => false, + function () { + throw new \Exception(); + }, + 'controller' => 'Tests', + 'action' => 'one' + ], + ], + //user + [ + 'id' => 1, + 'username' => 'luke', + 'role' => 'test', + ], + //request + [ + 'controller' => 'Tests', + 'action' => 'one' + ], + //expected + false + ], + 'multiple-callables' => [ + //permissions + [ + [ + function () { + return true; + }, + clone $trueRuleMock, + function () { + return true; + }, + 'controller' => 'Tests', + 'action' => 'one' + ], + ], + //user + [ + 'id' => 1, + 'username' => 'luke', + 'role' => 'test', + ], + //request + [ + 'controller' => 'Tests', + 'action' => 'one' + ], + //expected + true + ], 'happy-strict-all' => [ //permissions [[ @@ -501,7 +693,7 @@ public function providerAuthorize() //expected true ], - 'happy-array' => [ + 'happy-array-deny' => [ //permissions [[ 'plugin' => ['Tests'], @@ -667,23 +859,15 @@ public function providerAuthorize() //expected true ], - 'array-prefix' => [ + 'array-prefix-deny' => [ //permissions - [ - [ - 'role' => ['test'], - 'prefix' => ['one', 'admin'], - 'controller' => '*', - 'action' => 'one', - 'allowed' => false, - ], - [ - 'role' => ['test'], - 'prefix' => ['one', 'admin'], - 'controller' => '*', - 'action' => '*', - ], - ], + [[ + 'role' => ['test'], + 'prefix' => ['one', 'admin'], + 'controller' => '*', + 'action' => 'one', + 'allowed' => false, + ]], //user [ 'id' => 1, @@ -796,23 +980,15 @@ public function providerAuthorize() //expected true ], - 'array-ext' => [ + 'array-ext-deny' => [ //permissions - [ - [ - 'role' => ['test'], - 'extension' => ['csv', 'docx'], - 'controller' => '*', - 'action' => 'one', - 'allowed' => false, - ], - [ - 'role' => ['test'], - 'extension' => ['csv', 'docx'], - 'controller' => '*', - 'action' => '*', - ], - ], + [[ + 'role' => ['test'], + 'extension' => ['csv', 'docx'], + 'controller' => '*', + 'action' => 'one', + 'allowed' => false, + ]], //user [ 'id' => 1, @@ -854,4 +1030,180 @@ public function providerAuthorize() ], ]; } + + /** + * @dataProvider badPermissionProvider + * + * @param array $permissions + * @param array $user + * @param array $requestParams + * @param string $expectedMsg + */ + public function testBadPermission($permissions, $user, $requestParams, $expectedMsg) + { + $simpleRbacAuthorize = $this->getMockBuilder(SimpleRbacAuthorize::class) + ->setMethods(['_loadPermissions', 'log']) + ->disableOriginalConstructor() + ->getMock(); + $simpleRbacAuthorize + ->expects($this->once()) + ->method('log') + ->with($expectedMsg, LogLevel::DEBUG); + + $simpleRbacAuthorize->config('permissions', $permissions); + $request = $this->_requestFromArray($requestParams); + + $simpleRbacAuthorize->authorize($user, $request); + } + + public function badPermissionProvider() + { + return [ + 'no-controller' => [ + //permissions + [[ + 'plugin' => 'Tests', + 'role' => 'test', + //'controller' => 'Tests', + 'action' => 'test', + 'allowed' => true, + ]], + //user + [ + 'id' => 1, + 'username' => 'luke', + 'role' => 'test', + ], + //request + [ + 'plugin' => 'Tests', + 'controller' => 'Tests', + 'action' => 'test' + ], + //expected + __d('CakeDC/Users', "Cannot evaluate permission when 'controller' and/or 'action' keys are absent"), + ], + 'no-action' => [ + //permissions + [[ + 'plugin' => 'Tests', + 'role' => 'test', + 'controller' => 'Tests', + //'action' => 'test', + 'allowed' => true, + ]], + //user + [ + 'id' => 1, + 'username' => 'luke', + 'role' => 'test', + ], + //request + [ + 'plugin' => 'Tests', + 'controller' => 'Tests', + 'action' => 'test' + ], + //expected + __d('CakeDC/Users', "Cannot evaluate permission when 'controller' and/or 'action' keys are absent"), + ], + 'no-controller-and-action' => [ + //permissions + [[ + 'plugin' => 'Tests', + 'role' => 'test', + //'controller' => 'Tests', + //'action' => 'test', + 'allowed' => true, + ]], + //user + [ + 'id' => 1, + 'username' => 'luke', + 'role' => 'test', + ], + //request + [ + 'plugin' => 'Tests', + 'controller' => 'Tests', + 'action' => 'test' + ], + //expected + __d('CakeDC/Users', "Cannot evaluate permission when 'controller' and/or 'action' keys are absent"), + ], + 'no-controller and user-key' => [ + //permissions + [[ + 'plugin' => 'Tests', + 'role' => 'test', + //'controller' => 'Tests', + 'action' => 'test', + 'allowed' => true, + 'user' => 'something', + ]], + //user + [ + 'id' => 1, + 'username' => 'luke', + 'role' => 'test', + ], + //request + [ + 'plugin' => 'Tests', + 'controller' => 'Tests', + 'action' => 'test' + ], + //expected + __d('CakeDC/Users', "Cannot evaluate permission when 'controller' and/or 'action' keys are absent"), + ], + 'user-key' => [ + //permissions + [[ + 'plugin' => 'Tests', + 'role' => 'test', + 'controller' => 'Tests', + 'action' => 'test', + 'allowed' => true, + 'user' => 'something', + ]], + //user + [ + 'id' => 1, + 'username' => 'luke', + 'role' => 'test', + ], + //request + [ + 'plugin' => 'Tests', + 'controller' => 'Tests', + 'action' => 'test' + ], + //expected + __d('CakeDC/Users', "Permission key 'user' is illegal, cannot evaluate the permission"), + ], + ]; + } + + /** + * @param array $params + * @return \Cake\Network\Request + */ + protected function _requestFromArray($params) + { + $request = new Request(); + $request->plugin = Hash::get($params, 'plugin'); + $request->controller = $params['controller']; + $request->action = $params['action']; + $prefix = Hash::get($params, 'prefix'); + $request->params = []; + if ($prefix) { + $request->params['prefix'] = $prefix; + } + $extension = Hash::get($params, '_ext'); + if ($extension) { + $request->params['_ext'] = $extension; + } + + return $request; + } }