diff --git a/config/permissions.php b/config/permissions.php index 3b64d09..7a61e69 100644 --- a/config/permissions.php +++ b/config/permissions.php @@ -105,14 +105,10 @@ 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'resetOneTimePasswordAuthenticator', - 'allowed' => function (array $user, $role, \Cake\Http\ServerRequest $request) { - $userId = \Cake\Utility\Hash::get($request->getAttribute('params'), 'pass.0'); - if (!empty($userId) && !empty($user)) { - return $userId === $user['id']; - } - - return false; - } + 'allowed' => [ + 'className' => \CakeDC\Auth\Rbac\Rules\OTPRule::class, + 'option' => [] + ], ], //all roles allowed to Pages/display [ diff --git a/src/Rbac/CachedRbac.php b/src/Rbac/CachedRbac.php new file mode 100644 index 0000000..601fb5b --- /dev/null +++ b/src/Rbac/CachedRbac.php @@ -0,0 +1,207 @@ +undasherize = $config['undasherize']; + } + $this->permissionsMap = Cache::remember('auth_permissions', function () { + return $this->buildPermissionsMap(); + }, '_cakedc_auth_'); + } + + /** + * @return array + */ + public function getPermissions() + { + return $this->permissions; + } + + /** + * @return array + */ + public function buildPermissionsMap() + { + $asArray = function ($permission, $key, $default = null) { + if ($default !== null && !array_key_exists($key, $permission)) { + return [$default, '_']; + } + if (!array_key_exists($key, $permission) || $permission[$key] == false || $permission[$key] == null) { + return ['_']; + } + $item = $permission[$key]; + if (is_string($item)) { + return [$item]; + } + return (array)$item; + }; + $map = []; + foreach ($this->permissions as $permission) { + if (isset($permission['role'])) { + $role = $permission['role']; + } else { + $role = '*'; + } + $roles = (array)$role; + foreach ($roles as $role) { + $prefixes = $asArray($permission, 'prefix', '*'); + $plugins = $asArray($permission, 'plugin', '*'); + $controllers = $asArray($permission, 'controller', '*'); + foreach ($prefixes as $prefix) { + foreach ($plugins as $plugin) { + foreach ($controllers as $controller) { + $key = "$prefix|$plugin|$controller"; + $map[$role][$key][] = $permission; + } + } + } + } + } + + return $map; + } + + /** + * @param array $permissions permissions + * @return void + */ + public function setPermissions($permissions) + { + $this->permissions = $permissions; + } + + /** + * Match against permissions, return if matched + * Permissions are processed based on the 'permissions' config values + * + * @param array|\ArrayAccess $user current user array + * @param \Psr\Http\Message\ServerRequestInterface $request request + * @return bool true if there is a match in permissions + */ + public function checkPermissions($user, ServerRequestInterface $request) + { + $roleField = $this->getConfig('role_field'); + $defaultRole = $this->getConfig('default_role'); + $role = Hash::get($user, $roleField, $defaultRole); + + $keys = $this->permissionKeys($request); + foreach ([$role, '*'] as $checkRole) { + if (!array_key_exists($checkRole, $this->permissionsMap)) { + continue; + } + foreach ($keys as $key) { + if (!array_key_exists($key, $this->permissionsMap[$checkRole])) { + continue; + } + $permissions = $this->permissionsMap[$checkRole][$key]; + foreach ($permissions as $permission) { + $matchResult = $this->_matchPermission($permission, $user, $role, $request); + if ($matchResult !== null) { + if ($this->getConfig('log')) { + $this->log($matchResult->getReason(), LogLevel::DEBUG); + } + + return $matchResult->isAllowed(); + } + } + } + } + + return false; + } + + /** + * Build list of permission keys to search. + * + * @param \Psr\Http\Message\ServerRequestInterface $request request + * @return array list of key to search based on request. + */ + protected function permissionKeys(ServerRequestInterface $request) + { + $params = $request->getAttribute('params'); + $permission = [ + 'prefix' => $params['prefix'] ?? null, + 'plugin' => $params['plugin'] ?? null, + 'controller' => $params['controller'] ?? null, + ]; + $keys = []; + $getKeys = function ($permission, $key) { + if ($permission[$key] == false || $permission[$key] == null) { + return ['_', '*']; + } + $item = $permission[$key]; + if ($this->undasherize) { + $item = Inflector::camelize((string)$item, '-'); + } + return [$item, '*']; + }; + $prefixes = $getKeys($permission, 'prefix'); + $plugins = $getKeys($permission, 'plugin'); + $controllers = $getKeys($permission, 'controller'); + foreach ($prefixes as $prefix) { + foreach ($plugins as $plugin) { + foreach ($controllers as $controller) { + $keys[] = "$prefix|$plugin|$controller"; + } + } + } + + return $keys; + } + +} diff --git a/src/Rbac/Permissions/AbstractProvider.php b/src/Rbac/Permissions/AbstractProvider.php index 9341433..d19eb6d 100644 --- a/src/Rbac/Permissions/AbstractProvider.php +++ b/src/Rbac/Permissions/AbstractProvider.php @@ -97,14 +97,10 @@ public function __construct($config = []) 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'resetOneTimePasswordAuthenticator', - 'allowed' => function (array $user, string $role, \Cake\Http\ServerRequest $request): bool { - $userId = \Cake\Utility\Hash::get($request->getAttribute('params'), 'pass.0'); - if (!empty($userId) && !empty($user)) { - return $userId === $user['id']; - } - - return false; - }, + 'allowed' => [ + 'className' => \CakeDC\Auth\Rbac\Rules\OTPRule::class, + 'option' => [] + ], ], //all roles allowed to Pages/display [ diff --git a/src/Rbac/Rbac.php b/src/Rbac/Rbac.php index a7be601..8e7ce76 100644 --- a/src/Rbac/Rbac.php +++ b/src/Rbac/Rbac.php @@ -20,6 +20,7 @@ use Cake\Utility\Inflector; use CakeDC\Auth\Rbac\Permissions\AbstractProvider; use CakeDC\Auth\Rbac\Rules\Rule; +use CakeDC\Auth\Rbac\Rules\RuleRegistry; use Psr\Http\Message\ServerRequestInterface; use Psr\Log\LogLevel; @@ -180,6 +181,11 @@ protected function _matchPermission(array $permission, $user, $role, ServerReque $return = (bool)call_user_func($value, $user, $role, $request); } elseif ($value instanceof Rule) { $return = (bool)$value->allowed($user, $role, $request); + } elseif (is_array($value) && array_key_exists('className', $value)) { + $rule = RuleRegistry::get($value['className'], $value['options'] ?? []); + if ($rule instanceof Rule) { + $return = (bool)$rule->allowed($user, $role, $request); + } } elseif ($key === 'bypassAuth' && $value === true) { $return = true; } elseif ($key === 'allowed') { diff --git a/src/Rbac/Rules/OTPRule.php b/src/Rbac/Rules/OTPRule.php new file mode 100644 index 0000000..58eadee --- /dev/null +++ b/src/Rbac/Rules/OTPRule.php @@ -0,0 +1,36 @@ +getAttribute('params'), 'pass.0'); + if (!empty($userId) && !empty($user)) { + return $userId === $user['id']; + } + + return false; + } +} diff --git a/tests/TestCase/Rbac/CachedRbacTest.php b/tests/TestCase/Rbac/CachedRbacTest.php new file mode 100644 index 0000000..9d73f0b --- /dev/null +++ b/tests/TestCase/Rbac/CachedRbacTest.php @@ -0,0 +1,1147 @@ +defaultPermissions = [ + //all bypass + [ + 'prefix' => false, + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => [ + // LoginTrait + 'socialLogin', + 'login', + 'logout', + 'socialEmail', + 'verify', + // RegisterTrait + 'register', + 'validateEmail', + // PasswordManagementTrait used in RegisterTrait + 'changePassword', + 'resetPassword', + 'requestResetPassword', + // UserValidationTrait used in PasswordManagementTrait + 'resendTokenValidation', + 'linkSocial', + ], + 'bypassAuth' => true, + ], + [ + 'prefix' => false, + 'plugin' => 'CakeDC/Users', + 'controller' => 'SocialAccounts', + 'action' => [ + 'validateAccount', + 'resendValidation', + ], + 'bypassAuth' => true, + ], + //admin role allowed to all the things + [ + 'role' => 'admin', + 'prefix' => '*', + 'extension' => '*', + 'plugin' => '*', + 'controller' => '*', + 'action' => '*', + ], + //specific actions allowed for the all roles in Users plugin + [ + 'role' => '*', + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => ['profile', 'logout', 'linkSocial', 'callbackLinkSocial'], + ], + [ + 'role' => '*', + 'plugin' => 'CakeDC/Users', + 'controller' => 'Users', + 'action' => 'resetOneTimePasswordAuthenticator', + 'allowed' => [ + 'className' => \CakeDC\Auth\Rbac\Rules\OTPRule::class, + 'option' => [] + ], + ], + //all roles allowed to Pages/display + [ + 'role' => '*', + 'controller' => 'Pages', + 'action' => 'display', + ], + ]; + $this->rbac = new CachedRbac(null, $this->defaultPermissions); + \Cake\Cache\Cache::clear('_cakedc_auth_'); + } + + /** + * Tears down the fixture, for example, closes a network connection. + * This method is called after a test is executed. + */ + public function tearDown(): void + { + unset($this->rbac); + \Cake\Cache\Cache::clear('_cakedc_auth_'); + } + + /** + * @covers \CakeDC\Auth\Rbac\Rbac::__construct + */ + public function testConstructGetDefaultPermissions() + { + $this->rbac = new CachedRbac(); + $result = $this->rbac->getPermissions(); + $this->assertSame($this->defaultPermissions, $result); + } + + /** + * @covers \CakeDC\Auth\Rbac\Rbac::__construct + */ + public function testConstructBadProvider() + { + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Class "\Exception" must extend AbstractProvider'); + $this->rbac = new CachedRbac([ + 'permissions_provider_class' => '\Exception', + ]); + } + + /** + * @covers \CakeDC\Auth\Rbac\Rbac::__construct + */ + public function testConstructSetPermissions() + { + $this->rbac = new CachedRbac([ + 'permissions' => [], + ]); + $this->assertEmpty($this->rbac->getPermissions()); + } + + protected function assertConstructorPermissions($instance, $config, $permissions) + { + $reflectedClass = new ReflectionClass($instance); + $constructor = $reflectedClass->getConstructor(); + $constructor->invoke($this->simpleRbacAuthorize, $this->registry, $config); + + //we should have the default permissions + $resultPermissions = $this->simpleRbacAuthorize->getConfig('permissions'); + $this->assertEquals($permissions, $resultPermissions); + } + + /** + * @dataProvider providerAuthorize + */ + public function testAuthorize($permissions, $user, $requestParams, $expected) + { + $this->rbac = new CachedRbac(['permissions' => $permissions, 'undasherize' => true]); + $request = $this->_requestFromArray($requestParams); + + $result = $this->rbac->checkPermissions($user, $request); + $this->assertSame($expected, $result); + } + + public function providerAuthorize() + { + $trueRuleMock = $this->getMockBuilder(Owner::class) + ->setMethods(['allowed']) + ->getMock(); + $trueRuleMock->expects($this->any()) + ->method('allowed') + ->willReturn(true); + + return [ + 'deny-first-discard-after' => [ + //permissions + [ + [ + 'role' => 'test', + 'controller' => 'Tests', + 'action' => 'one', + 'allowed' => false, + ], + ], + //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, + ], + 'happy-strict-all' => [ + //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 + true, + ], + 'happy-strict-all-deny' => [ + //permissions + [[ + 'plugin' => 'Tests', + 'role' => 'test', + 'controller' => 'Tests', + 'action' => 'test', + 'allowed' => false, + ]], + //user + [ + 'id' => 1, + 'username' => 'luke', + 'role' => 'test', + ], + //request + [ + 'plugin' => 'Tests', + 'controller' => 'Tests', + 'action' => 'test', + ], + //expected + false, + ], + 'happy-plugin-null-allowed-null' => [ + //permissions + [[ + 'role' => 'test', + 'controller' => 'Tests', + 'action' => 'test', + ]], + //user + [ + 'id' => 1, + 'username' => 'luke', + 'role' => 'test', + ], + //request + [ + 'plugin' => null, + 'controller' => 'Tests', + 'action' => 'test', + ], + //expected + true, + ], + 'happy-plugin-asterisk' => [ + //permissions + [[ + 'plugin' => '*', + 'role' => 'test', + 'controller' => 'Tests', + 'action' => 'test', + ]], + //user + [ + 'id' => 1, + 'username' => 'luke', + 'role' => 'test', + ], + //request + [ + 'plugin' => 'Any', + 'controller' => 'Tests', + 'action' => 'test', + ], + //expected + true, + ], + 'happy-plugin-asterisk-main-app' => [ + //permissions + [[ + 'plugin' => '*', + 'role' => 'test', + 'controller' => 'Tests', + 'action' => 'test', + ]], + //user + [ + 'id' => 1, + 'username' => 'luke', + 'role' => 'test', + ], + //request + [ + 'plugin' => null, + 'controller' => 'Tests', + 'action' => 'test', + ], + //expected + true, + ], + 'happy-role-asterisk' => [ + //permissions + [[ + 'role' => '*', + 'controller' => 'Tests', + 'action' => 'test', + ]], + //user + [ + 'id' => 1, + 'username' => 'luke', + 'role' => 'any-role', + ], + //request + [ + 'plugin' => null, + 'controller' => 'Tests', + 'action' => 'test', + ], + //expected + true, + ], + 'happy-controller-asterisk' => [ + //permissions + [[ + 'plugin' => 'Tests', + 'role' => 'test', + 'controller' => '*', + 'action' => 'test', + ]], + //user + [ + 'id' => 1, + 'username' => 'luke', + 'role' => 'test', + ], + //request + [ + 'plugin' => 'Tests', + 'controller' => 'Tests', + 'action' => 'test', + ], + //expected + true, + ], + 'happy-action-asterisk' => [ + //permissions + [[ + 'plugin' => 'Tests', + 'role' => 'test', + 'controller' => 'Tests', + 'action' => '*', + ]], + //user + [ + 'id' => 1, + 'username' => 'luke', + 'role' => 'test', + ], + //request + [ + 'plugin' => 'Tests', + 'controller' => 'Tests', + 'action' => 'any', + ], + //expected + true, + ], + 'happy-some-asterisk-allowed' => [ + //permissions + [[ + 'plugin' => '*', + 'role' => 'test', + 'controller' => '*', + 'action' => '*', + ]], + //user + [ + 'id' => 1, + 'username' => 'luke', + 'role' => 'test', + ], + //request + [ + 'plugin' => 'Tests', + 'controller' => 'Tests', + 'action' => 'any', + ], + //expected + true, + ], + 'happy-some-asterisk-deny' => [ + //permissions + [[ + 'plugin' => '*', + 'role' => 'test', + 'controller' => '*', + 'action' => '*', + 'allowed' => false, + ]], + //user + [ + 'id' => 1, + 'username' => 'luke', + 'role' => 'test', + ], + //request + [ + 'plugin' => 'Tests', + 'controller' => 'Tests', + 'action' => 'any', + ], + //expected + false, + ], + 'all-deny' => [ + //permissions + [[ + 'plugin' => '*', + 'role' => '*', + 'controller' => '*', + 'action' => '*', + 'allowed' => false, + ]], + //user + [ + 'id' => 1, + 'username' => 'luke', + 'role' => 'test', + ], + //request + [ + 'plugin' => 'Any', + 'controller' => 'Any', + 'action' => 'any', + ], + //expected + false, + ], + 'dasherized' => [ + //permissions + [[ + 'plugin' => 'Tests', + 'role' => 'test', + 'controller' => 'TestTests', + 'action' => 'TestAction', + 'allowed' => true, + ]], + //user + [ + 'id' => 1, + 'username' => 'luke', + 'role' => 'test', + ], + //request + [ + 'plugin' => 'tests', + 'controller' => 'test-tests', + 'action' => 'test-action', + ], + //expected + true, + ], + 'happy-array' => [ + //permissions + [[ + '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, + ], + 'happy-array-deny' => [ + //permissions + [[ + 'plugin' => ['Tests'], + 'role' => ['test'], + 'controller' => ['Tests'], + 'action' => ['one', 'two'], + ]], + //user + [ + 'id' => 1, + 'username' => 'luke', + 'role' => 'test', + ], + //request + [ + 'plugin' => 'Tests', + 'controller' => 'Tests', + 'action' => 'three', + ], + //expected + false, + ], + 'happy-callback-check-params' => [ + //permissions + [[ + 'plugin' => ['Tests'], + 'role' => ['test'], + 'controller' => ['Tests'], + 'action' => ['one', 'two'], + // 'allowed' => function ($user, $role, $request) { + // return $user['id'] === 1 && $role = 'test' && $request->getParam('plugin') == 'Tests'; + // }, + ]], + //user + [ + 'id' => 1, + 'username' => 'luke', + 'role' => 'test', + ], + //request + [ + 'plugin' => 'Tests', + 'controller' => 'Tests', + 'action' => 'one', + ], + //expected + true, + ], + 'happy-callback-deny' => [ + //permissions + [[ + 'plugin' => ['*'], + 'role' => ['test'], + 'controller' => ['Tests'], + 'action' => ['one', 'two'], + 'allowed' => false, + // function ($user, $role, $request) { + // return false; + // }, + ]], + //user + [ + 'id' => 1, + 'username' => 'luke', + 'role' => 'test', + ], + //request + [ + 'plugin' => 'Tests', + 'controller' => 'Tests', + 'action' => 'one', + ], + //expected + false, + ], + 'happy-prefix' => [ + //permissions + [[ + 'role' => ['test'], + 'prefix' => ['Admin'], + 'controller' => ['Tests'], + 'action' => ['one', 'two'], + ]], + //user + [ + 'id' => 1, + 'username' => 'luke', + 'role' => 'test', + ], + //request + [ + 'prefix' => 'Admin', + 'controller' => 'Tests', + 'action' => 'one', + ], + //expected + true, + ], + 'deny-prefix' => [ + //permissions + [[ + 'role' => ['test'], + 'prefix' => ['admin'], + 'controller' => ['Tests'], + 'action' => ['one', 'two'], + ]], + //user + [ + 'id' => 1, + 'username' => 'luke', + 'role' => 'test', + ], + //request + [ + 'controller' => 'Tests', + 'action' => 'one', + ], + //expected + false, + ], + 'star-prefix' => [ + //permissions + [[ + 'role' => ['test'], + 'prefix' => '*', + 'controller' => ['Tests'], + 'action' => ['one', 'two'], + ]], + //user + [ + 'id' => 1, + 'username' => 'luke', + 'role' => 'test', + ], + //request + [ + 'prefix' => 'admin', + 'controller' => 'Tests', + 'action' => 'one', + ], + //expected + true, + ], + 'array-prefix' => [ + //permissions + [[ + 'role' => ['test'], + 'prefix' => ['One', 'Admin'], + 'controller' => '*', + 'action' => '*', + ]], + //user + [ + 'id' => 1, + 'username' => 'luke', + 'role' => 'test', + ], + //request + [ + 'prefix' => 'Admin', + 'controller' => 'Tests', + 'action' => 'one', + ], + //expected + true, + ], + 'array-prefix-deny' => [ + //permissions + [[ + 'role' => ['test'], + 'prefix' => ['one', 'admin'], + 'controller' => '*', + 'action' => 'one', + 'allowed' => false, + ]], + //user + [ + 'id' => 1, + 'username' => 'luke', + 'role' => 'test', + ], + //request + [ + 'prefix' => 'admin', + 'controller' => 'Tests', + 'action' => 'one', + ], + //expected + false, + ], + 'happy-ext' => [ + //permissions + [[ + 'role' => ['test'], + 'prefix' => ['Admin'], + 'extension' => ['csv'], + 'controller' => ['Tests'], + 'action' => ['one', 'two'], + ]], + //user + [ + 'id' => 1, + 'username' => 'luke', + 'role' => 'test', + ], + //request + [ + 'prefix' => 'Admin', + '_ext' => 'csv', + 'controller' => 'Tests', + 'action' => 'one', + ], + //expected + true, + ], + 'deny-ext' => [ + //permissions + [[ + 'role' => ['test'], + 'extension' => ['csv'], + 'controller' => ['Tests'], + 'action' => ['one', 'two'], + 'allowed' => false, + ]], + //user + [ + 'id' => 1, + 'username' => 'luke', + 'role' => 'test', + ], + //request + [ + 'controller' => 'Tests', + '_ext' => 'csv', + 'action' => 'one', + ], + //expected + false, + ], + 'star-ext' => [ + //permissions + [[ + 'role' => ['test'], + 'prefix' => '*', + 'extension' => '*', + 'controller' => ['Tests'], + 'action' => ['one', 'two'], + ]], + //user + [ + 'id' => 1, + 'username' => 'luke', + 'role' => 'test', + ], + //request + [ + 'prefix' => 'admin', + '_ext' => 'other', + 'controller' => 'Tests', + 'action' => 'one', + ], + //expected + true, + ], + 'array-ext' => [ + //permissions + [[ + 'role' => ['test'], + 'extension' => ['csv', 'pdf'], + 'controller' => '*', + 'action' => '*', + ]], + //user + [ + 'id' => 1, + 'username' => 'luke', + 'role' => 'test', + ], + //request + [ + '_ext' => 'csv', + 'controller' => 'Tests', + 'action' => 'one', + ], + //expected + true, + ], + 'array-ext-deny' => [ + //permissions + [[ + 'role' => ['test'], + 'extension' => ['csv', 'docx'], + 'controller' => '*', + 'action' => 'one', + 'allowed' => false, + ]], + //user + [ + 'id' => 1, + 'username' => 'luke', + 'role' => 'test', + ], + //request + [ + 'prefix' => 'csv', + 'controller' => 'Tests', + 'action' => 'one', + ], + //expected + false, + ], + 'rule-class' => [ + //permissions + [ + [ + 'role' => ['test'], + 'controller' => '*', + 'action' => 'one', + 'allowed' => $trueRuleMock, + ], + ], + //user + [ + 'id' => 1, + 'username' => 'luke', + 'role' => 'test', + ], + //request + [ + 'controller' => 'Tests', + 'action' => 'one', + ], + //expected + true, + ], + 'bypass-auth' => [ + //permissions + [[ + 'plugin' => 'Tests', + 'controller' => 'Tests', + 'action' => 'test', + 'bypassAuth' => true, + ]], + //user + [], + //request + [ + 'plugin' => 'Tests', + 'controller' => 'Tests', + 'action' => 'test', + ], + //expected + true, + ], + 'bypass-auth-callable' => [ + //permissions + [[ + 'plugin' => 'Tests', + 'controller' => 'Tests', + 'action' => 'test', + 'bypassAuth' => false, + ]], + //user + [], + //request + [ + 'plugin' => 'Tests', + 'controller' => 'Tests', + 'action' => 'test', + ], + //expected + false, + ], + 'bypass-auth-rule-not-allowed-order-matters' => [ + //permissions + [[ + 'plugin' => 'Tests', + 'controller' => 'Tests', + 'action' => 'test', + 'role' => '*', + 'bypassAuth' => true, + 'allowed' => false, + ]], + //user + [ + 'id' => 1, + 'username' => 'luke', + 'role' => 'test', + ], + //request + [ + 'plugin' => 'Tests', + 'controller' => 'Tests', + 'action' => 'test', + ], + //expected + true, + ], + 'rule-not-allowed-bypass-auth-order-matters' => [ + //permissions + [[ + 'plugin' => 'Tests', + 'controller' => 'Tests', + 'action' => 'test', + 'role' => '*', + 'allowed' => false, + 'bypassAuth' => true, + ]], + //user + [ + 'id' => 1, + 'username' => 'luke', + 'role' => 'test', + ], + //request + [ + 'plugin' => 'Tests', + 'controller' => 'Tests', + 'action' => 'test', + ], + //expected + false, + ], + 'bypass-auth-user-not-authorized-another-role' => [ + //permissions + [[ + 'plugin' => 'Tests', + 'controller' => 'Tests', + 'action' => 'test', + 'role' => 'admin', + 'bypassAuth' => true, + ]], + //user + [ + 'id' => 1, + 'username' => 'luke', + 'role' => 'test', + ], + //request + [ + 'plugin' => 'Tests', + 'controller' => 'Tests', + 'action' => 'test', + ], + //expected + false, + ], + 'custom-rule-any-role' => [ + //permissions + [[ + 'plugin' => 'Tests', + 'controller' => 'Tests', + 'action' => 'test', + 'role' => 'admin', + // 'allowed' => new SampleRule(), + ]], + //user + [ + 'id' => 1, + 'username' => 'luke', + 'role' => 'admin', + ], + //request + [ + 'plugin' => 'Tests', + 'controller' => 'Tests', + 'action' => 'test', + ], + //expected + true, + ], + 'custom-rule-no-role' => [ + //permissions + [[ + 'plugin' => 'Tests', + 'controller' => 'Tests', + 'action' => 'test', + 'role' => 'admin', + // 'allowed' => new SampleRule(), + ]], + //user + [ + ], + //request + [ + 'plugin' => 'Tests', + 'controller' => 'Tests', + 'action' => 'test', + ], + //expected + false, + ], + ]; + } + + /** + * @dataProvider badPermissionProvider + * @param array $permissions + * @param array $user + * @param array $requestParams + * @param string $expectedMsg + */ + public function testBadPermission($permissions, $user, $requestParams, $expectedMsg) + { + $rbac = $this->getMockBuilder(CachedRbac::class) + ->setMethods(['log']) + ->disableOriginalConstructor() + ->getMock(); + $rbac + ->expects($this->once()) + ->method('log') + ->with($expectedMsg, LogLevel::DEBUG); + + $rbac->setConfig('log', true); + $rbac->setPermissions($permissions); + $request = $this->_requestFromArray($requestParams); + + $rbac->checkPermissions($user, $request); + } + + public function badPermissionProvider() + { + return [ + ]; + } + + /** + * @param array $params + * @return ServerRequest + */ + protected function _requestFromArray($params) + { + $request = new ServerRequest(); + + return $request + ->withParam('plugin', $params['plugin'] ?? null) + ->withParam('controller', $params['controller'] ?? null) + ->withParam('action', $params['action'] ?? null) + ->withParam('prefix', $params['prefix'] ?? null) + ->withParam('_ext', $params['_ext'] ?? null); + } + + public function testGetPermissions() + { + $permissions = ['test']; + $this->rbac->setPermissions($permissions); + $this->assertSame($permissions, $this->rbac->getPermissions()); + } +} diff --git a/tests/TestCase/Rbac/RbacTest.php b/tests/TestCase/Rbac/RbacTest.php index d442b71..c4d93b2 100644 --- a/tests/TestCase/Rbac/RbacTest.php +++ b/tests/TestCase/Rbac/RbacTest.php @@ -97,7 +97,10 @@ public function setUp(): void 'plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'resetOneTimePasswordAuthenticator', - 'allowed' => true, + 'allowed' => [ + 'className' => \CakeDC\Auth\Rbac\Rules\OTPRule::class, + 'option' => [] + ], ], //all roles allowed to Pages/display [ @@ -125,8 +128,6 @@ public function testConstructGetDefaultPermissions() { $this->rbac = new Rbac(); $result = $this->rbac->getPermissions(); - $this->assertTrue(is_callable($result[4]['allowed'])); - $result[4]['allowed'] = true; $this->assertSame($this->defaultPermissions, $result); } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 09925f9..6279eb5 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -94,6 +94,13 @@ 'serialize' => 'File', 'duration' => '+10 seconds', ], + '_cakedc_auth_' => [ + 'className' => 'File', + 'prefix' => 'users_app_cakedc_auth_', + 'path' => CACHE . 'cakedc_auth/', + 'serialize' => 'File', + 'duration' => '+10 seconds', + ], ]; Cake\Cache\Cache::setConfig($cache);