├── .laminas-ci.json ├── .gitignore ├── phpcs.xml ├── src ├── Guard │ ├── GuardInterface.php │ ├── AbstractGuard.php │ ├── Route.php │ └── Controller.php ├── Exception │ ├── UnAuthorizedException.php │ ├── InvalidArgumentException.php │ └── InvalidRoleException.php ├── Service │ ├── GuardsServiceFactory.php │ ├── RoleProvidersServiceFactory.php │ ├── RuleProvidersServiceFactory.php │ ├── ResourceProvidersServiceFactory.php │ ├── AuthorizeAwareInterface.php │ ├── AuthorizeFactory.php │ ├── UserRoleServiceFactory.php │ ├── ConfigServiceFactory.php │ ├── IdentityProviderServiceFactory.php │ ├── RouteGuardServiceFactory.php │ ├── UnauthorizedStrategyServiceFactory.php │ ├── ConfigRoleProviderServiceFactory.php │ ├── ConfigRuleProviderServiceFactory.php │ ├── ControllerGuardServiceFactory.php │ ├── ConfigResourceProviderServiceFactory.php │ ├── CacheKeyGeneratorFactory.php │ ├── LaminasDbRoleProviderServiceFactory.php │ ├── RoleCollectorServiceFactory.php │ ├── AuthorizeAwareServiceInitializer.php │ ├── AuthenticationIdentityProviderServiceFactory.php │ ├── BaseProvidersServiceFactory.php │ ├── LmcUserLaminasDbIdentityProviderServiceFactory.php │ ├── CacheFactory.php │ └── ObjectRepositoryRoleProviderFactory.php ├── Provider │ ├── Rule │ │ ├── ProviderInterface.php │ │ └── Config.php │ ├── Role │ │ ├── ProviderInterface.php │ │ ├── ObjectRepositoryProvider.php │ │ ├── Config.php │ │ └── LaminasDb.php │ ├── Resource │ │ ├── ProviderInterface.php │ │ └── Config.php │ └── Identity │ │ ├── ProviderInterface.php │ │ ├── LmcUserLaminasDb.php │ │ └── AuthenticationIdentityProvider.php ├── Acl │ ├── HierarchicalRoleInterface.php │ └── Role.php ├── View │ ├── Helper │ │ ├── IsAllowed.php │ │ └── IsAllowedFactory.php │ ├── RedirectionStrategy.php │ └── UnauthorizedStrategy.php ├── Controller │ └── Plugin │ │ ├── IsAllowed.php │ │ └── IsAllowedFactory.php ├── Module.php └── Collector │ └── RoleCollector.php ├── data ├── schema.pgsql.sql ├── schema.sql ├── Role.php.odm.dist ├── Role.php.dist ├── User.php.odm.dist └── User.php.dist ├── test ├── Service │ ├── MockProvider.php │ ├── ConfigServiceFactoryTest.php │ ├── AuthorizeFactoryTest.php │ ├── ConfigRuleProviderServiceFactoryTest.php │ ├── ControllerGuardServiceFactoryTest.php │ ├── ConfigRoleProviderServiceFactoryTest.php │ ├── RoleCollectorServiceFactoryTest.php │ ├── RouteGuardServiceFactoryTest.php │ ├── LaminasDbRoleProviderServiceFactoryTest.php │ ├── ConfigResourceProviderServiceFactoryTest.php │ ├── UnauthorizedStrategyServiceFactoryTest.php │ ├── CacheFactoryTest.php │ ├── IdentityProviderServiceFactoryTest.php │ ├── CacheKeyGeneratorFactoryTest.php │ ├── ObjectRepositoryRoleProviderFactoryTest.php │ ├── AuthenticationIdentityProviderServiceFactoryTest.php │ ├── AuthorizeAwareServiceInitializerTest.php │ ├── BaseProvidersServiceFactoryTest.php │ └── AuthorizeTest.php ├── Provider │ ├── Resource │ │ └── ConfigTest.php │ ├── Role │ │ ├── ConfigTest.php │ │ ├── LaminasDbTest.php │ │ └── ObjectRepositoryProviderTest.php │ └── Identity │ │ ├── LmcUserLaminasDbTest.php │ │ └── AuthenticationIdentityProviderTest.php ├── View │ ├── Helper │ │ └── IsAllowedTest.php │ ├── UnauthorizedStrategyTest.php │ └── RedirectionStrategyTest.php ├── Controller │ └── Plugin │ │ └── IsAllowedTest.php ├── Acl │ └── RoleTest.php ├── ModuleTest.php ├── Collector │ └── RoleCollectorTest.php └── Guard │ └── RouteTest.php ├── view ├── error │ └── 403.phtml └── laminas-developer-tools │ └── toolbar │ └── bjy-authorize-role.phtml ├── phpunit.xml.dist ├── .github └── workflows │ └── continous-integration.yml ├── LICENSE ├── docs ├── unauthorized-strategies.md ├── doctrine.md └── simple-example.md ├── composer.json ├── config └── module.config.php └── README.md /.laminas-ci.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_php_platform_requirements": { 3 | "8.0": false, 4 | "8.1": true 5 | } 6 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | composer.phar 3 | clover.xml 4 | phpunit.xml 5 | vendor 6 | .idea 7 | .phpunit.result.cache -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | src 7 | test 8 | -------------------------------------------------------------------------------- /src/Guard/GuardInterface.php: -------------------------------------------------------------------------------- 1 | options = $options; 23 | $this->container = $container; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Provider/Rule/Config.php: -------------------------------------------------------------------------------- 1 | rules = $config; 21 | } 22 | 23 | /** 24 | * {@inheritDoc} 25 | */ 26 | public function getRules() 27 | { 28 | return $this->rules; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /view/error/403.phtml: -------------------------------------------------------------------------------- 1 |

403 Forbidden

2 |

3 | 4 | You are not authorized to access 5 | 6 | ::Action() 7 | 8 | . 9 | 10 | You are not authorized to access . 11 | 12 | You are not authorized . 13 | 14 | An unknown error occurred. 15 | 16 |

17 | -------------------------------------------------------------------------------- /src/Exception/InvalidRoleException.php: -------------------------------------------------------------------------------- 1 | resources = $config; 23 | } 24 | 25 | /** 26 | * {@inheritDoc} 27 | */ 28 | public function getResources() 29 | { 30 | return $this->resources; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Service/AuthorizeFactory.php: -------------------------------------------------------------------------------- 1 | get('BjyAuthorize\Config'), $container); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Service/UserRoleServiceFactory.php: -------------------------------------------------------------------------------- 1 | get('bjyauthorize_zend_db_adapter')); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Service/ConfigServiceFactory.php: -------------------------------------------------------------------------------- 1 | get('config'); 23 | 24 | return $config['bjyauthorize']; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Service/IdentityProviderServiceFactory.php: -------------------------------------------------------------------------------- 1 | get($container->get('BjyAuthorize\Config')['identity_provider']); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Service/RouteGuardServiceFactory.php: -------------------------------------------------------------------------------- 1 | get('BjyAuthorize\Config')['guards'][Route::class], $container); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | ./src 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | ./test 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/Service/UnauthorizedStrategyServiceFactory.php: -------------------------------------------------------------------------------- 1 | get('BjyAuthorize\Config')['template']); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/Provider/Resource/ConfigTest.php: -------------------------------------------------------------------------------- 1 | getResources(); 24 | 25 | $this->assertCount(2, $resources); 26 | $this->assertContains('resource1', $resources); 27 | $this->assertContains('resource2', $resources); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Service/ConfigRoleProviderServiceFactory.php: -------------------------------------------------------------------------------- 1 | get('BjyAuthorize\Config')['role_providers'][Config::class] 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Service/ConfigRuleProviderServiceFactory.php: -------------------------------------------------------------------------------- 1 | get('BjyAuthorize\Config')['rule_providers'][Config::class] 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/View/Helper/IsAllowed.php: -------------------------------------------------------------------------------- 1 | authorizeService = $authorizeService; 21 | } 22 | 23 | /** 24 | * @param mixed $resource 25 | * @param mixed|null $privilege 26 | * @return bool 27 | */ 28 | public function __invoke($resource, $privilege = null) 29 | { 30 | return $this->authorizeService->isAllowed($resource, $privilege); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Service/ControllerGuardServiceFactory.php: -------------------------------------------------------------------------------- 1 | get('BjyAuthorize\Config')['guards'][Controller::class], 25 | $container 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Service/ConfigResourceProviderServiceFactory.php: -------------------------------------------------------------------------------- 1 | get('BjyAuthorize\Config')['resource_providers'][Config::class] 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Controller/Plugin/IsAllowed.php: -------------------------------------------------------------------------------- 1 | authorizeService = $authorizeService; 21 | } 22 | 23 | /** 24 | * @param mixed $resource 25 | * @param mixed|null $privilege 26 | * @return bool 27 | */ 28 | public function __invoke($resource, $privilege = null) 29 | { 30 | return $this->authorizeService->isAllowed($resource, $privilege); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Service/CacheKeyGeneratorFactory.php: -------------------------------------------------------------------------------- 1 | get('BjyAuthorize\Config'); 23 | $cacheKey = empty($config['cache_key']) ? 'bjyauthorize_acl' : (string) $config['cache_key']; 24 | 25 | return function () use ($cacheKey) { 26 | return $cacheKey; 27 | }; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Service/LaminasDbRoleProviderServiceFactory.php: -------------------------------------------------------------------------------- 1 | get('BjyAuthorize\Config')['role_providers'][LaminasDb::class], 25 | $container 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.github/workflows/continous-integration.yml: -------------------------------------------------------------------------------- 1 | name: "Continuous Integration" 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - 'master' 8 | - 'feature/*' 9 | - '[0-9]+.[0-9]+.x' 10 | - 'refs/pull/*' 11 | 12 | jobs: 13 | matrix: 14 | name: Generate job matrix 15 | runs-on: ubuntu-latest 16 | outputs: 17 | matrix: ${{ steps.matrix.outputs.matrix }} 18 | steps: 19 | - name: Gather CI configuration 20 | id: matrix 21 | uses: laminas/laminas-ci-matrix-action@v1 22 | 23 | qa: 24 | name: QA Checks 25 | needs: [matrix] 26 | runs-on: ${{ matrix.operatingSystem }} 27 | strategy: 28 | fail-fast: false 29 | matrix: ${{ fromJSON(needs.matrix.outputs.matrix) }} 30 | steps: 31 | - name: ${{ matrix.name }} 32 | uses: laminas/laminas-continuous-integration-action@v1 33 | with: 34 | job: ${{ matrix.job }} -------------------------------------------------------------------------------- /src/Service/RoleCollectorServiceFactory.php: -------------------------------------------------------------------------------- 1 | get(ProviderInterface::class); 26 | 27 | return new RoleCollector($identityProvider); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/Service/ConfigServiceFactoryTest.php: -------------------------------------------------------------------------------- 1 | createMock(ContainerInterface::class); 23 | 24 | $container 25 | ->expects($this->any()) 26 | ->method('get') 27 | ->will($this->returnValue(['bjyauthorize' => ['foo' => 'bar']])); 28 | 29 | $this->assertSame(['foo' => 'bar'], $factory($container, ConfigServiceFactory::class)); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/Service/AuthorizeFactoryTest.php: -------------------------------------------------------------------------------- 1 | setService('BjyAuthorize\Config', ['cache_key' => 'bjyauthorize_acl']); 24 | 25 | $authorizeFactory = new AuthorizeFactory(); 26 | 27 | $authorize = $authorizeFactory($serviceManager, AuthorizeFactory::class); 28 | 29 | $this->assertInstanceOf(Authorize::class, $authorize); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /data/schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS `user_role` ( 2 | `id` INT(11) NOT NULL AUTO_INCREMENT, 3 | `role_id` VARCHAR(255) NOT NULL, 4 | `is_default` TINYINT(1) NOT NULL DEFAULT 0, 5 | `parent_id` INT(11) NULL, 6 | PRIMARY KEY (`id`), 7 | UNIQUE INDEX `unique_role` (`role_id` ASC), 8 | INDEX `idx_parent_id` (`parent_id` ASC), 9 | CONSTRAINT `fk_parent_id` FOREIGN KEY (`parent_id`) REFERENCES `user_role` (`id`) ON DELETE SET NULL 10 | ) ENGINE = InnoDB DEFAULT CHARACTER SET = utf8 COLLATE = utf8_unicode_ci; 11 | 12 | CREATE TABLE IF NOT EXISTS `user_role_linker` ( 13 | `user_id` INT(11) UNSIGNED NOT NULL, 14 | `role_id` INT(11) NOT NULL, 15 | PRIMARY KEY (`user_id`, `role_id`), 16 | INDEX `idx_role_id` (`role_id` ASC), 17 | INDEX `idx_user_id` (`user_id` ASC), 18 | CONSTRAINT `fk_role_id` FOREIGN KEY (`role_id`) REFERENCES `user_role` (`id`) ON DELETE CASCADE, 19 | CONSTRAINT `fk_user_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`user_id`) ON DELETE CASCADE 20 | ) ENGINE = InnoDB DEFAULT CHARACTER SET = utf8 COLLATE = utf8_unicode_ci; 21 | -------------------------------------------------------------------------------- /src/Service/AuthorizeAwareServiceInitializer.php: -------------------------------------------------------------------------------- 1 | get(Authorize::class); 30 | 31 | $instance->setAuthorizeService($authorize); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2012 Ben Youngblood 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software 4 | and associated documentation files (the "Software"), to deal in the Software without restriction, 5 | including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 6 | and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 7 | subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all copies or substantial 10 | portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 13 | LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 14 | NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 15 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 16 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | -------------------------------------------------------------------------------- /src/Controller/Plugin/IsAllowedFactory.php: -------------------------------------------------------------------------------- 1 | getServiceLocator(), IsAllowed::class); 22 | } 23 | 24 | /** 25 | * @param string $requestedName 26 | * @param array|null $options 27 | * @return IsAllowed 28 | */ 29 | public function __invoke(ContainerInterface $container, $requestedName, ?array $options = null) 30 | { 31 | $authorize = $container->get(Authorize::class); 32 | 33 | return new IsAllowed($authorize); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/View/Helper/IsAllowedFactory.php: -------------------------------------------------------------------------------- 1 | getServiceLocator(), IsAllowed::class); 22 | } 23 | 24 | /** 25 | * @param string $requestedName 26 | * @param array|null $options 27 | * @return IsAllowed 28 | */ 29 | public function __invoke(ContainerInterface $container, $requestedName, ?array $options = null) 30 | { 31 | /** @var Authorize $authorize */ 32 | $authorize = $container->get(Authorize::class); 33 | 34 | return new IsAllowed($authorize); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test/Service/ConfigRuleProviderServiceFactoryTest.php: -------------------------------------------------------------------------------- 1 | createMock(ContainerInterface::class); 24 | $config = ['rule_providers' => [Config::class => []]]; 25 | 26 | $container 27 | ->expects($this->any()) 28 | ->method('get') 29 | ->with('BjyAuthorize\\Config') 30 | ->will($this->returnValue($config)); 31 | 32 | $this->assertInstanceOf(Config::class, $factory($container, ConfigRuleProviderServiceFactory::class)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Service/AuthenticationIdentityProviderServiceFactory.php: -------------------------------------------------------------------------------- 1 | get('lmcuser_user_service'); 24 | $simpleIdentityProvider = new AuthenticationIdentityProvider($user->getAuthService()); 25 | $config = $container->get('BjyAuthorize\Config'); 26 | 27 | $simpleIdentityProvider->setDefaultRole($config['default_role']); 28 | $simpleIdentityProvider->setAuthenticatedRole($config['authenticated_role']); 29 | 30 | return $simpleIdentityProvider; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/Service/ControllerGuardServiceFactoryTest.php: -------------------------------------------------------------------------------- 1 | createMock(ContainerInterface::class); 24 | $config = [ 25 | 'guards' => [ 26 | Controller::class => [], 27 | ], 28 | ]; 29 | 30 | $container 31 | ->expects($this->any()) 32 | ->method('get') 33 | ->with('BjyAuthorize\Config') 34 | ->will($this->returnValue($config)); 35 | 36 | $guard = $factory($container, ControllerGuardServiceFactory::class); 37 | 38 | $this->assertInstanceOf(Controller::class, $guard); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Service/BaseProvidersServiceFactory.php: -------------------------------------------------------------------------------- 1 | get('BjyAuthorize\Config'); 26 | $providers = []; 27 | 28 | foreach ($config[static::PROVIDER_SETTING] as $providerName => $providerConfig) { 29 | if ($container->has($providerName)) { 30 | $providers[] = $container->get($providerName); 31 | } else { 32 | $providers[] = new $providerName($providerConfig, $container); 33 | } 34 | } 35 | 36 | return $providers; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/Service/ConfigRoleProviderServiceFactoryTest.php: -------------------------------------------------------------------------------- 1 | createMock(ContainerInterface::class); 24 | $config = [ 25 | 'role_providers' => [ 26 | Config::class => [], 27 | ], 28 | ]; 29 | 30 | $container 31 | ->expects($this->any()) 32 | ->method('get') 33 | ->with('BjyAuthorize\Config') 34 | ->will($this->returnValue($config)); 35 | 36 | $guard = $factory($container, ConfigRoleProviderServiceFactory::class); 37 | 38 | $this->assertInstanceOf(Config::class, $guard); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /test/Service/RoleCollectorServiceFactoryTest.php: -------------------------------------------------------------------------------- 1 | createMock(ContainerInterface::class); 25 | $identityProvider = $this->createMock(ProviderInterface::class); 26 | 27 | $container 28 | ->expects($this->any()) 29 | ->method('get') 30 | ->with(ProviderInterface::class) 31 | ->will($this->returnValue($identityProvider)); 32 | 33 | $collector = $factory($container, RoleCollectorServiceFactory::class); 34 | 35 | $this->assertInstanceOf(RoleCollector::class, $collector); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test/Service/RouteGuardServiceFactoryTest.php: -------------------------------------------------------------------------------- 1 | createMock(ContainerInterface::class); 25 | $config = [ 26 | 'guards' => [ 27 | Route::class => [], 28 | ], 29 | ]; 30 | 31 | $container 32 | ->expects($this->any()) 33 | ->method('get') 34 | ->with('BjyAuthorize\Config') 35 | ->will($this->returnValue($config)); 36 | 37 | $guard = $factory($container, UnauthorizedStrategyServiceFactory::class); 38 | 39 | $this->assertInstanceOf(Route::class, $guard); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/Service/LaminasDbRoleProviderServiceFactoryTest.php: -------------------------------------------------------------------------------- 1 | createMock(ContainerInterface::class); 24 | $config = [ 25 | 'role_providers' => [ 26 | LaminasDb::class => [], 27 | ], 28 | ]; 29 | 30 | $container 31 | ->expects($this->any()) 32 | ->method('get') 33 | ->with('BjyAuthorize\Config') 34 | ->will($this->returnValue($config)); 35 | 36 | $guard = $factory($container, LaminasDbRoleProviderServiceFactory::class); 37 | 38 | $this->assertInstanceOf(LaminasDb::class, $guard); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /test/Service/ConfigResourceProviderServiceFactoryTest.php: -------------------------------------------------------------------------------- 1 | createMock(ContainerInterface::class); 24 | $config = [ 25 | 'resource_providers' => [ 26 | Config::class => [], 27 | ], 28 | ]; 29 | 30 | $container 31 | ->expects($this->any()) 32 | ->method('get') 33 | ->with('BjyAuthorize\Config') 34 | ->will($this->returnValue($config)); 35 | 36 | $guard = $factory($container, ConfigResourceProviderServiceFactory::class); 37 | 38 | $this->assertInstanceOf(Config::class, $guard); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /docs/unauthorized-strategies.md: -------------------------------------------------------------------------------- 1 | # View Strategies 2 | 3 | BjyAuthorize comes with two default view strategies that are used to handle 4 | 5 | * `BjyAuthorize\View\UnauthorizedStrategy` (registered by default) - renders a view with an unauthorized exception message 6 | * `BjyAuthorize\View\RedirectionStrategy` - redirects the user to a configured route or URI 7 | 8 | You can configure the `UnauthorizedStrategy` from the module config. 9 | 10 | If you want to enable the `RedirectionStrategy`, simply attach it to your application's `EventManager` 11 | at bootstrap time: 12 | 13 | 14 | ```php 15 | namespace MyApp; 16 | 17 | use BjyAuthorize\View\RedirectionStrategy; 18 | use Laminas\EventManager\EventInterface; 19 | 20 | class Module 21 | { 22 | public function onBootstrap(EventInterface $e) 23 | { 24 | $application = $e->getTarget(); 25 | $eventManager = $application->getEventManager(); 26 | 27 | $strategy = new RedirectionStrategy(); 28 | 29 | // eventually set the route name (default is LmcUser's login route) 30 | $strategy->setRedirectRoute('my/route/name'); 31 | 32 | // eventually set the URI to be used for redirects 33 | $strategy->setRedirectUri('http://example.org/login'); 34 | 35 | $eventManager->attach($strategy); 36 | } 37 | 38 | // ... 39 | } 40 | ``` 41 | -------------------------------------------------------------------------------- /test/View/Helper/IsAllowedTest.php: -------------------------------------------------------------------------------- 1 | getMockBuilder(Authorize::class)->disableOriginalConstructor()->getMock(); 22 | $authorize 23 | ->expects($this->once()) 24 | ->method('isAllowed') 25 | ->with('test', 'privilege') 26 | ->will($this->returnValue(true)); 27 | 28 | $plugin = new IsAllowed($authorize); 29 | $this->assertTrue($plugin->__invoke('test', 'privilege')); 30 | 31 | $authorize2 = $this->getMockBuilder(Authorize::class)->disableOriginalConstructor()->getMock(); 32 | $authorize2 33 | ->expects($this->once()) 34 | ->method('isAllowed') 35 | ->with('test2', 'privilege2') 36 | ->will($this->returnValue(false)); 37 | 38 | $plugin = new IsAllowed($authorize2); 39 | 40 | $this->assertFalse($plugin->__invoke('test2', 'privilege2')); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Service/LmcUserLaminasDbIdentityProviderServiceFactory.php: -------------------------------------------------------------------------------- 1 | get('lmcuser_laminas_db_adapter')); 27 | /** @var User $userService */ 28 | $userService = $container->get('lmcuser_user_service'); 29 | $config = $container->get('BjyAuthorize\Config'); 30 | 31 | $provider = new LmcUserLaminasDb($tableGateway, $userService); 32 | 33 | $provider->setDefaultRole($config['default_role']); 34 | 35 | return $provider; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test/Service/UnauthorizedStrategyServiceFactoryTest.php: -------------------------------------------------------------------------------- 1 | createMock(ContainerInterface::class); 24 | $config = [ 25 | 'template' => 'foo/bar', 26 | ]; 27 | 28 | $containerInterface 29 | ->expects($this->any()) 30 | ->method('get') 31 | ->with('BjyAuthorize\Config') 32 | ->will($this->returnValue($config)); 33 | 34 | $strategy = $factory($containerInterface, UnauthorizedStrategyServiceFactory::class); 35 | 36 | $this->assertInstanceOf(UnauthorizedStrategy::class, $strategy); 37 | $this->assertSame('foo/bar', $strategy->getTemplate()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /test/Controller/Plugin/IsAllowedTest.php: -------------------------------------------------------------------------------- 1 | getMockBuilder(Authorize::class)->disableOriginalConstructor()->getMock(); 22 | $authorize 23 | ->expects($this->once()) 24 | ->method('isAllowed') 25 | ->with('test', 'privilege') 26 | ->will($this->returnValue(true)); 27 | 28 | $plugin = new IsAllowed($authorize); 29 | $this->assertTrue($plugin->__invoke('test', 'privilege')); 30 | 31 | $authorize2 = $this->getMockBuilder(Authorize::class)->disableOriginalConstructor()->getMock(); 32 | $authorize2 33 | ->expects($this->once()) 34 | ->method('isAllowed') 35 | ->with('test2', 'privilege2') 36 | ->will($this->returnValue(false)); 37 | 38 | $plugin = new IsAllowed($authorize2); 39 | 40 | $this->assertFalse($plugin->__invoke('test2', 'privilege2')); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/Service/CacheFactoryTest.php: -------------------------------------------------------------------------------- 1 | [ 24 | 'adapter' => [ 25 | 'name' => 'memory', 26 | ], 27 | 'plugins' => [ 28 | [ 29 | 'name' => 'serializer', 30 | ], 31 | ], 32 | ], 33 | ]; 34 | 35 | $container = new ServiceManager(); 36 | $container->setService('BjyAuthorize\Config', $config); 37 | $container->setService( 38 | StorageAdapterFactoryInterface::class, 39 | $this->getMockBuilder(StorageAdapterFactoryInterface::class) 40 | ->getMock() 41 | ); 42 | 43 | $factory = new CacheFactory(); 44 | 45 | $this->assertIsObject($factory($container, CacheFactory::class)); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Service/CacheFactory.php: -------------------------------------------------------------------------------- 1 | get(StorageAdapterFactoryInterface::class); 27 | 28 | $cacheOptions = $container->get('BjyAuthorize\Config')['cache_options']; 29 | 30 | $plugins = []; 31 | foreach ($cacheOptions['plugins'] as $plugin) { 32 | if (is_array($plugin)) { 33 | $plugins[] = $plugin; 34 | } else { 35 | $plugins[] = [ 36 | 'name' => $plugin, 37 | ]; 38 | } 39 | } 40 | 41 | return $storageFactory->create( 42 | $cacheOptions['adapter']['name'], 43 | $cacheOptions['options'] ?? [], 44 | $plugins 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /test/Service/IdentityProviderServiceFactoryTest.php: -------------------------------------------------------------------------------- 1 | createMock(ContainerInterface::class); 24 | $identityProvider = $this->createMock(ProviderInterface::class); 25 | $config = ['identity_provider' => 'foo']; 26 | 27 | $container 28 | ->expects($this->any()) 29 | ->method('get') 30 | ->with($this->logicalOr('BjyAuthorize\\Config', 'foo')) 31 | ->will( 32 | $this->returnCallback( 33 | function ($serviceName) use ($identityProvider, $config) { 34 | if ('BjyAuthorize\\Config' === $serviceName) { 35 | return $config; 36 | } 37 | 38 | return $identityProvider; 39 | } 40 | ) 41 | ); 42 | 43 | $this->assertSame($identityProvider, $factory($container, IdentityProviderServiceFactory::class)); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Provider/Role/ObjectRepositoryProvider.php: -------------------------------------------------------------------------------- 1 | objectRepository = $objectRepository; 25 | } 26 | 27 | /** 28 | * {@inheritDoc} 29 | */ 30 | public function getRoles() 31 | { 32 | $result = $this->objectRepository->findAll(); 33 | $roles = []; 34 | 35 | // Pass One: Build each object 36 | foreach ($result as $role) { 37 | if (! $role instanceof RoleInterface) { 38 | continue; 39 | } 40 | 41 | $roleId = $role->getRoleId(); 42 | $parent = null; 43 | 44 | if ($role instanceof HierarchicalRoleInterface && $parent = $role->getParent()) { 45 | $parent = $parent->getRoleId(); 46 | } 47 | 48 | $roles[$roleId] = new Role($roleId, $parent); 49 | } 50 | 51 | // Pass Two: Re-inject parent objects to preserve hierarchy 52 | /** @var Role $roleObj */ 53 | foreach ($roles as $roleObj) { 54 | $parentRoleObj = $roleObj->getParent(); 55 | 56 | if ($parentRoleObj && $parentRoleObj->getRoleId()) { 57 | $roleObj->setParent($roles[$parentRoleObj->getRoleId()]); 58 | } 59 | } 60 | 61 | return array_values($roles); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Service/ObjectRepositoryRoleProviderFactory.php: -------------------------------------------------------------------------------- 1 | get('BjyAuthorize\Config'); 26 | 27 | if (! isset($config['role_providers'][ObjectRepositoryProvider::class])) { 28 | throw new InvalidArgumentException( 29 | 'Config for "BjyAuthorize\Provider\Role\ObjectRepositoryProvider" not set' 30 | ); 31 | } 32 | 33 | $providerConfig = $config['role_providers'][ObjectRepositoryProvider::class]; 34 | 35 | if (! isset($providerConfig['role_entity_class'])) { 36 | throw new InvalidArgumentException('role_entity_class not set in the bjyauthorize role_providers config.'); 37 | } 38 | 39 | if (! isset($providerConfig['object_manager'])) { 40 | throw new InvalidArgumentException('object_manager not set in the bjyauthorize role_providers config.'); 41 | } 42 | 43 | /** @var ObjectManager $objectManager */ 44 | $objectManager = $container->get($providerConfig['object_manager']); 45 | 46 | return new ObjectRepositoryProvider($objectManager->getRepository($providerConfig['role_entity_class'])); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /test/Provider/Role/ConfigTest.php: -------------------------------------------------------------------------------- 1 | [], 26 | 'role2', 27 | 'role3' => [ 28 | 'children' => ['role4'], 29 | ], 30 | 'role5' => [ 31 | 'children' => [ 32 | 'role6', 33 | 'role7' => [], 34 | ], 35 | ], 36 | ] 37 | ); 38 | 39 | $roles = $config->getRoles(); 40 | 41 | $this->assertCount(7, $roles); 42 | 43 | /** @var Role $role */ 44 | foreach ($roles as $role) { 45 | $this->assertInstanceOf(Role::class, $role); 46 | $this->assertContains( 47 | $role->getRoleId(), 48 | ['role1', 'role2', 'role3', 'role4', 'role5', 'role6', 'role7'] 49 | ); 50 | 51 | if ('role4' === $role->getRoleId()) { 52 | $this->assertNotNull($role->getParent()); 53 | $this->assertSame('role3', $role->getParent()->getRoleId()); 54 | } elseif ('role6' === $role->getRoleId() || 'role7' === $role->getRoleId()) { 55 | $this->assertNotNull($role->getParent()); 56 | $this->assertSame('role5', $role->getParent()->getRoleId()); 57 | } else { 58 | $this->assertNull($role->getParent()); 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Provider/Role/Config.php: -------------------------------------------------------------------------------- 1 | $value) { 30 | if (is_numeric($key)) { 31 | $roles = array_merge($roles, $this->loadRole($value)); 32 | } else { 33 | $roles = array_merge($roles, $this->loadRole($key, $value)); 34 | } 35 | } 36 | 37 | $this->roles = $roles; 38 | } 39 | 40 | /** 41 | * @param string $name 42 | * @param array $options 43 | * @param string|null $parent 44 | * @return array 45 | */ 46 | protected function loadRole($name, $options = [], $parent = null) 47 | { 48 | if (isset($options['children']) && count($options['children']) > 0) { 49 | $children = $options['children']; 50 | } else { 51 | $children = []; 52 | } 53 | 54 | $roles = []; 55 | $role = new Role($name, $parent); 56 | $roles[] = $role; 57 | 58 | foreach ($children as $key => $value) { 59 | if (is_numeric($key)) { 60 | $roles = array_merge($roles, $this->loadRole($value, [], $role)); 61 | } else { 62 | $roles = array_merge($roles, $this->loadRole($key, $value, $role)); 63 | } 64 | } 65 | 66 | return $roles; 67 | } 68 | 69 | /** 70 | * {@inheritDoc} 71 | */ 72 | public function getRoles() 73 | { 74 | return $this->roles; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /test/Service/CacheKeyGeneratorFactoryTest.php: -------------------------------------------------------------------------------- 1 | createMock(ContainerInterface::class); 24 | $config = []; 25 | 26 | $container 27 | ->expects($this->any()) 28 | ->method('get') 29 | ->with('BjyAuthorize\Config') 30 | ->will($this->returnValue($config)); 31 | 32 | $factory = new CacheKeyGeneratorFactory(); 33 | 34 | $cacheKeyGenerator = $factory($container, CacheKeyGeneratorFactory::class); 35 | $this->assertTrue(is_callable($cacheKeyGenerator)); 36 | $this->assertEquals('bjyauthorize_acl', $cacheKeyGenerator()); 37 | } 38 | 39 | /** 40 | * @covers \BjyAuthorize\Service\CacheKeyGeneratorFactory::__invoke 41 | */ 42 | public function testInvokeReturnsCacheKeyGeneratorCallable() 43 | { 44 | $container = $this->createMock(ContainerInterface::class); 45 | $config = [ 46 | 'cache_key' => 'some_new_value', 47 | ]; 48 | 49 | $container 50 | ->expects($this->any()) 51 | ->method('get') 52 | ->with('BjyAuthorize\Config') 53 | ->will($this->returnValue($config)); 54 | 55 | $factory = new CacheKeyGeneratorFactory(); 56 | 57 | $cacheKeyGenerator = $factory($container, CacheKeyGeneratorFactory::class); 58 | $this->assertTrue(is_callable($cacheKeyGenerator)); 59 | $this->assertEquals('some_new_value', $cacheKeyGenerator()); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Acl/Role.php: -------------------------------------------------------------------------------- 1 | setRoleId($roleId); 32 | } 33 | if (null !== $parent) { 34 | $this->setParent($parent); 35 | } 36 | } 37 | 38 | /** 39 | * {@inheritDoc} 40 | */ 41 | public function getRoleId() 42 | { 43 | return $this->roleId; 44 | } 45 | 46 | /** 47 | * @param string $roleId 48 | * @return self 49 | */ 50 | public function setRoleId($roleId) 51 | { 52 | $this->roleId = (string) $roleId; 53 | 54 | return $this; 55 | } 56 | 57 | /** 58 | * {@inheritDoc} 59 | */ 60 | public function getParent() 61 | { 62 | return $this->parent; 63 | } 64 | 65 | /** 66 | * @param RoleInterface|string|null $parent 67 | * @throws InvalidRoleException 68 | * @return self 69 | */ 70 | public function setParent($parent) 71 | { 72 | if (null === $parent) { 73 | $this->parent = null; 74 | 75 | return $this; 76 | } 77 | 78 | if (is_string($parent)) { 79 | $this->parent = new Role($parent); 80 | 81 | return $this; 82 | } 83 | 84 | if ($parent instanceof RoleInterface) { 85 | $this->parent = $parent; 86 | 87 | return $this; 88 | } 89 | 90 | throw Exception\InvalidRoleException::invalidRoleInstance($parent); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /data/Role.php.odm.dist: -------------------------------------------------------------------------------- 1 | id; 44 | } 45 | 46 | /** 47 | * Set the id. 48 | * 49 | * @param int $id 50 | * 51 | * @return void 52 | */ 53 | public function setId($id) 54 | { 55 | $this->id = (int) $id; 56 | } 57 | 58 | /** 59 | * Get the role id. 60 | * 61 | * @return string 62 | */ 63 | public function getRoleId() 64 | { 65 | return $this->roleId; 66 | } 67 | 68 | /** 69 | * Set the role id. 70 | * 71 | * @param string $roleId 72 | * 73 | * @return void 74 | */ 75 | public function setRoleId($roleId) 76 | { 77 | $this->roleId = (string) $roleId; 78 | } 79 | 80 | /** 81 | * Get the parent role 82 | * 83 | * @return Role 84 | */ 85 | public function getParent() 86 | { 87 | return $this->parent; 88 | } 89 | 90 | /** 91 | * Set the parent role. 92 | * 93 | * @param Role $role 94 | * 95 | * @return void 96 | */ 97 | public function setParent(Role $parent = null) 98 | { 99 | $this->parent = $parent; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /test/Service/ObjectRepositoryRoleProviderFactoryTest.php: -------------------------------------------------------------------------------- 1 | createMock(ContainerInterface::class); 25 | $entityManager = $this->createMock(ObjectManager::class); 26 | $repository = $this->createMock(ObjectRepository::class); 27 | $factory = new ObjectRepositoryRoleProviderFactory(); 28 | 29 | $testClassName = 'TheTestClass'; 30 | 31 | $config = [ 32 | 'role_providers' => [ 33 | ObjectRepositoryProvider::class => [ 34 | 'role_entity_class' => $testClassName, 35 | 'object_manager' => 'doctrine.entitymanager.orm_default', 36 | ], 37 | ], 38 | ]; 39 | 40 | $entityManager->expects($this->once()) 41 | ->method('getRepository') 42 | ->with($testClassName) 43 | ->will($this->returnValue($repository)); 44 | 45 | $container->expects($this->exactly(2)) 46 | ->method('get') 47 | ->withConsecutive( 48 | ['BjyAuthorize\Config'], 49 | ['doctrine.entitymanager.orm_default'] 50 | ) 51 | ->willReturn( 52 | $this->returnValue($config), 53 | $this->returnValue($entityManager) 54 | ); 55 | 56 | $this->assertInstanceOf( 57 | ObjectRepositoryProvider::class, 58 | $factory($container, ObjectRepositoryRoleProviderFactory::class) 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Module.php: -------------------------------------------------------------------------------- 1 | getTarget(); 31 | /** @var ServiceManager $serviceManager */ 32 | $serviceManager = $app->getServiceManager(); 33 | $config = $serviceManager->get('BjyAuthorize\Config'); 34 | /** @var UnauthorizedStrategy $strategy */ 35 | $strategy = $serviceManager->get($config['unauthorized_strategy']); 36 | /** @var AbstractGuard[] $guards */ 37 | $guards = $serviceManager->get('BjyAuthorize\Guards'); 38 | 39 | // TODO remove in 3.0.0, fix alias 40 | if ($serviceManager instanceof ServiceManager && $serviceManager->has('lmcuser_user_service') === false) { 41 | $serviceManager->setAllowOverride(true); 42 | $serviceManager->setAlias('lmcuser_user_service', 'zfcuser_user_service'); 43 | $serviceManager->setAllowOverride(false); 44 | } 45 | 46 | foreach ($guards as $guard) { 47 | $guard->attach($app->getEventManager()); 48 | } 49 | 50 | $strategy->attach($app->getEventManager()); 51 | } 52 | 53 | /** 54 | * {@inheritDoc} 55 | */ 56 | public function getConfig() 57 | { 58 | return include __DIR__ . '/../config/module.config.php'; 59 | } 60 | 61 | /** 62 | * {@inheritDoc} 63 | */ 64 | public function getModuleDependencies() 65 | { 66 | return [ 67 | 'Laminas\Cache', 68 | ]; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /data/Role.php.dist: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class Role implements HierarchicalRoleInterface 17 | { 18 | /** 19 | * @var int 20 | * @ORM\Id 21 | * @ORM\Column(type="integer") 22 | * @ORM\GeneratedValue(strategy="AUTO") 23 | */ 24 | protected $id; 25 | 26 | /** 27 | * @var string 28 | * @ORM\Column(type="string", name="role_id", length=255, unique=true, nullable=true) 29 | */ 30 | protected $roleId; 31 | 32 | /** 33 | * @var Role 34 | * @ORM\ManyToOne(targetEntity="MyNamespace\Role") 35 | * @ORM\JoinColumn(name="parent_id", referencedColumnName="id", nullable=true) 36 | */ 37 | protected $parent; 38 | 39 | /** 40 | * Get the id. 41 | * 42 | * @return int 43 | */ 44 | public function getId() 45 | { 46 | return $this->id; 47 | } 48 | 49 | /** 50 | * Set the id. 51 | * 52 | * @param int $id 53 | * 54 | * @return void 55 | */ 56 | public function setId($id) 57 | { 58 | $this->id = (int)$id; 59 | } 60 | 61 | /** 62 | * Get the role id. 63 | * 64 | * @return string 65 | */ 66 | public function getRoleId() 67 | { 68 | return $this->roleId; 69 | } 70 | 71 | /** 72 | * Set the role id. 73 | * 74 | * @param string $roleId 75 | * 76 | * @return void 77 | */ 78 | public function setRoleId($roleId) 79 | { 80 | $this->roleId = (string) $roleId; 81 | } 82 | 83 | /** 84 | * Get the parent role 85 | * 86 | * @return Role 87 | */ 88 | public function getParent() 89 | { 90 | return $this->parent; 91 | } 92 | 93 | /** 94 | * Set the parent role. 95 | * 96 | * @param Role $parent 97 | * 98 | * @return void 99 | */ 100 | public function setParent(Role $parent) 101 | { 102 | $this->parent = $parent; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /test/Service/AuthenticationIdentityProviderServiceFactoryTest.php: -------------------------------------------------------------------------------- 1 | 'test-guest', 27 | 'authenticated_role' => 'test-user', 28 | ]; 29 | 30 | $user = $this->getMockBuilder(User::class)->getMock(); 31 | $auth = $this->createMock(AuthenticationService::class); 32 | $container = $this->createMock(ContainerInterface::class); 33 | 34 | $user->expects($this->once())->method('getAuthService')->will($this->returnValue($auth)); 35 | $container 36 | ->expects($this->any()) 37 | ->method('get') 38 | ->with($this->logicalOr('lmcuser_user_service', 'BjyAuthorize\\Config')) 39 | ->will( 40 | $this->returnCallback( 41 | function ($service) use ($user, $config) { 42 | if ('lmcuser_user_service' === $service) { 43 | return $user; 44 | } 45 | 46 | return $config; 47 | } 48 | ) 49 | ); 50 | 51 | $authenticationFactory = new AuthenticationIdentityProviderServiceFactory(); 52 | $authentication = $authenticationFactory( 53 | $container, 54 | AuthenticationIdentityProviderServiceFactory::class 55 | ); 56 | 57 | $this->assertEquals($authentication->getDefaultRole(), 'test-guest'); 58 | $this->assertEquals($authentication->getAuthenticatedRole(), 'test-user'); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Guard/AbstractGuard.php: -------------------------------------------------------------------------------- 1 | container = $container; 33 | foreach ($rules as $rule) { 34 | $rule['roles'] = (array) $rule['roles']; 35 | $rule['action'] = isset($rule['action']) ? (array) $rule['action'] : [null]; 36 | foreach ($this->extractResourcesFromRule($rule) as $resource) { 37 | $this->rules[$resource] = ['roles' => (array) $rule['roles']]; 38 | if (isset($rule['assertion'])) { 39 | $this->rules[$resource]['assertion'] = $rule['assertion']; 40 | } 41 | } 42 | } 43 | } 44 | 45 | abstract protected function extractResourcesFromRule(array $rule); 46 | 47 | /** 48 | * {@inheritDoc} 49 | */ 50 | public function getResources() 51 | { 52 | $resources = []; 53 | foreach (array_keys($this->rules) as $resource) { 54 | $resources[] = $resource; 55 | } 56 | 57 | return $resources; 58 | } 59 | 60 | /** 61 | * {@inheritDoc} 62 | */ 63 | public function getRules() 64 | { 65 | $rules = []; 66 | foreach ($this->rules as $resource => $ruleData) { 67 | $rule = []; 68 | $rule[] = $ruleData['roles']; 69 | $rule[] = $resource; 70 | if (isset($ruleData['assertion'])) { 71 | $rule[] = null; 72 | // no privilege 73 | $rule[] = $ruleData['assertion']; 74 | } 75 | 76 | $rules[] = $rule; 77 | } 78 | 79 | return ['allow' => $rules]; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Provider/Identity/LmcUserLaminasDb.php: -------------------------------------------------------------------------------- 1 | tableGateway = $tableGateway; 35 | $this->userService = $userService; 36 | } 37 | 38 | /** 39 | * {@inheritDoc} 40 | */ 41 | public function getIdentityRoles() 42 | { 43 | $authService = $this->userService->getAuthService(); 44 | 45 | if (! $authService->hasIdentity()) { 46 | return [$this->getDefaultRole()]; 47 | } 48 | 49 | // get roles associated with the logged in user 50 | $sql = new Select(); 51 | 52 | $sql->from($this->tableName); 53 | // @todo these fields should eventually be configurable 54 | $sql->join('user_role', 'user_role.id = ' . $this->tableName . '.role_id'); 55 | $sql->where(['user_id' => $authService->getIdentity()->getId()]); 56 | 57 | $results = $this->tableGateway->selectWith($sql); 58 | 59 | $roles = []; 60 | 61 | foreach ($results as $role) { 62 | $roles[] = $role['role_id']; 63 | } 64 | 65 | return $roles; 66 | } 67 | 68 | /** 69 | * @return string|RoleInterface 70 | */ 71 | public function getDefaultRole() 72 | { 73 | return $this->defaultRole; 74 | } 75 | 76 | /** 77 | * @param string|RoleInterface $defaultRole 78 | * @throws InvalidRoleException 79 | */ 80 | public function setDefaultRole($defaultRole) 81 | { 82 | if (! ($defaultRole instanceof RoleInterface || is_string($defaultRole))) { 83 | throw InvalidRoleException::invalidRoleInstance($defaultRole); 84 | } 85 | 86 | $this->defaultRole = $defaultRole; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /test/Service/AuthorizeAwareServiceInitializerTest.php: -------------------------------------------------------------------------------- 1 | authorize = $this->getMockBuilder(Authorize::class)->disableOriginalConstructor()->getMock(); 36 | $this->container = $this->createMock(ContainerInterface::class); 37 | $this->initializer = new AuthorizeAwareServiceInitializer(); 38 | 39 | $this->container->expects($this->any())->method('get')->will($this->returnValue($this->authorize)); 40 | } 41 | 42 | /** 43 | * {@inheritDoc} 44 | * 45 | * @see \PHPUnit\Framework\TestCase::tearDown() 46 | */ 47 | protected function tearDown(): void 48 | { 49 | unset($this->initializer); 50 | unset($this->container); 51 | unset($this->authorize); 52 | } 53 | 54 | /** 55 | * @covers \BjyAuthorize\Service\AuthorizeAwareServiceInitializer::__invoke 56 | */ 57 | public function testInitializeWithAuthorizeAwareObject() 58 | { 59 | $awareObject = $this->createMock(AuthorizeAwareInterface::class); 60 | 61 | $awareObject->expects($this->once())->method('setAuthorizeService')->with($this->authorize); 62 | 63 | $initializer = $this->initializer; 64 | $initializer($this->container, $awareObject); 65 | } 66 | 67 | /** 68 | * @covers \BjyAuthorize\Service\AuthorizeAwareServiceInitializer::__invoke 69 | */ 70 | public function testInitializeWithSimpleObject() 71 | { 72 | $awareObject = $this->getMockBuilder('stdClass')->getMock(); 73 | 74 | $this->container->expects($this->never())->method('get'); 75 | 76 | $initializer = $this->initializer; 77 | $initializer($this->container, $awareObject); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Guard/Route.php: -------------------------------------------------------------------------------- 1 | listeners[] = $events->attach(MvcEvent::EVENT_ROUTE, [$this, 'onRoute'], -1000); 41 | } 42 | 43 | /** 44 | * Event callback to be triggered on dispatch, causes application error triggering 45 | * in case of failed authorization check 46 | * 47 | * @return mixed 48 | */ 49 | public function onRoute(MvcEvent $event) 50 | { 51 | /** @var Authorize $service */ 52 | $service = $this->container->get(Authorize::class); 53 | $match = $event->getRouteMatch(); 54 | $routeName = $match->getMatchedRouteName(); 55 | 56 | if ( 57 | $service->isAllowed('route/' . $routeName) 58 | || (class_exists(ConsoleRequest::class) 59 | && $event->getRequest() instanceof ConsoleRequest) 60 | ) { 61 | return; 62 | } 63 | 64 | $event->setError(static::ERROR); 65 | $event->setParam('route', $routeName); 66 | $event->setParam('identity', $service->getIdentity()); 67 | $event->setParam( 68 | 'exception', 69 | new UnAuthorizedException('You are not authorized to access ' . $routeName) 70 | ); 71 | 72 | /** @var Application $app */ 73 | $app = $event->getTarget(); 74 | $eventManager = $app->getEventManager(); 75 | 76 | $event->setName(MvcEvent::EVENT_DISPATCH_ERROR); 77 | $results = $eventManager->triggerEvent($event); 78 | 79 | $return = $results->last(); 80 | if (! $return) { 81 | return $event->getResult(); 82 | } 83 | 84 | return $return; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /test/Acl/RoleTest.php: -------------------------------------------------------------------------------- 1 | assertSame('test1', $role->getRoleId()); 27 | $this->assertNull($role->getParent()); 28 | 29 | $role = new Role('test2', 'parent'); 30 | 31 | $this->assertSame('test2', $role->getRoleId()); 32 | $parent = $role->getParent(); 33 | $this->assertNotNull($parent); 34 | $this->assertSame('parent', $parent->getRoleId()); 35 | } 36 | 37 | /** 38 | * @covers \BjyAuthorize\Acl\Role::setRoleId 39 | * @covers \BjyAuthorize\Acl\Role::getRoleId 40 | */ 41 | public function testSetGetRoleId() 42 | { 43 | $role = new Role('test1'); 44 | 45 | $this->assertSame('test1', $role->getRoleId()); 46 | $role->setRoleId('test2'); 47 | $this->assertSame('test2', $role->getRoleId()); 48 | } 49 | 50 | /** 51 | * @covers \BjyAuthorize\Acl\Role::setParent 52 | * @covers \BjyAuthorize\Acl\Role::getParent 53 | */ 54 | public function testSetGetParent() 55 | { 56 | $role = new Role('test1'); 57 | $parent = new Role('parent'); 58 | 59 | $role->setParent($parent); 60 | $this->assertSame($parent, $role->getParent()); 61 | 62 | $role->setParent('parent2'); 63 | $this->assertNotSame($parent, $role->getParent()); 64 | $this->assertSame('parent2', $role->getParent()->getRoleId()); 65 | } 66 | 67 | /** 68 | * @covers \BjyAuthorize\Acl\Role::setParent 69 | * @covers \BjyAuthorize\Acl\Role::getParent 70 | */ 71 | public function testSetParentWithNull() 72 | { 73 | $parent = new Role('parent'); 74 | $role = new Role('test1', $parent); 75 | 76 | $this->assertSame($parent, $role->getParent()); 77 | 78 | $role->setParent(null); 79 | $this->assertNull($role->getParent()); 80 | } 81 | 82 | /** 83 | * @covers \BjyAuthorize\Acl\Role::setParent 84 | */ 85 | public function testSetInvalidParent() 86 | { 87 | $role = new Role('test1'); 88 | 89 | $this->expectException(InvalidRoleException::class); 90 | $role->setParent(new stdClass()); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /view/laminas-developer-tools/toolbar/bjy-authorize-role.phtml: -------------------------------------------------------------------------------- 1 | getCollectedRoles(); 4 | $rolesCount = count($roles); 5 | ?> 6 |
7 |
8 | BjyAuthorize Current Identity Roles 9 | 10 | 11 | escapeHtml(reset($roles)); 14 | } elseif ($rolesCount > 1) { 15 | echo $rolesCount . ' roles'; 16 | } 17 | ?> 18 | 19 | 20 |
21 |
22 | 23 | BjyAuthorize 24 |
25 | Identity Roles - 34 |
35 | 36 |
37 | 38 | 39 | escapeHtml($role) ?> 40 | 41 | 42 | 43 |
44 |
-------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kokspflanze/bjy-authorize", 3 | "description": "Laminas\\Acl based firewall system for Laminas dispatch protection", 4 | "type": "library", 5 | "license": "BSD-3-Clause", 6 | "homepage": "https://github.com/kokspflanze/BjyAuthorize", 7 | "keywords": [ 8 | "laminas", 9 | "acl", 10 | "authorization", 11 | "lmc-user" 12 | ], 13 | "authors": [ 14 | { 15 | "name": "Ben Youngblood", 16 | "email": "bx.youngblood@gmail.com", 17 | "homepage": "http://bjyoungblood.com/", 18 | "role": "Developer" 19 | }, 20 | { 21 | "name": "Marco Pivetta", 22 | "email": "ocramius@gmail.com", 23 | "homepage": "http://ocramius.github.com/", 24 | "role": "Developer" 25 | } 26 | ], 27 | "require": { 28 | "php": "^7.3 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0", 29 | "laminas/laminas-permissions-acl": "^2.8.0", 30 | "laminas/laminas-mvc": "^3.2.0", 31 | "laminas/laminas-eventmanager": "^3.4.0", 32 | "laminas/laminas-servicemanager": "^3.7.0", 33 | "laminas/laminas-http": "^2.15.0", 34 | "laminas/laminas-view": "^2.14.2", 35 | "laminas/laminas-authentication": "^2.8.0", 36 | "laminas/laminas-cache": "^2.13.2 || ^3.1.0" 37 | }, 38 | "require-dev": { 39 | "phpunit/phpunit": "^9.5.9", 40 | "laminas/laminas-coding-standard": "^2.3.0", 41 | "laminas/laminas-db": "^2.13.4", 42 | "doctrine/persistence": "^1.3.8 || ^2.2.2", 43 | "laminas/laminas-developer-tools": "^2.1.1", 44 | "lm-commons/lmc-user": "^3.5.0", 45 | "laminas/laminas-cache-storage-adapter-memory" : "@stable" 46 | }, 47 | "suggests": { 48 | "laminas/laminas-developer-tools": "if you need to see current authorization details while developing", 49 | "lm-commons/lmc-user": "LmcUser provides a good default setup to get started with bjyauthorize", 50 | "lm-commons/lmc-user-doctrine-orm": "To support Doctrine with LmcUser" 51 | }, 52 | "autoload": { 53 | "psr-4": { 54 | "BjyAuthorize\\": "src/" 55 | } 56 | }, 57 | "autoload-dev": { 58 | "psr-4": { 59 | "BjyAuthorizeTest\\": "test/" 60 | } 61 | }, 62 | "config": { 63 | "sort-packages": true, 64 | "allow-plugins": { 65 | "composer/package-versions-deprecated": true, 66 | "dealerdirect/phpcodesniffer-composer-installer": true 67 | } 68 | }, 69 | "scripts": { 70 | "check": [ 71 | "@cs-check", 72 | "@test" 73 | ], 74 | "cs-check": "phpcs", 75 | "cs-fix": "phpcbf", 76 | "test": "phpunit --colors=always --configuration phpunit.xml.dist" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /test/Provider/Role/LaminasDbTest.php: -------------------------------------------------------------------------------- 1 | serviceLocator = $this->createMock(ServiceLocatorInterface::class); 35 | $this->provider = new LaminasDb([], $this->serviceLocator); 36 | $this->tableGateway = $this->getMockBuilder(TableGateway::class) 37 | ->disableOriginalConstructor() 38 | ->getMock(); 39 | } 40 | 41 | /** 42 | * @covers \BjyAuthorize\Provider\Role\LaminasDb::getRoles 43 | */ 44 | public function testGetRoles() 45 | { 46 | $this->tableGateway->expects($this->any())->method('selectWith')->will( 47 | $this->returnValue( 48 | [ 49 | ['id' => 1, 'role_id' => 'guest', 'is_default' => 1, 'parent_id' => null], 50 | ['id' => 2, 'role_id' => 'user', 'is_default' => 0, 'parent_id' => null], 51 | ] 52 | ) 53 | ); 54 | 55 | $this->serviceLocator->expects($this->any())->method('get')->will($this->returnValue($this->tableGateway)); 56 | $provider = new LaminasDb([], $this->serviceLocator); 57 | 58 | $this->assertEquals($provider->getRoles(), [new Role('guest'), new Role('user')]); 59 | } 60 | 61 | /** 62 | * @covers \BjyAuthorize\Provider\Role\LaminasDb::getRoles 63 | */ 64 | public function testGetRolesWithInheritance() 65 | { 66 | $this->tableGateway->expects($this->any())->method('selectWith')->will( 67 | $this->returnValue( 68 | [ 69 | ['id' => 1, 'role_id' => 'guest', 'is_default' => 1, 'parent_id' => null], 70 | ['id' => 2, 'role_id' => 'user', 'is_default' => 0, 'parent_id' => 1], 71 | ] 72 | ) 73 | ); 74 | 75 | $this->serviceLocator->expects($this->any())->method('get')->will($this->returnValue($this->tableGateway)); 76 | $provider = new LaminasDb([], $this->serviceLocator); 77 | 78 | $this->assertEquals($provider->getRoles(), [new Role('guest'), new Role('user', 'guest')]); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /docs/doctrine.md: -------------------------------------------------------------------------------- 1 | # Using BjyAuthorize with Doctrine 2 | 3 | If you wish to use Doctrine 2 ORM entities (ORM) or MongoDB ODM Documents (ODM), all you will need to do is 4 | having your authentication identity implement either `Laminas\Permissions\Acl\Role\RoleInterface` or 5 | `BjyAuthorize\Provider\Role\ProviderInterface`. 6 | 7 | ## BjyAuthorize, LmcUser and LmcUserDoctrineORM 8 | 9 | Here's some simple steps to do this specifically with `LmcUserDoctrineORM`, though any authentication service 10 | will work too: 11 | 12 | 13 | ### Installation 14 | 15 | Install and enable `LmcUser` and `LmcUserDoctrineORM`: 16 | 17 | ```sh 18 | php composer.phar require lm-commons/lmc-user-doctrine-orm 19 | ``` 20 | 21 | You will obviously need to enable all the involved modules 22 | 23 | ### Implement a `MyNamespace\User` and a `MyNamespace\Role` entities 24 | 25 | Implement a `MyNamespace\User` and a `MyNamespace\Role` entity. 26 | You can use the [`User.php.dist`](https://github.com/kokspflanze/BjyAuthorize/blob/master/data/User.php.dist) 27 | and [`Role.php.dist`](https://github.com/kokspflanze/BjyAuthorize/blob/master/data/Role.php.dist) files as blueprint. 28 | 29 | ### Configuration 30 | 31 | You will need to override the settings of `LmcUserDoctrineORM` to use the entities you defined: 32 | 33 | ```php 34 | return array( 35 | 'doctrine' => array( 36 | 'driver' => array( 37 | // overriding lmc-user-doctrine-orm's config 38 | 'lmcuser_entity' => array( 39 | 'class' => 'Doctrine\ORM\Mapping\Driver\AnnotationDriver', 40 | 'paths' => 'path/to/your/entities/dir', 41 | ), 42 | 43 | 'orm_default' => array( 44 | 'drivers' => array( 45 | 'MyNamespace' => 'lmcuser_entity', 46 | ), 47 | ), 48 | ), 49 | ), 50 | 51 | 'lmcuser' => array( 52 | // telling LmcUser to use our own class 53 | 'user_entity_class' => 'MyNamespace\User', 54 | // telling LmcUserDoctrineORM to skip the entities it defines 55 | 'enable_default_entities' => false, 56 | ), 57 | 58 | 'bjyauthorize' => array( 59 | // Using the authentication identity provider, which basically reads the roles from the auth service's identity 60 | 'identity_provider' => 'BjyAuthorize\Provider\Identity\AuthenticationIdentityProvider', 61 | 62 | 'role_providers' => array( 63 | // using an object repository (entity repository) to load all roles into our ACL 64 | 'BjyAuthorize\Provider\Role\ObjectRepositoryProvider' => array( 65 | 'object_manager' => 'doctrine.entitymanager.orm_default', 66 | 'role_entity_class' => 'MyNamespace\Role', 67 | ), 68 | ), 69 | ), 70 | ); 71 | ``` 72 | 73 | This setup will simply check the current identity: if none is set, it will use the default role configured in the 74 | `BjyAuthorize\Provider\Identity\AuthenticationIdentityProvider`, otherwise it will try to extract the roles from 75 | your user object. -------------------------------------------------------------------------------- /test/Provider/Identity/LmcUserLaminasDbTest.php: -------------------------------------------------------------------------------- 1 | authService = $this->createMock(AuthenticationService::class); 41 | $this->userService = $this->getMockBuilder(User::class)->getMock(); 42 | $this->tableGateway = $this->getMockBuilder(TableGateway::class) 43 | ->disableOriginalConstructor() 44 | ->getMock(); 45 | 46 | $this 47 | ->userService 48 | ->expects($this->any()) 49 | ->method('getAuthService') 50 | ->will($this->returnValue($this->authService)); 51 | 52 | $this->provider = new LmcUserLaminasDb($this->tableGateway, $this->userService); 53 | } 54 | 55 | /** 56 | * @covers \BjyAuthorize\Provider\Identity\LmcUserLaminasDb::getIdentityRoles 57 | * @covers \BjyAuthorize\Provider\Identity\LmcUserLaminasDb::setDefaultRole 58 | */ 59 | public function testGetIdentityRolesWithNoAuthIdentity() 60 | { 61 | $this->provider->setDefaultRole('test-default'); 62 | 63 | $this->assertSame(['test-default'], $this->provider->getIdentityRoles()); 64 | } 65 | 66 | /** 67 | * @covers \BjyAuthorize\Provider\Identity\LmcUserLaminasDb::getIdentityRoles 68 | */ 69 | public function testSetGetDefaultRole() 70 | { 71 | $this->provider->setDefaultRole('test'); 72 | $this->assertSame('test', $this->provider->getDefaultRole()); 73 | 74 | $role = $this->createMock(RoleInterface::class); 75 | $this->provider->setDefaultRole($role); 76 | $this->assertSame($role, $this->provider->getDefaultRole()); 77 | 78 | $this->expectException(InvalidRoleException::class); 79 | $this->provider->setDefaultRole(false); 80 | } 81 | 82 | /** 83 | * @covers \BjyAuthorize\Provider\Identity\LmcUserLaminasDb::getIdentityRoles 84 | */ 85 | public function testGetIdentityRoles() 86 | { 87 | $roles = $this->provider->getIdentityRoles(); 88 | $this->assertEquals($roles, [null]); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Provider/Role/LaminasDb.php: -------------------------------------------------------------------------------- 1 | serviceLocator = $serviceLocator; 40 | 41 | if (isset($options['table'])) { 42 | $this->tableName = $options['table']; 43 | } 44 | 45 | if (isset($options['identifier_field_name'])) { 46 | $this->identifierFieldName = $options['identifier_field_name']; 47 | } 48 | 49 | if (isset($options['role_id_field'])) { 50 | $this->roleIdFieldName = $options['role_id_field']; 51 | } 52 | 53 | if (isset($options['parent_role_field'])) { 54 | $this->parentRoleFieldName = $options['parent_role_field']; 55 | } 56 | } 57 | 58 | /** 59 | * {@inheritDoc} 60 | */ 61 | public function getRoles() 62 | { 63 | /** @var TableGateway $tableGateway */ 64 | $tableGateway = $this->serviceLocator->get('BjyAuthorize\Service\RoleDbTableGateway'); 65 | $sql = new Select(); 66 | 67 | $sql->from($this->tableName); 68 | 69 | /** @var Role[] $roles */ 70 | $roles = []; 71 | $indexedRows = []; 72 | $rowset = $tableGateway->selectWith($sql); 73 | 74 | // Pass 1: collect all rows and index them by PK 75 | foreach ($rowset as $row) { 76 | $indexedRows[$row[$this->identifierFieldName]] = $row; 77 | } 78 | 79 | // Pass 2: build a role for each indexed row 80 | foreach ($indexedRows as $row) { 81 | $parentRoleId = isset($row[$this->parentRoleFieldName]) 82 | ? $indexedRows[$row[$this->parentRoleFieldName]][$this->roleIdFieldName] : null; 83 | $roleId = $row[$this->roleIdFieldName]; 84 | $roles[$roleId] = new Role($roleId, $parentRoleId); 85 | } 86 | 87 | // Pass 3: Re-inject parent objects to preserve hierarchy 88 | foreach ($roles as $role) { 89 | $parentRoleObj = $role->getParent(); 90 | 91 | if ($parentRoleObj && ($parentRoleId = $parentRoleObj->getRoleId())) { 92 | $role->setParent($roles[$parentRoleId]); 93 | } 94 | } 95 | 96 | return array_values($roles); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/Collector/RoleCollector.php: -------------------------------------------------------------------------------- 1 | identityProvider = $identityProvider; 36 | } 37 | 38 | /** 39 | * {@inheritDoc} 40 | */ 41 | public function getName() 42 | { 43 | return static::NAME; 44 | } 45 | 46 | /** 47 | * {@inheritDoc} 48 | */ 49 | public function getPriority() 50 | { 51 | return static::PRIORITY; 52 | } 53 | 54 | /** 55 | * {@inheritDoc} 56 | */ 57 | public function collect(MvcEvent $mvcEvent) 58 | { 59 | if (! $this->identityProvider) { 60 | return; 61 | } 62 | 63 | $roles = $this->identityProvider->getIdentityRoles(); 64 | 65 | if (! is_array($roles) && ! $roles instanceof Traversable) { 66 | $roles = (array) $roles; 67 | } 68 | 69 | foreach ($roles as $role) { 70 | if ($role instanceof RoleInterface) { 71 | $role = $role->getRoleId(); 72 | } 73 | 74 | if ($role) { 75 | $this->collectedRoles[] = (string) $role; 76 | } 77 | } 78 | } 79 | 80 | /** 81 | * @return array|string[] 82 | */ 83 | public function getCollectedRoles() 84 | { 85 | return $this->collectedRoles; 86 | } 87 | 88 | /** 89 | * {@inheritDoc} 90 | * TODO remove with php74+ 91 | */ 92 | public function serialize() 93 | { 94 | return serialize($this->collectedRoles); 95 | } 96 | 97 | /** 98 | * {@inheritDoc} 99 | * TODO remove with php74+ 100 | */ 101 | public function unserialize($serialized) 102 | { 103 | $this->collectedRoles = unserialize($serialized); 104 | } 105 | 106 | /** 107 | * {@inheritDoc} 108 | */ 109 | public function __serialize() 110 | { 111 | return [ 112 | 'collectedRoles' => $this->collectedRoles, 113 | ]; 114 | } 115 | 116 | /** 117 | * {@inheritDoc} 118 | */ 119 | public function __unserialize(array $serialized) 120 | { 121 | $this->collectedRoles = $serialized['collectedRoles']; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/Provider/Identity/AuthenticationIdentityProvider.php: -------------------------------------------------------------------------------- 1 | authService = $authService; 31 | } 32 | 33 | /** 34 | * {@inheritDoc} 35 | */ 36 | public function getIdentityRoles() 37 | { 38 | if (! $identity = $this->authService->getIdentity()) { 39 | return [$this->defaultRole]; 40 | } 41 | 42 | if ($identity instanceof RoleInterface) { 43 | return [$identity]; 44 | } 45 | 46 | if ($identity instanceof RoleProviderInterface) { 47 | return $identity->getRoles(); 48 | } 49 | 50 | return [$this->authenticatedRole]; 51 | } 52 | 53 | /** 54 | * Get the rule that's used if you're not authenticated 55 | * 56 | * @return string|RoleInterface 57 | */ 58 | public function getDefaultRole() 59 | { 60 | return $this->defaultRole; 61 | } 62 | 63 | /** 64 | * Set the rule that's used if you're not authenticated 65 | * 66 | * @param string|RoleInterface $defaultRole 67 | * @throws InvalidRoleException 68 | */ 69 | public function setDefaultRole($defaultRole) 70 | { 71 | if (! ($defaultRole instanceof RoleInterface || is_string($defaultRole))) { 72 | throw InvalidRoleException::invalidRoleInstance($defaultRole); 73 | } 74 | 75 | $this->defaultRole = $defaultRole; 76 | } 77 | 78 | /** 79 | * Get the role that is used if you're authenticated and the identity provides no role 80 | * 81 | * @return string|RoleInterface 82 | */ 83 | public function getAuthenticatedRole() 84 | { 85 | return $this->authenticatedRole; 86 | } 87 | 88 | /** 89 | * Set the role that is used if you're authenticated and the identity provides no role 90 | * 91 | * @param string|RoleInterface $authenticatedRole 92 | * @throws InvalidRoleException 93 | */ 94 | public function setAuthenticatedRole($authenticatedRole) 95 | { 96 | if (! ($authenticatedRole instanceof RoleInterface || is_string($authenticatedRole))) { 97 | throw InvalidRoleException::invalidRoleInstance($authenticatedRole); 98 | } 99 | 100 | $this->authenticatedRole = $authenticatedRole; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /test/Service/BaseProvidersServiceFactoryTest.php: -------------------------------------------------------------------------------- 1 | getMockForAbstractClass(BaseProvidersServiceFactory::class); 28 | $container = $this->createMock(ContainerInterface::class); 29 | $foo = $this->createMock(ProviderInterface::class); 30 | $bar = $this->createMock(ProviderInterface::class); 31 | $config = [ 32 | 'providers' => [ 33 | 'foo' => [], 34 | 'bar' => [], 35 | __NAMESPACE__ . '\\MockProvider' => ['option' => 'value'], 36 | ], 37 | ]; 38 | 39 | $container 40 | ->expects($this->any()) 41 | ->method('has') 42 | ->will( 43 | $this->returnCallback( 44 | function ($serviceName) { 45 | return in_array($serviceName, ['foo', 'bar'], true); 46 | } 47 | ) 48 | ); 49 | 50 | $container 51 | ->expects($this->any()) 52 | ->method('get') 53 | ->with($this->logicalOr('BjyAuthorize\\Config', 'foo', 'bar')) 54 | ->will( 55 | $this->returnCallback( 56 | function ($serviceName) use ($foo, $bar, $config) { 57 | if ('BjyAuthorize\\Config' === $serviceName) { 58 | return $config; 59 | } 60 | 61 | if ('foo' === $serviceName) { 62 | return $foo; 63 | } 64 | 65 | return $bar; 66 | } 67 | ) 68 | ); 69 | 70 | $providers = $factory($container, BaseProvidersServiceFactory::class); 71 | 72 | $this->assertCount(3, $providers); 73 | $this->assertContains($foo, $providers); 74 | $this->assertContains($bar, $providers); 75 | 76 | $invokableProvider = array_filter( 77 | $providers, 78 | function ($item) { 79 | return $item instanceof MockProvider; 80 | } 81 | ); 82 | 83 | $this->assertCount(1, $invokableProvider); 84 | 85 | /** @var MockProvider $invokableProvider */ 86 | $invokableProvider = array_shift($invokableProvider); 87 | 88 | $this->assertInstanceOf(__NAMESPACE__ . '\\MockProvider', $invokableProvider); 89 | 90 | $this->assertSame(['option' => 'value'], $invokableProvider->options); 91 | $this->assertSame($container, $invokableProvider->container); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /test/Provider/Role/ObjectRepositoryProviderTest.php: -------------------------------------------------------------------------------- 1 | repository = $this->createMock(ObjectRepository::class); 32 | $this->provider = new ObjectRepositoryProvider($this->repository); 33 | } 34 | 35 | /** 36 | * @param string $name 37 | * @param string $parent 38 | * @return MockObject|HierarchicalRoleInterface 39 | */ 40 | private function createRoleMock($name, $parent) 41 | { 42 | $role = $this->createMock(HierarchicalRoleInterface::class); 43 | $role->expects($this->atLeastOnce()) 44 | ->method('getRoleId') 45 | ->will($this->returnValue($name)); 46 | 47 | $role->expects($this->atLeastOnce()) 48 | ->method('getParent') 49 | ->will($this->returnValue($parent)); 50 | 51 | return $role; 52 | } 53 | 54 | /** 55 | * @covers \BjyAuthorize\Provider\Role\ObjectRepositoryProvider::getRoles 56 | */ 57 | public function testGetRolesWithNoParents() 58 | { 59 | // Set up mocks 60 | $roles = [ 61 | new stdClass(), // to be skipped 62 | $this->createRoleMock('role1', null), 63 | $this->createRoleMock('role2', null), 64 | ]; 65 | 66 | $this->repository->expects($this->once()) 67 | ->method('findAll') 68 | ->will($this->returnValue($roles)); 69 | 70 | // Set up the expected outcome 71 | $expects = [ 72 | new Role('role1', null), 73 | new Role('role2', null), 74 | ]; 75 | 76 | $this->assertEquals($expects, $this->provider->getRoles()); 77 | } 78 | 79 | /** 80 | * @covers \BjyAuthorize\Provider\Role\ObjectRepositoryProvider::getRoles 81 | */ 82 | public function testGetRolesWithParents() 83 | { 84 | // Setup mocks 85 | $role1 = $this->createRoleMock('role1', null); 86 | $roles = [ 87 | $role1, 88 | $this->createRoleMock('role2', null), 89 | $this->createRoleMock('role3', $role1), 90 | ]; 91 | 92 | $this->repository->expects($this->once()) 93 | ->method('findAll') 94 | ->will($this->returnValue($roles)); 95 | 96 | // Set up the expected outcome 97 | $expectedRole1 = new Role('role1', null); 98 | $expects = [ 99 | $expectedRole1, 100 | new Role('role2', null), 101 | new Role('role3', $expectedRole1), 102 | ]; 103 | 104 | $this->assertEquals($expects, $this->provider->getRoles()); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/View/RedirectionStrategy.php: -------------------------------------------------------------------------------- 1 | listeners[] = $events->attach(MvcEvent::EVENT_DISPATCH_ERROR, [$this, 'onDispatchError'], -5000); 37 | } 38 | 39 | /** 40 | * {@inheritDoc} 41 | */ 42 | public function detach(EventManagerInterface $events) 43 | { 44 | foreach ($this->listeners as $index => $listener) { 45 | if ($events->detach($listener)) { 46 | unset($this->listeners[$index]); 47 | } 48 | } 49 | } 50 | 51 | /** 52 | * Handles redirects in case of dispatch errors caused by unauthorized access 53 | */ 54 | public function onDispatchError(MvcEvent $event) 55 | { 56 | // Do nothing if the result is a response object 57 | $result = $event->getResult(); 58 | $routeMatch = $event->getRouteMatch(); 59 | $response = $event->getResponse(); 60 | $router = $event->getRouter(); 61 | $error = $event->getError(); 62 | $url = $this->redirectUri; 63 | 64 | if ( 65 | $result instanceof Response 66 | || ! $routeMatch 67 | || ($response && ! $response instanceof Response) 68 | || ! ( 69 | Route::ERROR === $error 70 | || Controller::ERROR === $error 71 | || ( 72 | Application::ERROR_EXCEPTION === $error 73 | && $event->getParam('exception') instanceof UnAuthorizedException 74 | ) 75 | ) 76 | ) { 77 | return; 78 | } 79 | 80 | if (null === $url) { 81 | $url = $router->assemble([], ['name' => $this->redirectRoute]); 82 | } 83 | 84 | $response = $response ?: new Response(); 85 | 86 | $response->getHeaders()->addHeaderLine('Location', $url); 87 | $response->setStatusCode(302); 88 | 89 | $event->setResponse($response); 90 | } 91 | 92 | /** 93 | * @param string $redirectRoute 94 | */ 95 | public function setRedirectRoute($redirectRoute) 96 | { 97 | $this->redirectRoute = (string) $redirectRoute; 98 | } 99 | 100 | /** 101 | * @param string|null $redirectUri 102 | */ 103 | public function setRedirectUri($redirectUri) 104 | { 105 | $this->redirectUri = $redirectUri ? (string) $redirectUri : null; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /docs/simple-example.md: -------------------------------------------------------------------------------- 1 | `BjyAuthorize` can be used as a way to control access to certain pages in an administration by 2 | virtue of the user status. Here is an example on how this can be done: 3 | 4 | ### Controlling what html a user can see in a view: 5 | 6 | The view to be modified consisted of three menu options: 7 | 8 | ```php 9 | 14 | ``` 15 | 16 | The admin can see all three menus, the affiliate can see menu #2 and #3, and the guest #3 only. 17 | 18 | To get this to work, we setup a resource in our `config/autoload/bjyauthorize.global.php` file. 19 | 20 | ```php 21 | return [ 22 | 'bjyauthorize' => [ 23 | 'resource_providers' => [ 24 | \BjyAuthorize\Provider\Resource\Config::class => [ 25 | 'menu' => [], 26 | ], 27 | ], 28 | ], 29 | ]; 30 | ``` 31 | 32 | The name for the resource is `'menu'`, as it is specific to the menu items in this view. 33 | 34 | Then, under `'rule_providers'`, We setup following rules (in 35 | `config/autoload/bjyauthorize.global.php` again): 36 | 37 | ```php 38 | return [ 39 | 'bjyauthorize' => [ 40 | 'rule_providers' => [ 41 | \BjyAuthorize\Provider\Rule\Config::class => [ 42 | 'allow' => [ 43 | [['administration'], 'menu', ['menu_menu1']], 44 | [['administration', 'affiliate'], 'menu', ['menu_menu2']], 45 | [['administration', 'affiliate', 'guest'], 'menu', ['menu_menu3']], 46 | ], 47 | ], 48 | ], 49 | ], 50 | ]; 51 | ``` 52 | 53 | These rules grant access to `'menu_menu1'` to the `'administrator'` role, `'menu_menu2'` to the 54 | `'affiliate'` as well as the `'administrator'` and `'menu_menu3'` to all 3 existing roles. 55 | 56 | 57 | Finally we use the `isAllowed` **view helper**, provided by BjyAuthorize, to limit access to menu 58 | items: 59 | 60 | ```php 61 | 74 | ``` 75 | 76 | This will hide or show the items in our menu according to our configured ACL rules and the 77 | current logged in user. 78 | 79 | ### Disabling the page content associated to a route: 80 | 81 | Obviously, what provided so far only **disables** the links to those sections of our site, 82 | so we **MUST** prevent direct access to those features of the application to unauthorized 83 | users. 84 | 85 | BjyAuthorize's main feature is exactly this: "guarding" against unauthorized acces, exactly 86 | like a firewall. 87 | 88 | In order to do that, we have to configure the "guards" provided by the module, which is usually 89 | done via configuration (again in `config/autoload/bjyauthorize.global.php`): 90 | 91 | ```php 92 | return [ 93 | 'bjyauthorize' => [ 94 | 'guards' => [ 95 | \BjyAuthorize\Guard\Controller::class => [ 96 | ['controller' => 'lmcuser', 'roles' => []], 97 | ['controller' => ['Module\Controller\Menu1Controller'], 'roles' => ['admin']], 98 | ['controller' => ['Module\Controller\Menu2Controller'], 'roles' => ['admin','affiliate']], 99 | ['controller' => ['Module\Controller\Menu3Controller'], 'roles' => ['admin','affiliate','guest']], 100 | ], 101 | ], 102 | ], 103 | ]; 104 | ``` 105 | -------------------------------------------------------------------------------- /test/ModuleTest.php: -------------------------------------------------------------------------------- 1 | getConfig(); 24 | 25 | $this->assertIsArray($config); 26 | $this->assertArrayHasKey('bjyauthorize', $config); 27 | $this->assertArrayHasKey('service_manager', $config); 28 | $this->assertArrayHasKey('controller_plugins', $config); 29 | $this->assertArrayHasKey('view_manager', $config); 30 | $this->assertArrayHasKey('view_helpers', $config); 31 | $this->assertArrayHasKey('laminas-developer-tools', $config); 32 | } 33 | 34 | public function testIfBoostrapRegistersGuardsAndStrategy() 35 | { 36 | $module = new Module(); 37 | $event = $this->getMockedBootstrapEvent(); 38 | 39 | $module->onBootstrap($event); 40 | } 41 | 42 | /** 43 | * @return MvcEvent 44 | */ 45 | protected function getMockedBootstrapEvent() 46 | { 47 | $guard1 = $this->getMockBuilder(Controller::class) 48 | ->disableOriginalConstructor() 49 | ->getMock(); 50 | 51 | $guard1->expects($this->once()) 52 | ->method('attach'); 53 | 54 | $guard2 = $this->getMockBuilder(Route::class) 55 | ->disableOriginalConstructor() 56 | ->getMock(); 57 | 58 | $guard2->expects($this->once()) 59 | ->method('attach'); 60 | 61 | $strategy = $this->getMockBuilder(UnauthorizedStrategy::class) 62 | ->disableOriginalConstructor() 63 | ->getMock(); 64 | 65 | $strategy->expects($this->once()) 66 | ->method('attach'); 67 | 68 | $serviceManager = $this->getMockBuilder(ServiceManager::class) 69 | ->getMock(); 70 | 71 | $serviceManager->expects($this->any()) 72 | ->method('get') 73 | ->will($this->returnCallback(function (string $name) use ($guard1, $guard2, $strategy) { 74 | switch ($name) { 75 | case 'BjyAuthorize\Config': 76 | return ['unauthorized_strategy' => 'my_unauthorized_strategy']; 77 | case 'my_unauthorized_strategy': 78 | return $strategy; 79 | case 'BjyAuthorize\Guards': 80 | return [$guard1, $guard2]; 81 | default: 82 | throw new InvalidArgumentException('Invalid service call.'); 83 | } 84 | })); 85 | 86 | $eventManager = $this->getMockBuilder(EventManager::class) 87 | ->getMock(); 88 | 89 | $app = $this->getMockBuilder(Application::class) 90 | ->disableOriginalConstructor() 91 | ->getMock(); 92 | 93 | $app->expects($this->any()) 94 | ->method('getServiceManager') 95 | ->willReturn($serviceManager); 96 | 97 | $app->expects($this->any()) 98 | ->method('getEventManager') 99 | ->willReturn($eventManager); 100 | 101 | $event = $this->getMockBuilder(MvcEvent::class) 102 | ->disableOriginalConstructor() 103 | ->getMock(); 104 | 105 | $event->expects($this->any()) 106 | ->method('getTarget') 107 | ->willReturn($app); 108 | 109 | return $event; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/Guard/Controller.php: -------------------------------------------------------------------------------- 1 | getResourceName($controller, $action); 41 | } 42 | } 43 | 44 | return $results; 45 | } 46 | 47 | /** 48 | * {@inheritDoc} 49 | */ 50 | public function attach(EventManagerInterface $events, $priority = 1) 51 | { 52 | $this->listeners[] = $events->attach(MvcEvent::EVENT_ROUTE, [$this, 'onDispatch'], -1000); 53 | } 54 | 55 | /** 56 | * Retrieves the resource name for a given controller 57 | * 58 | * @param string $controller 59 | * @param string $action 60 | * @return string 61 | */ 62 | public function getResourceName($controller, $action = null) 63 | { 64 | if (isset($action)) { 65 | return sprintf('controller/%s:%s', $controller, strtolower($action)); 66 | } 67 | 68 | return sprintf('controller/%s', $controller); 69 | } 70 | 71 | /** 72 | * Event callback to be triggered on dispatch, causes application error triggering 73 | * in case of failed authorization check 74 | * 75 | * @return mixed 76 | */ 77 | public function onDispatch(MvcEvent $event) 78 | { 79 | /** @var Authorize $service */ 80 | $service = $this->container->get(Authorize::class); 81 | $match = $event->getRouteMatch(); 82 | $controller = $match->getParam('controller'); 83 | $action = $match->getParam('action'); 84 | $request = $event->getRequest(); 85 | $method = $request instanceof HttpRequest ? strtolower((string) $request->getMethod()) : null; 86 | 87 | $authorized = (class_exists(ConsoleRequest::class) && $event->getRequest() instanceof ConsoleRequest) 88 | || $service->isAllowed($this->getResourceName($controller)) 89 | || $service->isAllowed($this->getResourceName($controller, $action)) 90 | || ($method && $service->isAllowed($this->getResourceName($controller, $method))); 91 | 92 | if ($authorized) { 93 | return; 94 | } 95 | 96 | $event->setError(static::ERROR); 97 | $event->setParam('identity', $service->getIdentity()); 98 | $event->setParam('controller', $controller); 99 | $event->setParam('action', $action); 100 | 101 | $errorMessage = sprintf("You are not authorized to access %s:%s", $controller, $action); 102 | $event->setParam('exception', new UnAuthorizedException($errorMessage)); 103 | 104 | /** @var ApplicationInterface $app */ 105 | $app = $event->getTarget(); 106 | $eventManager = $app->getEventManager(); 107 | 108 | $event->setName(MvcEvent::EVENT_DISPATCH_ERROR); 109 | $results = $eventManager->triggerEvent($event); 110 | 111 | $return = $results->last(); 112 | if (! $return) { 113 | return $event->getResult(); 114 | } 115 | 116 | return $return; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/View/UnauthorizedStrategy.php: -------------------------------------------------------------------------------- 1 | template = (string) $template; 36 | } 37 | 38 | /** 39 | * {@inheritDoc} 40 | */ 41 | public function attach(EventManagerInterface $events, $priority = 1) 42 | { 43 | $this->listeners[] = $events->attach(MvcEvent::EVENT_DISPATCH_ERROR, [$this, 'onDispatchError'], -5000); 44 | } 45 | 46 | /** 47 | * {@inheritDoc} 48 | */ 49 | public function detach(EventManagerInterface $events) 50 | { 51 | foreach ($this->listeners as $index => $listener) { 52 | if ($events->detach($listener)) { 53 | unset($this->listeners[$index]); 54 | } 55 | } 56 | } 57 | 58 | /** 59 | * @param string $template 60 | */ 61 | public function setTemplate($template) 62 | { 63 | $this->template = (string) $template; 64 | } 65 | 66 | /** 67 | * @return string 68 | */ 69 | public function getTemplate() 70 | { 71 | return $this->template; 72 | } 73 | 74 | /** 75 | * Callback used when a dispatch error occurs. Modifies the 76 | * response object with an according error if the application 77 | * event contains an exception related with authorization. 78 | * 79 | * @return void 80 | */ 81 | public function onDispatchError(MvcEvent $event) 82 | { 83 | // Do nothing if the result is a response object 84 | $result = $event->getResult(); 85 | $response = $event->getResponse(); 86 | 87 | if ($result instanceof Response || ($response && ! $response instanceof HttpResponse)) { 88 | return; 89 | } 90 | 91 | // Common view variables 92 | $viewVariables = [ 93 | 'error' => $event->getParam('error'), 94 | 'identity' => $event->getParam('identity'), 95 | ]; 96 | 97 | switch ($event->getError()) { 98 | case Controller::ERROR: 99 | $viewVariables['controller'] = $event->getParam('controller'); 100 | $viewVariables['action'] = $event->getParam('action'); 101 | break; 102 | case Route::ERROR: 103 | $viewVariables['route'] = $event->getParam('route'); 104 | break; 105 | case Application::ERROR_EXCEPTION: 106 | if (! $event->getParam('exception') instanceof UnAuthorizedException) { 107 | return; 108 | } 109 | 110 | $viewVariables['reason'] = $event->getParam('exception')->getMessage(); 111 | $viewVariables['error'] = 'error-unauthorized'; 112 | break; 113 | default: 114 | /* 115 | * do nothing if there is no error in the event or the error 116 | * does not match one of our predefined errors (we don't want 117 | * our 403 template to handle other types of errors) 118 | */ 119 | 120 | return; 121 | } 122 | 123 | $model = new ViewModel($viewVariables); 124 | $response = $response ?: new HttpResponse(); 125 | 126 | $model->setTemplate($this->getTemplate()); 127 | $event->getViewModel()->addChild($model); 128 | $response->setStatusCode(403); 129 | $event->setResponse($response); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /test/Collector/RoleCollectorTest.php: -------------------------------------------------------------------------------- 1 | identityProvider = $this->createMock(ProviderInterface::class); 36 | $this->collector = new RoleCollector($this->identityProvider); 37 | } 38 | 39 | /** 40 | * @covers \BjyAuthorize\Collector\RoleCollector::collect 41 | * @covers \BjyAuthorize\Collector\RoleCollector::serialize 42 | * @covers \BjyAuthorize\Collector\RoleCollector::unserialize 43 | * @covers \BjyAuthorize\Collector\RoleCollector::getCollectedRoles 44 | */ 45 | public function testCollect() 46 | { 47 | $role1 = $this->createMock(RoleInterface::class); 48 | $mvcEvent = $this->createMock(MvcEvent::class); 49 | 50 | $role1->expects($this->any())->method('getRoleId')->will($this->returnValue('role1')); 51 | 52 | $this 53 | ->identityProvider 54 | ->expects($this->any()) 55 | ->method('getIdentityRoles') 56 | ->will( 57 | $this->returnValue( 58 | [ 59 | $role1, 60 | 'role2', 61 | 'key' => 'role3', 62 | ] 63 | ) 64 | ); 65 | 66 | $this->collector->collect($mvcEvent); 67 | 68 | $roles = $this->collector->getCollectedRoles(); 69 | 70 | $this->assertCount(3, $roles); 71 | $this->assertContains('role1', $roles); 72 | $this->assertContains('role2', $roles); 73 | $this->assertContains('role3', $roles); 74 | 75 | /** @var RoleCollector $collector */ 76 | $collector = unserialize(serialize($this->collector)); 77 | 78 | $collector->collect($mvcEvent); 79 | 80 | $roles = $this->collector->getCollectedRoles(); 81 | 82 | $this->assertCount(3, $roles); 83 | $this->assertContains('role1', $roles); 84 | $this->assertContains('role2', $roles); 85 | $this->assertContains('role3', $roles); 86 | } 87 | 88 | /** 89 | * @covers \BjyAuthorize\Collector\RoleCollector::collect 90 | * @covers \BjyAuthorize\Collector\RoleCollector::getCollectedRoles 91 | */ 92 | public function testTraversableCollect() 93 | { 94 | $role1 = $this->createMock(RoleInterface::class); 95 | $mvcEvent = $this->createMock(MvcEvent::class); 96 | 97 | $role1->expects($this->any())->method('getRoleId')->will($this->returnValue('role1')); 98 | 99 | $this 100 | ->identityProvider 101 | ->expects($this->any()) 102 | ->method('getIdentityRoles') 103 | ->will( 104 | $this->returnValue( 105 | new ArrayIterator( 106 | [ 107 | $role1, 108 | 'role2', 109 | 'key' => 'role3', 110 | ] 111 | ) 112 | ) 113 | ); 114 | 115 | $this->collector->collect($mvcEvent); 116 | 117 | $roles = $this->collector->getCollectedRoles(); 118 | 119 | $this->assertCount(3, $roles); 120 | $this->assertContains('role1', $roles); 121 | $this->assertContains('role2', $roles); 122 | $this->assertContains('role3', $roles); 123 | } 124 | 125 | /** 126 | * @covers \BjyAuthorize\Collector\RoleCollector::getName 127 | */ 128 | public function testGetName() 129 | { 130 | $this->assertIsString($this->collector->getName()); 131 | } 132 | 133 | /** 134 | * @covers \BjyAuthorize\Collector\RoleCollector::getPriority 135 | */ 136 | public function testGetPriority() 137 | { 138 | $this->assertIsInt($this->collector->getPriority()); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /data/User.php.odm.dist: -------------------------------------------------------------------------------- 1 | roles = new ArrayCollection(); 70 | } 71 | 72 | 73 | /** 74 | * Set id. 75 | * 76 | * @param int $id 77 | * 78 | * @return void 79 | */ 80 | public function setId($id) 81 | { 82 | $this->id = (int) $id; 83 | } 84 | 85 | /** 86 | * Get id 87 | * 88 | * @return id $id 89 | */ 90 | public function getId() 91 | { 92 | return $this->id; 93 | } 94 | 95 | /** 96 | * Set email 97 | * 98 | * @param string $email 99 | * 100 | * @return void 101 | */ 102 | public function setEmail($email) 103 | { 104 | $this->email = $email; 105 | } 106 | 107 | /** 108 | * Get email 109 | * 110 | * @return string $email 111 | */ 112 | public function getEmail() 113 | { 114 | return $this->email; 115 | } 116 | 117 | /** 118 | * Get username. 119 | * 120 | * @return string 121 | */ 122 | public function getUsername() 123 | { 124 | return $this->username; 125 | } 126 | 127 | /** 128 | * Set username. 129 | * 130 | * @param string $username 131 | * 132 | * @return void 133 | */ 134 | public function setUsername($username) 135 | { 136 | $this->username = $username; 137 | } 138 | 139 | /** 140 | * Get displayName. 141 | * 142 | * @return string 143 | */ 144 | public function getDisplayName() 145 | { 146 | return $this->displayName; 147 | } 148 | 149 | /** 150 | * Set displayName. 151 | * 152 | * @param string $displayName 153 | * 154 | * @return void 155 | */ 156 | public function setDisplayName($displayName) 157 | { 158 | $this->displayName = $displayName; 159 | } 160 | 161 | /** 162 | * Get password. 163 | * 164 | * @return string 165 | */ 166 | public function getPassword() 167 | { 168 | return $this->password; 169 | } 170 | 171 | /** 172 | * Set password. 173 | * 174 | * @param string $password 175 | * 176 | * @return void 177 | */ 178 | public function setPassword($password) 179 | { 180 | $this->password = $password; 181 | } 182 | 183 | /** 184 | * Get state. 185 | * 186 | * @return int 187 | */ 188 | public function getState() 189 | { 190 | return $this->state; 191 | } 192 | 193 | /** 194 | * Set state. 195 | * 196 | * @param int $state 197 | * 198 | * @return void 199 | */ 200 | public function setState($state) 201 | { 202 | $this->state = $state; 203 | } 204 | 205 | /** 206 | * Get role. 207 | * 208 | * @return array 209 | */ 210 | public function getRoles() 211 | { 212 | return $this->roles->toArray(); 213 | } 214 | 215 | 216 | /** 217 | * Add roles 218 | * 219 | * @param MyNamespace\Role $roles 220 | */ 221 | public function addRole(Role $roles) 222 | { 223 | $this->roles[] = $roles; 224 | } 225 | 226 | /** 227 | * Remove roles 228 | * 229 | * @param MyNamespace\Role $roles 230 | */ 231 | public function removeRole(Role $roles) 232 | { 233 | $this->roles->removeElement($roles); 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /data/User.php.dist: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class User implements UserInterface, ProviderInterface 19 | { 20 | /** 21 | * @var int 22 | * @ORM\Id 23 | * @ORM\Column(type="integer") 24 | * @ORM\GeneratedValue(strategy="AUTO") 25 | */ 26 | protected $id; 27 | 28 | /** 29 | * @var string 30 | * @ORM\Column(type="string", length=255, unique=true, nullable=true) 31 | */ 32 | protected $username; 33 | 34 | /** 35 | * @var string 36 | * @ORM\Column(type="string", unique=true, length=255) 37 | */ 38 | protected $email; 39 | 40 | /** 41 | * @var string 42 | * @ORM\Column(type="string", length=50, nullable=true) 43 | */ 44 | protected $displayName; 45 | 46 | /** 47 | * @var string 48 | * @ORM\Column(type="string", length=128) 49 | */ 50 | protected $password; 51 | 52 | /** 53 | * @var int 54 | * @ORM\Column(type="integer") 55 | */ 56 | protected $state; 57 | 58 | /** 59 | * @var \Doctrine\Common\Collections\Collection 60 | * @ORM\ManyToMany(targetEntity="MyNamespace\Role") 61 | * @ORM\JoinTable(name="user_role_linker", 62 | * joinColumns={@ORM\JoinColumn(name="user_id", referencedColumnName="id")}, 63 | * inverseJoinColumns={@ORM\JoinColumn(name="role_id", referencedColumnName="id")} 64 | * ) 65 | */ 66 | protected $roles; 67 | 68 | /** 69 | * Initializes the roles variable. 70 | */ 71 | public function __construct() 72 | { 73 | $this->roles = new ArrayCollection(); 74 | } 75 | 76 | /** 77 | * Get id. 78 | * 79 | * @return int 80 | */ 81 | public function getId() 82 | { 83 | return $this->id; 84 | } 85 | 86 | /** 87 | * Set id. 88 | * 89 | * @param int $id 90 | * 91 | * @return void 92 | */ 93 | public function setId($id) 94 | { 95 | $this->id = (int) $id; 96 | } 97 | 98 | /** 99 | * Get username. 100 | * 101 | * @return string 102 | */ 103 | public function getUsername() 104 | { 105 | return $this->username; 106 | } 107 | 108 | /** 109 | * Set username. 110 | * 111 | * @param string $username 112 | * 113 | * @return void 114 | */ 115 | public function setUsername($username) 116 | { 117 | $this->username = $username; 118 | } 119 | 120 | /** 121 | * Get email. 122 | * 123 | * @return string 124 | */ 125 | public function getEmail() 126 | { 127 | return $this->email; 128 | } 129 | 130 | /** 131 | * Set email. 132 | * 133 | * @param string $email 134 | * 135 | * @return void 136 | */ 137 | public function setEmail($email) 138 | { 139 | $this->email = $email; 140 | } 141 | 142 | /** 143 | * Get displayName. 144 | * 145 | * @return string 146 | */ 147 | public function getDisplayName() 148 | { 149 | return $this->displayName; 150 | } 151 | 152 | /** 153 | * Set displayName. 154 | * 155 | * @param string $displayName 156 | * 157 | * @return void 158 | */ 159 | public function setDisplayName($displayName) 160 | { 161 | $this->displayName = $displayName; 162 | } 163 | 164 | /** 165 | * Get password. 166 | * 167 | * @return string 168 | */ 169 | public function getPassword() 170 | { 171 | return $this->password; 172 | } 173 | 174 | /** 175 | * Set password. 176 | * 177 | * @param string $password 178 | * 179 | * @return void 180 | */ 181 | public function setPassword($password) 182 | { 183 | $this->password = $password; 184 | } 185 | 186 | /** 187 | * Get state. 188 | * 189 | * @return int 190 | */ 191 | public function getState() 192 | { 193 | return $this->state; 194 | } 195 | 196 | /** 197 | * Set state. 198 | * 199 | * @param int $state 200 | * 201 | * @return void 202 | */ 203 | public function setState($state) 204 | { 205 | $this->state = $state; 206 | } 207 | 208 | /** 209 | * Get role. 210 | * 211 | * @return array 212 | */ 213 | public function getRoles() 214 | { 215 | return $this->roles->getValues(); 216 | } 217 | 218 | /** 219 | * Add a role to the user. 220 | * 221 | * @param Role $role 222 | * 223 | * @return void 224 | */ 225 | public function addRole($role) 226 | { 227 | $this->roles[] = $role; 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /config/module.config.php: -------------------------------------------------------------------------------- 1 | [ 9 | // default role for unauthenticated users 10 | 'default_role' => 'guest', 11 | 12 | // default role for authenticated users (if using the 13 | // 'BjyAuthorize\Provider\Identity\AuthenticationIdentityProvider' identity provider) 14 | 'authenticated_role' => 'user', 15 | 16 | // identity provider service name 17 | 'identity_provider' => Provider\Identity\LmcUserLaminasDb::class, 18 | 19 | // Role providers to be used to load all available roles into Laminas\Permissions\Acl\Acl 20 | // Keys are the provider service names, values are the options to be passed to the provider 21 | 'role_providers' => [], 22 | 23 | // Resource providers to be used to load all available resources into Laminas\Permissions\Acl\Acl 24 | // Keys are the provider service names, values are the options to be passed to the provider 25 | 'resource_providers' => [], 26 | 27 | // Rule providers to be used to load all available rules into Laminas\Permissions\Acl\Acl 28 | // Keys are the provider service names, values are the options to be passed to the provider 29 | 'rule_providers' => [], 30 | 31 | // Guard listeners to be attached to the application event manager 32 | 'guards' => [], 33 | 34 | // strategy service name for the strategy listener to be used when permission-related errors are detected 35 | 'unauthorized_strategy' => View\UnauthorizedStrategy::class, 36 | 37 | // Template name for the unauthorized strategy 38 | 'template' => 'error/403', 39 | 40 | // Flag if cache should be enabled or not 41 | 'cache_enabled' => true, 42 | 43 | // cache options have to be compatible with Laminas\Cache\StorageAdapterFactoryInterface::create 44 | 'cache_options' => [ 45 | 'adapter' => [ 46 | 'name' => 'memory', 47 | ], 48 | 'plugins' => [ 49 | [ 50 | 'name'=> 'serializer', 51 | ], 52 | ], 53 | ], 54 | 55 | // Key used by the cache for caching the acl 56 | 'cache_key' => 'bjyauthorize_acl' 57 | ], 58 | 'service_manager' => [ 59 | 'factories' => [ 60 | 'BjyAuthorize\Cache' => Service\CacheFactory::class, 61 | 'BjyAuthorize\CacheKeyGenerator' => Service\CacheKeyGeneratorFactory::class, 62 | 'BjyAuthorize\Config' => Service\ConfigServiceFactory::class, 63 | 'BjyAuthorize\Guards' => Service\GuardsServiceFactory::class, 64 | 'BjyAuthorize\RoleProviders' => Service\RoleProvidersServiceFactory::class, 65 | 'BjyAuthorize\ResourceProviders' => Service\ResourceProvidersServiceFactory::class, 66 | 'BjyAuthorize\RuleProviders' => Service\RuleProvidersServiceFactory::class, 67 | 'BjyAuthorize\Service\RoleDbTableGateway' => Service\UserRoleServiceFactory::class, 68 | Collector\RoleCollector::class => Service\RoleCollectorServiceFactory::class, 69 | Guard\Controller::class => Service\ControllerGuardServiceFactory::class, 70 | Guard\Route::class => Service\RouteGuardServiceFactory::class, 71 | Provider\Identity\AuthenticationIdentityProvider::class 72 | => Service\AuthenticationIdentityProviderServiceFactory::class, 73 | Provider\Identity\LmcUserLaminasDb::class => Service\LmcUserLaminasDbIdentityProviderServiceFactory::class, 74 | Provider\Identity\ProviderInterface::class => Service\IdentityProviderServiceFactory::class, 75 | Provider\Resource\Config::class => Service\ConfigResourceProviderServiceFactory::class, 76 | Provider\Role\Config::class => Service\ConfigRoleProviderServiceFactory::class, 77 | Provider\Role\LaminasDb::class => Service\LaminasDbRoleProviderServiceFactory::class, 78 | Provider\Role\ObjectRepositoryProvider::class => Service\ObjectRepositoryRoleProviderFactory::class, 79 | Provider\Rule\Config::class => Service\ConfigRuleProviderServiceFactory::class, 80 | Service\Authorize::class => Service\AuthorizeFactory::class, 81 | View\UnauthorizedStrategy::class => Service\UnauthorizedStrategyServiceFactory::class, 82 | ], 83 | 'invokables' => [ 84 | View\RedirectionStrategy::class, 85 | ], 86 | 'aliases' => [ 87 | 'bjyauthorize_zend_db_adapter' => \Laminas\Db\Adapter\Adapter::class, 88 | ], 89 | 'initializers' => [ 90 | Service\AuthorizeAwareServiceInitializer::class 91 | ], 92 | ], 93 | 'controller_plugins' => [ 94 | 'factories' => [ 95 | 'isAllowed' => Controller\Plugin\IsAllowedFactory::class 96 | ], 97 | ], 98 | 'view_manager' => [ 99 | 'template_map' => [ 100 | 'error/403' => __DIR__ . '/../view/error/403.phtml', 101 | 'laminas-developer-tools/toolbar/bjy-authorize-role' 102 | => __DIR__ . '/../view/laminas-developer-tools/toolbar/bjy-authorize-role.phtml', 103 | ], 104 | ], 105 | 'view_helpers' => [ 106 | 'factories' => [ 107 | 'isAllowed' => View\Helper\IsAllowedFactory::class, 108 | ], 109 | ], 110 | 'laminas-developer-tools' => [ 111 | 'profiler' => [ 112 | 'collectors' => [ 113 | 'bjy_authorize_role_collector' => Collector\RoleCollector::class, 114 | ], 115 | ], 116 | 'toolbar' => [ 117 | 'entries' => [ 118 | 'bjy_authorize_role_collector' => 'laminas-developer-tools/toolbar/bjy-authorize-role', 119 | ], 120 | ], 121 | ], 122 | ]; 123 | -------------------------------------------------------------------------------- /test/Provider/Identity/AuthenticationIdentityProviderTest.php: -------------------------------------------------------------------------------- 1 | authService = $this->createMock(AuthenticationService::class); 34 | $this->provider = new AuthenticationIdentityProvider($this->authService); 35 | } 36 | 37 | /** 38 | * @covers \BjyAuthorize\Provider\Identity\AuthenticationIdentityProvider::getIdentityRoles 39 | */ 40 | public function testAuthenticationIdentityProviderIfAuthenticated() 41 | { 42 | $this->authService->expects($this->once())->method('getIdentity')->will($this->returnValue('foo')); 43 | 44 | $this->provider->setDefaultRole('guest'); 45 | $this->provider->setAuthenticatedRole('user'); 46 | 47 | $this->assertEquals($this->provider->getIdentityRoles(), ['user']); 48 | } 49 | 50 | /** 51 | * @covers \BjyAuthorize\Provider\Identity\AuthenticationIdentityProvider::getIdentityRoles 52 | */ 53 | public function testAuthenticationIdentityProviderIfUnauthenticated() 54 | { 55 | $this->authService->expects($this->once())->method('getIdentity')->will($this->returnValue(null)); 56 | 57 | $this->provider->setDefaultRole('guest'); 58 | $this->provider->setAuthenticatedRole('user'); 59 | 60 | $this->assertEquals(['guest'], $this->provider->getIdentityRoles()); 61 | } 62 | 63 | /** 64 | * @covers \BjyAuthorize\Provider\Identity\AuthenticationIdentityProvider::getIdentityRoles 65 | */ 66 | public function testAuthenticationIdentityProviderIfAuthenticatedWithRoleInterface() 67 | { 68 | $this->authService->expects($this->once())->method('getIdentity')->will($this->returnValue('foo')); 69 | 70 | $authorizedRole = $this->getMockBuilder(RoleInterface::class)->getMock(); 71 | 72 | $this->provider->setAuthenticatedRole($authorizedRole); 73 | 74 | $this->assertSame([$authorizedRole], $this->provider->getIdentityRoles()); 75 | } 76 | 77 | /** 78 | * @covers \BjyAuthorize\Provider\Identity\AuthenticationIdentityProvider::getIdentityRoles 79 | */ 80 | public function testAuthenticationIdentityProviderIfUnauthenticatedWithRoleInterface() 81 | { 82 | $this->authService->expects($this->once())->method('getIdentity')->will($this->returnValue(null)); 83 | 84 | $defaultRole = $this->getMockBuilder(RoleInterface::class)->getMock(); 85 | 86 | $this->provider->setDefaultRole($defaultRole); 87 | 88 | $this->assertSame([$defaultRole], $this->provider->getIdentityRoles()); 89 | } 90 | 91 | /** 92 | * @covers \BjyAuthorize\Provider\Identity\AuthenticationIdentityProvider::getIdentityRoles 93 | */ 94 | public function testGetIdentityRolesRetrievesRolesFromIdentityThatIsARoleProvider() 95 | { 96 | $role1 = $this->createMock(RoleInterface::class); 97 | $role2 = $this->createMock(RoleInterface::class); 98 | $user = $this->createMock(ProviderInterface::class); 99 | 100 | $user->expects($this->once()) 101 | ->method('getRoles') 102 | ->will($this->returnValue([$role1, $role2])); 103 | 104 | $this->authService->expects($this->any()) 105 | ->method('getIdentity') 106 | ->will($this->returnValue($user)); 107 | 108 | $roles = $this->provider->getIdentityRoles(); 109 | 110 | $this->assertCount(2, $roles); 111 | $this->assertContains($role1, $roles); 112 | $this->assertContains($role2, $roles); 113 | } 114 | 115 | /** 116 | * @covers \BjyAuthorize\Provider\Identity\AuthenticationIdentityProvider::getIdentityRoles 117 | */ 118 | public function testGetIdentityRolesRetrievesIdentityThatIsARole() 119 | { 120 | $user = $this->createMock(RoleInterface::class); 121 | 122 | $this->authService->expects($this->any()) 123 | ->method('getIdentity') 124 | ->will($this->returnValue($user)); 125 | 126 | $this->assertSame([$user], $this->provider->getIdentityRoles()); 127 | } 128 | 129 | /** 130 | * @covers \BjyAuthorize\Provider\Identity\AuthenticationIdentityProvider::setAuthenticatedRole 131 | * @covers \BjyAuthorize\Provider\Identity\AuthenticationIdentityProvider::getAuthenticatedRole 132 | * @covers \BjyAuthorize\Exception\InvalidRoleException::invalidRoleInstance 133 | */ 134 | public function testSetGetAuthenticatedRole() 135 | { 136 | $this->provider->setAuthenticatedRole('test'); 137 | $this->assertSame('test', $this->provider->getAuthenticatedRole()); 138 | 139 | $role = $this->createMock(RoleInterface::class); 140 | $this->provider->setAuthenticatedRole($role); 141 | $this->assertSame($role, $this->provider->getAuthenticatedRole()); 142 | 143 | $this->expectException(InvalidRoleException::class); 144 | $this->provider->setAuthenticatedRole(false); 145 | } 146 | 147 | /** 148 | * @covers \BjyAuthorize\Provider\Identity\AuthenticationIdentityProvider::setDefaultRole 149 | * @covers \BjyAuthorize\Provider\Identity\AuthenticationIdentityProvider::getDefaultRole 150 | * @covers \BjyAuthorize\Exception\InvalidRoleException::invalidRoleInstance 151 | */ 152 | public function testSetGetDefaultRole() 153 | { 154 | $this->provider->setDefaultRole('test'); 155 | $this->assertSame('test', $this->provider->getDefaultRole()); 156 | 157 | $role = $this->createMock(RoleInterface::class); 158 | $this->provider->setDefaultRole($role); 159 | $this->assertSame($role, $this->provider->getDefaultRole()); 160 | 161 | $this->expectException(InvalidRoleException::class); 162 | $this->provider->setDefaultRole(false); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /test/View/UnauthorizedStrategyTest.php: -------------------------------------------------------------------------------- 1 | strategy = new UnauthorizedStrategy('template/name'); 37 | } 38 | 39 | /** 40 | * @covers \BjyAuthorize\View\UnauthorizedStrategy::attach 41 | * @covers \BjyAuthorize\View\UnauthorizedStrategy::detach 42 | */ 43 | public function testAttachDetach() 44 | { 45 | $eventManager = $this->getMockBuilder(EventManagerInterface::class) 46 | ->getMock(); 47 | 48 | $callbackDummy = new class { 49 | public function __invoke() 50 | { 51 | } 52 | }; 53 | 54 | $eventManager 55 | ->expects($this->once()) 56 | ->method('attach') 57 | ->with() 58 | ->will($this->returnValue($callbackDummy)); 59 | $this->strategy->attach($eventManager); 60 | $eventManager 61 | ->expects($this->once()) 62 | ->method('detach') 63 | ->with($callbackDummy) 64 | ->will($this->returnValue(true)); 65 | $this->strategy->detach($eventManager); 66 | } 67 | 68 | /** 69 | * @covers \BjyAuthorize\View\UnauthorizedStrategy::setTemplate 70 | * @covers \BjyAuthorize\View\UnauthorizedStrategy::getTemplate 71 | */ 72 | public function testGetSetTemplate() 73 | { 74 | $this->assertSame('template/name', $this->strategy->getTemplate()); 75 | $this->strategy->setTemplate('other/template'); 76 | $this->assertSame('other/template', $this->strategy->getTemplate()); 77 | } 78 | 79 | /** 80 | * @covers \BjyAuthorize\View\UnauthorizedStrategy::onDispatchError 81 | */ 82 | public function testOnDispatchErrorWithGenericUnAuthorizedException() 83 | { 84 | $exception = $this->createMock(UnAuthorizedException::class); 85 | $viewModel = $this->createMock(ModelInterface::class); 86 | $mvcEvent = $this->createMock(MvcEvent::class); 87 | 88 | $mvcEvent->expects($this->any())->method('getError')->will($this->returnValue(Application::ERROR_EXCEPTION)); 89 | $mvcEvent->expects($this->any())->method('getViewModel')->will($this->returnValue($viewModel)); 90 | $mvcEvent 91 | ->expects($this->any()) 92 | ->method('getParam') 93 | ->will( 94 | $this->returnCallback( 95 | function ($name) use ($exception) { 96 | return $name === 'exception' ? $exception : null; 97 | } 98 | ) 99 | ); 100 | 101 | $test = $this; 102 | 103 | $viewModel 104 | ->expects($this->once()) 105 | ->method('addChild') 106 | ->will( 107 | $this->returnCallback( 108 | function (ModelInterface $model) { 109 | // using a return callback because of a bug in HHVM 110 | if ('template/name' !== $model->getTemplate()) { 111 | throw new UnexpectedValueException('Template name does not match expectations!'); 112 | } 113 | } 114 | ) 115 | ); 116 | $mvcEvent 117 | ->expects($this->once()) 118 | ->method('setResponse') 119 | ->will( 120 | $this->returnCallback( 121 | function (Response $response) { 122 | // using a return callback because of a bug in HHVM 123 | if (403 !== $response->getStatusCode()) { 124 | throw new UnexpectedValueException('Response code not match expectations!'); 125 | } 126 | } 127 | ) 128 | ); 129 | 130 | $this->assertNull($this->strategy->onDispatchError($mvcEvent)); 131 | } 132 | 133 | /** 134 | * @covers \BjyAuthorize\View\UnauthorizedStrategy::onDispatchError 135 | */ 136 | public function testIgnoresUnknownExceptions() 137 | { 138 | $exception = $this->createMock(Exception::class); 139 | $viewModel = $this->createMock(ModelInterface::class); 140 | $mvcEvent = $this->createMock(MvcEvent::class); 141 | 142 | $mvcEvent->expects($this->any())->method('getError')->will($this->returnValue(Application::ERROR_EXCEPTION)); 143 | $mvcEvent->expects($this->any())->method('getViewModel')->will($this->returnValue($viewModel)); 144 | $mvcEvent 145 | ->expects($this->any()) 146 | ->method('getParam') 147 | ->will( 148 | $this->returnCallback( 149 | function ($name) use ($exception) { 150 | return $name === 'exception' ? $exception : null; 151 | } 152 | ) 153 | ); 154 | 155 | $viewModel->expects($this->never())->method('addChild'); 156 | $mvcEvent->expects($this->never())->method('setResponse'); 157 | 158 | $this->assertNull($this->strategy->onDispatchError($mvcEvent)); 159 | } 160 | 161 | /** 162 | * @covers \BjyAuthorize\View\UnauthorizedStrategy::onDispatchError 163 | */ 164 | public function testIgnoresUnknownErrors() 165 | { 166 | $viewModel = $this->createMock(ModelInterface::class); 167 | $mvcEvent = $this->createMock(MvcEvent::class); 168 | 169 | $mvcEvent->expects($this->any())->method('getError')->will($this->returnValue('unknown')); 170 | $mvcEvent->expects($this->any())->method('getViewModel')->will($this->returnValue($viewModel)); 171 | 172 | $viewModel->expects($this->never())->method('addChild'); 173 | $mvcEvent->expects($this->never())->method('setResponse'); 174 | 175 | $this->assertNull($this->strategy->onDispatchError($mvcEvent)); 176 | } 177 | 178 | /** 179 | * @covers \BjyAuthorize\View\UnauthorizedStrategy::onDispatchError 180 | */ 181 | public function testIgnoresOnExistingResponse() 182 | { 183 | $response = $this->createMock(ResponseInterface::class); 184 | $viewModel = $this->createMock(ModelInterface::class); 185 | $mvcEvent = $this->createMock(MvcEvent::class); 186 | 187 | $mvcEvent->expects($this->any())->method('getResult')->will($this->returnValue($response)); 188 | $mvcEvent->expects($this->any())->method('getViewModel')->will($this->returnValue($viewModel)); 189 | 190 | $viewModel->expects($this->never())->method('addChild'); 191 | $mvcEvent->expects($this->never())->method('setResponse'); 192 | 193 | $this->assertNull($this->strategy->onDispatchError($mvcEvent)); 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /test/Guard/RouteTest.php: -------------------------------------------------------------------------------- 1 | serviceLocator = $this->getMockBuilder(ServiceLocatorInterface::class) 45 | ->getMock(); 46 | $this->authorize = $authorize = $this->getMockBuilder(Authorize::class) 47 | ->disableOriginalConstructor() 48 | ->getMock(); 49 | $this->routeGuard = new Route([], $this->serviceLocator); 50 | 51 | $this 52 | ->serviceLocator 53 | ->expects($this->any()) 54 | ->method('get') 55 | ->with(Authorize::class) 56 | ->will($this->returnValue($authorize)); 57 | } 58 | 59 | /** 60 | * @covers \BjyAuthorize\Guard\Route::attach 61 | * @covers \BjyAuthorize\Guard\Route::detach 62 | */ 63 | public function testAttachDetach() 64 | { 65 | $eventManager = $this->createMock(EventManagerInterface::class); 66 | 67 | $callbackDummy = new class { 68 | public function __invoke() 69 | { 70 | } 71 | }; 72 | 73 | $eventManager 74 | ->expects($this->once()) 75 | ->method('attach') 76 | ->with() 77 | ->will($this->returnValue($callbackDummy)); 78 | $this->routeGuard->attach($eventManager); 79 | $eventManager 80 | ->expects($this->once()) 81 | ->method('detach') 82 | ->with($callbackDummy) 83 | ->will($this->returnValue(true)); 84 | $this->routeGuard->detach($eventManager); 85 | } 86 | 87 | /** 88 | * @covers \BjyAuthorize\Guard\Route::__construct 89 | * @covers \BjyAuthorize\Guard\Route::getResources 90 | * @covers \BjyAuthorize\Guard\Route::getRules 91 | */ 92 | public function testGetResourcesGetRules() 93 | { 94 | $controller = new Route( 95 | [ 96 | [ 97 | 'route' => 'test/route', 98 | 'roles' => [ 99 | 'admin', 100 | 'user', 101 | ], 102 | ], 103 | [ 104 | 'route' => 'test2-route', 105 | 'roles' => [ 106 | 'admin2', 107 | 'user2', 108 | ], 109 | ], 110 | [ 111 | 'route' => 'test3-route', 112 | 'roles' => 'admin3', 113 | ], 114 | ], 115 | $this->serviceLocator 116 | ); 117 | 118 | $resources = $controller->getResources(); 119 | 120 | $this->assertCount(3, $resources); 121 | $this->assertContains('route/test/route', $resources); 122 | $this->assertContains('route/test2-route', $resources); 123 | $this->assertContains('route/test3-route', $resources); 124 | 125 | $rules = $controller->getRules(); 126 | 127 | $this->assertCount(3, $rules['allow']); 128 | $this->assertContains( 129 | [['admin', 'user'], 'route/test/route'], 130 | $rules['allow'] 131 | ); 132 | $this->assertContains( 133 | [['admin2', 'user2'], 'route/test2-route'], 134 | $rules['allow'] 135 | ); 136 | $this->assertContains( 137 | [['admin3'], 'route/test3-route'], 138 | $rules['allow'] 139 | ); 140 | } 141 | 142 | /** 143 | * @covers \BjyAuthorize\Guard\Route::__construct 144 | * @covers \BjyAuthorize\Guard\Route::getRules 145 | */ 146 | public function testGetRulesWithAssertion() 147 | { 148 | $controller = new Route( 149 | [ 150 | [ 151 | 'route' => 'test/route', 152 | 'roles' => [ 153 | 'admin', 154 | 'user', 155 | ], 156 | 'assertion' => 'test-assertion', 157 | ], 158 | ], 159 | $this->serviceLocator 160 | ); 161 | 162 | $rules = $controller->getRules(); 163 | 164 | $this->assertCount(1, $rules['allow']); 165 | $this->assertContains( 166 | [['admin', 'user'], 'route/test/route', null, 'test-assertion'], 167 | $rules['allow'] 168 | ); 169 | } 170 | 171 | /** 172 | * @covers \BjyAuthorize\Guard\Route::onRoute 173 | */ 174 | public function testOnRouteWithValidRoute() 175 | { 176 | $event = $this->createMvcEvent('test-route'); 177 | $event->getTarget()->getEventManager()->expects($this->never())->method('triggerEvent'); 178 | $this 179 | ->authorize 180 | ->expects($this->any()) 181 | ->method('isAllowed') 182 | ->will( 183 | $this->returnValue( 184 | function ($resource) { 185 | return $resource === 'route/test-route'; 186 | } 187 | ) 188 | ); 189 | 190 | $this->assertNull($this->routeGuard->onRoute($event), 'Does not stop event propagation'); 191 | } 192 | 193 | /** 194 | * @covers \BjyAuthorize\Guard\Route::onRoute 195 | */ 196 | public function testOnRouteWithInvalidResource() 197 | { 198 | $event = $this->createMvcEvent('test-route'); 199 | $this->authorize->expects($this->any())->method('getIdentity')->will($this->returnValue('admin')); 200 | $this 201 | ->authorize 202 | ->expects($this->any()) 203 | ->method('isAllowed') 204 | ->will($this->returnValue(false)); 205 | $event->expects($this->once())->method('setError')->with(Route::ERROR); 206 | 207 | $event->expects($this->exactly(3))->method('setParam')->withConsecutive( 208 | ['route', 'test-route'], 209 | ['identity', 'admin'], 210 | ['exception', $this->isInstanceOf(UnAuthorizedException::class)] 211 | ); 212 | 213 | $responseCollection = $this->getMockBuilder(ResponseCollection::class) 214 | ->getMock(); 215 | 216 | $event->setName(MvcEvent::EVENT_DISPATCH_ERROR); 217 | $event 218 | ->getTarget() 219 | ->getEventManager() 220 | ->expects($this->once()) 221 | ->method('triggerEvent') 222 | ->with($event) 223 | ->willReturn($responseCollection); 224 | 225 | $this->assertNull($this->routeGuard->onRoute($event), 'Does not stop event propagation'); 226 | } 227 | 228 | /** 229 | * @covers \BjyAuthorize\Guard\Controller::onDispatch 230 | */ 231 | public function testOnDispatchWithInvalidResourceConsole() 232 | { 233 | $event = $this->getMockBuilder(MvcEvent::class) 234 | ->getMock(); 235 | $routeMatch = $this->getMockBuilder(RouteMatch::class) 236 | ->disableOriginalConstructor() 237 | ->getMock(); 238 | $request = $this->getMockBuilder(ConsoleRequest::class) 239 | ->disableOriginalConstructor() 240 | ->getMock(); 241 | $event->method('getRouteMatch')->willReturn($routeMatch); 242 | $event->method('getRequest')->willReturn($request); 243 | 244 | $this->assertNull($this->routeGuard->onRoute($event), 'Does not stop event propagation'); 245 | } 246 | 247 | /** 248 | * @param string|null $route 249 | * @return MockObject|MvcEvent 250 | */ 251 | private function createMvcEvent($route = null) 252 | { 253 | $eventManager = $this->getMockBuilder(EventManagerInterface::class) 254 | ->getMock(); 255 | $application = $this->getMockBuilder(Application::class) 256 | ->disableOriginalConstructor() 257 | ->getMock(); 258 | $event = $this->getMockBuilder(MvcEvent::class) 259 | ->getMock(); 260 | $routeMatch = $this->getMockBuilder(RouteMatch::class) 261 | ->disableOriginalConstructor() 262 | ->getMock(); 263 | $request = $this->getMockBuilder(HttpRequest::class) 264 | ->getMock(); 265 | 266 | $event->expects($this->any())->method('getRouteMatch')->will($this->returnValue($routeMatch)); 267 | $event->expects($this->any())->method('getRequest')->will($this->returnValue($request)); 268 | $event->expects($this->any())->method('getTarget')->will($this->returnValue($application)); 269 | $application->expects($this->any())->method('getEventManager')->will($this->returnValue($eventManager)); 270 | $routeMatch->expects($this->any())->method('getMatchedRouteName')->will($this->returnValue($route)); 271 | 272 | return $event; 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /test/Service/AuthorizeTest.php: -------------------------------------------------------------------------------- 1 | getMockBuilder(AbstractAdapter::class) 40 | ->disableOriginalConstructor() 41 | ->getMock(); 42 | 43 | $cache->expects($this->any())->method('getItem'); 44 | $cache->expects($this->any())->method('setItem'); 45 | 46 | $serviceManager = new ServiceManager(); 47 | $serviceManager->setService('BjyAuthorize\Cache', $cache); 48 | $serviceManager->setService(ProviderInterface::class, $this->createMock(ProviderInterface::class)); 49 | $serviceManager->setService( 50 | 'BjyAuthorize\RoleProviders', 51 | $this->createMock(RoleProvidersServiceFactory::class) 52 | ); 53 | $serviceManager->setService( 54 | 'BjyAuthorize\ResourceProviders', 55 | $this->createMock(ResourceProvidersServiceFactory::class) 56 | ); 57 | $serviceManager->setService( 58 | 'BjyAuthorize\RuleProviders', 59 | $this->createMock(RuleProvidersServiceFactory::class) 60 | ); 61 | $serviceManager->setService('BjyAuthorize\Guards', $this->createMock(GuardsServiceFactory::class)); 62 | $serviceManager->setService( 63 | 'BjyAuthorize\CacheKeyGenerator', 64 | function () { 65 | return 'bjyauthorize-acl'; 66 | } 67 | ); 68 | $this->serviceManager = $serviceManager; 69 | } 70 | 71 | /** 72 | * {@inheritDoc} 73 | * 74 | * @see \PHPUnit\Framework\TestCase::tearDown() 75 | */ 76 | protected function tearDown(): void 77 | { 78 | unset($this->serviceManager); 79 | } 80 | 81 | /** 82 | * @covers \BjyAuthorize\Service\Authorize::load 83 | */ 84 | public function testLoadLoadsAclFromCacheAndDoesNotBuildANewAclObject() 85 | { 86 | $this->markTestSkipped('TODO refactoring'); 87 | $acl = $this->createMock(Acl::class); 88 | 89 | $cache = $this->getMockBuilder(AbstractAdapter::class) 90 | ->disableOriginalConstructor() 91 | ->getMock(); 92 | 93 | $cache 94 | ->expects($this->once()) 95 | ->method('getItem') 96 | ->will( 97 | $this->returnCallback( 98 | function ($key, &$success) use ($acl) { 99 | $success = true; 100 | 101 | return $acl; 102 | } 103 | ) 104 | ); 105 | 106 | $serviceManager = new ServiceManager(); 107 | $serviceManager->setService(ProviderInterface::class, $this->createMock(ProviderInterface::class)); 108 | $serviceManager->setService('BjyAuthorize\Cache', $cache); 109 | $serviceManager->setService( 110 | 'BjyAuthorize\CacheKeyGenerator', 111 | function () { 112 | return 'bjyauthorize-acl'; 113 | } 114 | ); 115 | $authorize = new Authorize(['cache_key' => 'bjyauthorize-acl'], $serviceManager); 116 | $authorize->load(); 117 | 118 | $this->assertSame($acl, $authorize->getAcl()); 119 | } 120 | 121 | /** 122 | * @covers \BjyAuthorize\Service\Authorize::load 123 | */ 124 | public function testLoadWritesAclToCacheIfCacheIsEnabledButAclIsNotStoredInCache() 125 | { 126 | $cache = $this->getMockBuilder(AbstractAdapter::class) 127 | ->disableOriginalConstructor() 128 | ->getMock(); 129 | 130 | $cache->expects($this->any())->method('getItem'); 131 | $cache->expects($this->any())->method('setItem'); 132 | 133 | $serviceManager = new ServiceManager(); 134 | $serviceManager->setService('BjyAuthorize\Cache', $cache); 135 | $serviceManager->setService(ProviderInterface::class, $this->createMock(ProviderInterface::class)); 136 | $serviceManager->setService( 137 | 'BjyAuthorize\RoleProviders', 138 | $this->createMock(RoleProvidersServiceFactory::class) 139 | ); 140 | $serviceManager->setService( 141 | 'BjyAuthorize\ResourceProviders', 142 | $this->createMock(ResourceProvidersServiceFactory::class) 143 | ); 144 | $serviceManager->setService( 145 | 'BjyAuthorize\RuleProviders', 146 | $this->createMock(RuleProvidersServiceFactory::class) 147 | ); 148 | $serviceManager->setService('BjyAuthorize\Guards', $this->createMock(GuardsServiceFactory::class)); 149 | $serviceManager->setService( 150 | 'BjyAuthorize\CacheKeyGenerator', 151 | function () { 152 | return 'acl'; 153 | } 154 | ); 155 | $authorize = new Authorize(['cache_key' => 'acl'], $serviceManager); 156 | $authorize->load(); 157 | 158 | $this->assertTrue(true); 159 | } 160 | 161 | /** 162 | * @group bjyoungblood/BjyAuthorize#258 163 | */ 164 | public function testCanAddResourceInterfaceToLoadResource() 165 | { 166 | $serviceManager = $this->serviceManager; 167 | $serviceManager->setAllowOverride(true); 168 | 169 | $resourceProviderMock = $this->getMockBuilder(ResourceConfig::class) 170 | ->disableOriginalConstructor() 171 | ->getMock(); 172 | 173 | $resourceProviderMock 174 | ->expects($this->once()) 175 | ->method('getResources') 176 | ->will( 177 | $this->returnValue( 178 | [new GenericResource('test')] 179 | ) 180 | ); 181 | 182 | $serviceManager->setService(ResourceConfig::class, $resourceProviderMock); 183 | $serviceManager->setService('BjyAuthorize\ResourceProviders', [$resourceProviderMock]); 184 | 185 | $authorize = new Authorize(['cache_key' => 'acl'], $this->serviceManager); 186 | $authorize->load(); 187 | 188 | $acl = $authorize->getAcl(); 189 | 190 | $this->assertTrue($acl->hasResource('test')); 191 | } 192 | 193 | /** 194 | * @group bjyoungblood/BjyAuthorize#258 195 | */ 196 | public function testCanAddTraversableResourceToLoadResource() 197 | { 198 | $serviceManager = $this->serviceManager; 199 | $serviceManager->setAllowOverride(true); 200 | 201 | $resourceProviderMock = $this->getMockBuilder(ResourceConfig::class) 202 | ->disableOriginalConstructor() 203 | ->getMock(); 204 | 205 | $resourceProviderMock 206 | ->expects($this->once()) 207 | ->method('getResources') 208 | ->will( 209 | $this->returnValue( 210 | new ArrayObject(['test']) 211 | ) 212 | ); 213 | 214 | $serviceManager->setService(ResourceConfig::class, $resourceProviderMock); 215 | $serviceManager->setService('BjyAuthorize\ResourceProviders', [$resourceProviderMock]); 216 | 217 | $authorize = new Authorize(['cache_key' => 'acl'], $serviceManager); 218 | 219 | $acl = $authorize->getAcl(); 220 | 221 | $this->assertTrue($acl->hasResource('test')); 222 | } 223 | 224 | /** 225 | * @group bjyoungblood/BjyAuthorize#258 226 | */ 227 | public function testCanAddNonTraversableResourceToLoadResourceThrowsInvalidArgumentException() 228 | { 229 | $this->expectException(InvalidArgumentException::class); 230 | 231 | $serviceManager = $this->serviceManager; 232 | $serviceManager->setAllowOverride(true); 233 | 234 | $resourceProviderMock = $this->getMockBuilder(ResourceConfig::class) 235 | ->disableOriginalConstructor() 236 | ->getMock(); 237 | 238 | $resourceProviderMock 239 | ->expects($this->once()) 240 | ->method('getResources') 241 | ->will( 242 | $this->returnValue( 243 | 'test' 244 | ) 245 | ); 246 | 247 | $serviceManager->setService(ResourceConfig::class, $resourceProviderMock); 248 | $serviceManager->setService('BjyAuthorize\ResourceProviders', [$resourceProviderMock]); 249 | 250 | $authorize = new Authorize(['cache_key' => 'acl'], $this->serviceManager); 251 | $authorize->load(); 252 | } 253 | 254 | /** 255 | * @group bjyoungblood/BjyAuthorize#258 256 | */ 257 | public function testCanAddTraversableRoleToLoadRole() 258 | { 259 | $serviceManager = $this->serviceManager; 260 | $serviceManager->setAllowOverride(true); 261 | 262 | $roleProviderMock = $this->getMockBuilder(RoleConfig::class) 263 | ->disableOriginalConstructor() 264 | ->getMock(); 265 | 266 | $roleProviderMock 267 | ->expects($this->once()) 268 | ->method('getRoles') 269 | ->will( 270 | $this->returnValue( 271 | new ArrayObject([new Role('test')]) 272 | ) 273 | ); 274 | 275 | $serviceManager->setService(RoleConfig::class, $roleProviderMock); 276 | $serviceManager->setService('BjyAuthorize\RoleProviders', [$roleProviderMock]); 277 | 278 | $authorize = new Authorize(['cache_key' => 'acl'], $this->serviceManager); 279 | $authorize->load(); 280 | 281 | $acl = $authorize->getAcl(); 282 | 283 | $this->assertTrue($acl->hasRole('test')); 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /test/View/RedirectionStrategyTest.php: -------------------------------------------------------------------------------- 1 | strategy = new RedirectionStrategy(); 36 | } 37 | 38 | /** 39 | * @covers \BjyAuthorize\View\RedirectionStrategy::attach 40 | * @covers \BjyAuthorize\View\RedirectionStrategy::detach 41 | */ 42 | public function testAttachDetach() 43 | { 44 | $eventManager = $this->getMockBuilder(EventManagerInterface::class) 45 | ->getMock(); 46 | 47 | $callbackDummy = new class { 48 | public function __invoke() 49 | { 50 | } 51 | }; 52 | 53 | $eventManager 54 | ->expects($this->once()) 55 | ->method('attach') 56 | ->with() 57 | ->will($this->returnValue($callbackDummy)); 58 | $this->strategy->attach($eventManager); 59 | $eventManager 60 | ->expects($this->once()) 61 | ->method('detach') 62 | ->with($callbackDummy) 63 | ->will($this->returnValue(true)); 64 | $this->strategy->detach($eventManager); 65 | } 66 | 67 | /** 68 | * @covers \BjyAuthorize\View\RedirectionStrategy::onDispatchError 69 | */ 70 | public function testWillIgnoreUnrecognizedResponse() 71 | { 72 | $mvcEvent = $this->createMock(MvcEvent::class); 73 | $response = $this->createMock(ResponseInterface::class); 74 | $routeMatch = $this->getMockBuilder(RouteMatch::class)->disableOriginalConstructor()->getMock(); 75 | 76 | $mvcEvent->expects($this->any())->method('getResponse')->will($this->returnValue($response)); 77 | $mvcEvent->expects($this->any())->method('getRouteMatch')->will($this->returnValue($routeMatch)); 78 | $mvcEvent->expects($this->any())->method('getError')->will($this->returnValue(Route::ERROR)); 79 | $mvcEvent->expects($this->never())->method('setResponse'); 80 | 81 | $this->strategy->onDispatchError($mvcEvent); 82 | } 83 | 84 | /** 85 | * @covers \BjyAuthorize\View\RedirectionStrategy::onDispatchError 86 | */ 87 | public function testWillIgnoreUnrecognizedErrorType() 88 | { 89 | $mvcEvent = $this->createMock(MvcEvent::class); 90 | $response = $this->createMock(Response::class); 91 | $routeMatch = $this->getMockBuilder(RouteMatch::class)->disableOriginalConstructor()->getMock(); 92 | $route = $this->createMock(RouteInterface::class); 93 | 94 | $mvcEvent->expects($this->any())->method('getResponse')->will($this->returnValue($response)); 95 | $mvcEvent->expects($this->any())->method('getRouteMatch')->will($this->returnValue($routeMatch)); 96 | $mvcEvent->expects($this->any())->method('getRouter')->will($this->returnValue($route)); 97 | $mvcEvent->expects($this->any())->method('getError')->will($this->returnValue('unknown')); 98 | $mvcEvent->expects($this->never())->method('setResponse'); 99 | 100 | $this->strategy->onDispatchError($mvcEvent); 101 | } 102 | 103 | /** 104 | * @covers \BjyAuthorize\View\RedirectionStrategy::onDispatchError 105 | */ 106 | public function testWillIgnoreOnExistingResult() 107 | { 108 | $mvcEvent = $this->createMock(MvcEvent::class); 109 | $response = $this->createMock(Response::class); 110 | $routeMatch = $this->getMockBuilder(RouteMatch::class)->disableOriginalConstructor()->getMock(); 111 | 112 | $mvcEvent->expects($this->any())->method('getResult')->will($this->returnValue($response)); 113 | $mvcEvent->expects($this->any())->method('getResponse')->will($this->returnValue($response)); 114 | $mvcEvent->expects($this->any())->method('getRouteMatch')->will($this->returnValue($routeMatch)); 115 | $mvcEvent->expects($this->any())->method('getError')->will($this->returnValue(Route::ERROR)); 116 | $mvcEvent->expects($this->never())->method('setResponse'); 117 | 118 | $this->strategy->onDispatchError($mvcEvent); 119 | } 120 | 121 | /** 122 | * @covers \BjyAuthorize\View\RedirectionStrategy::onDispatchError 123 | */ 124 | public function testWillIgnoreOnMissingRouteMatch() 125 | { 126 | $mvcEvent = $this->createMock(MvcEvent::class); 127 | $response = $this->createMock(Response::class); 128 | 129 | $mvcEvent->expects($this->any())->method('getResponse')->will($this->returnValue($response)); 130 | $mvcEvent->expects($this->any())->method('getError')->will($this->returnValue(Route::ERROR)); 131 | $mvcEvent->expects($this->never())->method('setResponse'); 132 | 133 | $this->strategy->onDispatchError($mvcEvent); 134 | } 135 | 136 | /** 137 | * @covers \BjyAuthorize\View\RedirectionStrategy::onDispatchError 138 | * @covers \BjyAuthorize\View\RedirectionStrategy::setRedirectRoute 139 | * @covers \BjyAuthorize\View\RedirectionStrategy::setRedirectUri 140 | */ 141 | public function testWillRedirectToRouteOnSetRoute() 142 | { 143 | $this->strategy->setRedirectRoute('redirect/route'); 144 | $this->strategy->setRedirectUri(null); 145 | 146 | $mvcEvent = $this->createMock(MvcEvent::class); 147 | $response = $this->createMock(Response::class); 148 | $routeMatch = $this->getMockBuilder(RouteMatch::class)->disableOriginalConstructor()->getMock(); 149 | $route = $this->getMockForAbstractClass(RouteInterface::class, [], '', true, true, true, ['assemble']); 150 | $headers = $this->createMock(Headers::class); 151 | 152 | $mvcEvent->expects($this->any())->method('getResponse')->will($this->returnValue($response)); 153 | $mvcEvent->expects($this->any())->method('getRouteMatch')->will($this->returnValue($routeMatch)); 154 | $mvcEvent->expects($this->any())->method('getRouter')->will($this->returnValue($route)); 155 | $mvcEvent->expects($this->any())->method('getError')->will($this->returnValue(Route::ERROR)); 156 | 157 | $response->expects($this->any())->method('getHeaders')->will($this->returnValue($headers)); 158 | $response->expects($this->once())->method('setStatusCode')->with(302); 159 | 160 | $headers->expects($this->once())->method('addHeaderLine')->with('Location', 'http://www.example.org/'); 161 | 162 | $route 163 | ->expects($this->any()) 164 | ->method('assemble') 165 | ->with([], ['name' => 'redirect/route']) 166 | ->will($this->returnValue('http://www.example.org/')); 167 | 168 | $mvcEvent->expects($this->once())->method('setResponse')->with($response); 169 | 170 | $this->strategy->onDispatchError($mvcEvent); 171 | } 172 | 173 | /** 174 | * @covers \BjyAuthorize\View\RedirectionStrategy::onDispatchError 175 | * @covers \BjyAuthorize\View\RedirectionStrategy::setRedirectUri 176 | */ 177 | public function testWillRedirectToRouteOnSetUri() 178 | { 179 | $this->strategy->setRedirectUri('http://www.example.org/'); 180 | 181 | $mvcEvent = $this->createMock(MvcEvent::class); 182 | $response = $this->createMock(Response::class); 183 | $routeMatch = $this->getMockBuilder(RouteMatch::class)->disableOriginalConstructor()->getMock(); 184 | $route = $this->createMock(RouteInterface::class); 185 | $headers = $this->createMock(Headers::class); 186 | 187 | $mvcEvent->expects($this->any())->method('getResponse')->will($this->returnValue($response)); 188 | $mvcEvent->expects($this->any())->method('getRouteMatch')->will($this->returnValue($routeMatch)); 189 | $mvcEvent->expects($this->any())->method('getRouter')->will($this->returnValue($route)); 190 | $mvcEvent->expects($this->any())->method('getError')->will($this->returnValue(Route::ERROR)); 191 | 192 | $response->expects($this->any())->method('getHeaders')->will($this->returnValue($headers)); 193 | $response->expects($this->once())->method('setStatusCode')->with(302); 194 | 195 | $headers->expects($this->once())->method('addHeaderLine')->with('Location', 'http://www.example.org/'); 196 | 197 | $mvcEvent->expects($this->once())->method('setResponse')->with($response); 198 | 199 | $this->strategy->onDispatchError($mvcEvent); 200 | } 201 | 202 | /** 203 | * @covers \BjyAuthorize\View\RedirectionStrategy::onDispatchError 204 | * @covers \BjyAuthorize\View\RedirectionStrategy::setRedirectUri 205 | */ 206 | public function testWillRedirectToRouteOnSetUriWithApplicationError() 207 | { 208 | $this->strategy->setRedirectUri('http://www.example.org/'); 209 | 210 | $mvcEvent = $this->createMock(MvcEvent::class); 211 | $response = $this->createMock(Response::class); 212 | $routeMatch = $this->getMockBuilder(RouteMatch::class)->disableOriginalConstructor()->getMock(); 213 | $route = $this->createMock(RouteInterface::class); 214 | $headers = $this->createMock(Headers::class); 215 | $exception = $this->createMock(UnAuthorizedException::class); 216 | 217 | $mvcEvent->expects($this->any())->method('getResponse')->will($this->returnValue($response)); 218 | $mvcEvent->expects($this->any())->method('getRouteMatch')->will($this->returnValue($routeMatch)); 219 | $mvcEvent->expects($this->any())->method('getRouter')->will($this->returnValue($route)); 220 | $mvcEvent->expects($this->any())->method('getError')->will($this->returnValue(Application::ERROR_EXCEPTION)); 221 | $mvcEvent->expects($this->any())->method('getParam')->with('exception')->will($this->returnValue($exception)); 222 | 223 | $response->expects($this->any())->method('getHeaders')->will($this->returnValue($headers)); 224 | $response->expects($this->once())->method('setStatusCode')->with(302); 225 | 226 | $headers->expects($this->once())->method('addHeaderLine')->with('Location', 'http://www.example.org/'); 227 | 228 | $mvcEvent->expects($this->once())->method('setResponse')->with($response); 229 | 230 | $this->strategy->onDispatchError($mvcEvent); 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BjyAuthorize - Acl security for Laminas 2 | 3 | [![Continuous Integration](https://github.com/kokspflanze/BjyAuthorize/actions/workflows/continous-integration.yml/badge.svg)](https://github.com/kokspflanze/BjyAuthorize/actions/workflows/continous-integration.yml) 4 | [![Total Downloads](https://poser.pugx.org/kokspflanze/bjy-authorize/downloads.png)](https://packagist.org/packages/kokspflanze/bjy-authorize) 5 | [![Latest Stable Version](https://poser.pugx.org/kokspflanze/bjy-authorize/v/stable.png)](https://packagist.org/packages/kokspflanze/bjy-authorize) 6 | 7 | 8 | This module is designed to provide a facade for `Laminas\Permissions\Acl` that will 9 | ease its usage with modules and applications. By default, it provides simple 10 | setup via config files or by using `Laminas\Db` or Doctrine ORM/ODM. 11 | 12 | ## Information 13 | 14 | This is a fork of the original [bjyoungblood/BjyAuthorize](https://github.com/bjyoungblood/BjyAuthorize) module. 15 | I added Laminas support, so starting with release 2.0.0 the module works with Laminas. 16 | Releases from the 1.x series are still targeted at Zend Framework 2 and 3. 17 | If you found a bug, please report it, just pm me in [gitter](https://gitter.im/kokspflanze) or open a PullRequest. 18 | 19 | ## What does BjyAuthorize do? 20 | 21 | BjyAuthorize adds event listeners to your application so that you have a "security" or "firewall" that disallows 22 | unauthorized access to your controllers or routes. 23 | 24 | This is what a normal `Laminas\Mvc` application workflow would look like: 25 | 26 | ![Laminas Mvc Application workflow](http://yuml.me/diagram/plain;/activity/%28start%29-%3E%28route%29%2C%20%28route%29-%3E%28get%20controller%29%2C%20%28get%20controller%29-%3E%28dispatch%29%2C%20%28dispatch%29-%3E%28end%29) 27 | 28 | And here's how it would look like with BjyAuthorize enabled: 29 | 30 | ![Laminas Mvc Application workflow with BjyAuthorize](http://yuml.me/diagram/plain;/activity/%28start%29-%3E%28route%29%2C%20%28route%29-%3E%3Ca%3E-no%20route%20guard%3E%28get%20controller%29%2C%20%3Ca%3E-%3E%28route%20guard%29%2C%20%28route%20guard%29-%3E%3Cb%3E-authorized%3E%28get%20controller%29%2C%20%3Cb%3Eunauthorized-%3E%28error%29%2C%20%28get%20controller%29-%3E%3Cc%3E-no%20controller%20guard%3E%28dispatch%29%2C%20%3Cc%3E-%3E%28controller%20guard%29%2C%20%28controller%20guard%29-%3E%3Cd%3E-authorized%3E%28dispatch%29%2C%20%3Cd%3Eunauthorized-%3E%28error%29%2C%20%28error%29-%3E%28end%29%2C%20%28dispatch%29-%3E%28end%29) 31 | 32 | ## Requirements 33 | 34 | * [Laminas](https://getlaminas.org/) 35 | * [LmcUser](https://github.com/LM-Commons/LmcUser) (optional) 36 | 37 | ## Installation 38 | 39 | ### Composer 40 | 41 | The suggested installation method is via [composer](http://getcomposer.org/): 42 | 43 | ```sh 44 | composer require kokspflanze/bjy-authorize 45 | ``` 46 | 47 | ## Configuration 48 | 49 | Following steps apply if you want to use `LmcUser` with `Laminas\Db`. If you want to use Doctrine ORM/ODM, you should 50 | also check the [doctrine documentation](https://github.com/kokspflanze/BjyAuthorize/blob/master/docs/doctrine.md). 51 | 52 | 1. Ensure that following modules are enabled in your `application.config.php` file in the this order: 53 | * `LmcUser` 54 | * `BjyAuthorize` 55 | 3. Import the SQL schema located in `./vendor/BjyAuthorize/data/schema.sql`. 56 | 4. Create a `./config/autoload/bjyauthorize.global.php` file and fill it with 57 | configuration variable values as described in the following annotated example. 58 | 59 | Here is an annotated sample configuration file: 60 | 61 | ```php 62 | [ 66 | 67 | // set the 'guest' role as default (must be defined in a role provider) 68 | 'default_role' => 'guest', 69 | 70 | /* this module uses a meta-role that inherits from any roles that should 71 | * be applied to the active user. the identity provider tells us which 72 | * roles the "identity role" should inherit from. 73 | * for LmcUser, this will be your default identity provider 74 | */ 75 | 'identity_provider' => \BjyAuthorize\Provider\Identity\LmcUserLaminasDb::class, 76 | 77 | /* If you only have a default role and an authenticated role, you can 78 | * use the 'AuthenticationIdentityProvider' to allow/restrict access 79 | * with the guards based on the state 'logged in' and 'not logged in'. 80 | * 81 | * 'default_role' => 'guest', // not authenticated 82 | * 'authenticated_role' => 'user', // authenticated 83 | * 'identity_provider' => \BjyAuthorize\Provider\Identity\AuthenticationIdentityProvider::class, 84 | */ 85 | 86 | /* role providers simply provide a list of roles that should be inserted 87 | * into the Laminas\Acl instance. the module comes with two providers, one 88 | * to specify roles in a config file and one to load roles using a 89 | * Laminas\Db adapter. 90 | */ 91 | 'role_providers' => [ 92 | 93 | /* here, 'guest' and 'user are defined as top-level roles, with 94 | * 'admin' inheriting from user 95 | */ 96 | \BjyAuthorize\Provider\Role\Config::class => [ 97 | 'guest' => [], 98 | 'user' => ['children' => [ 99 | 'admin' => [], 100 | ]], 101 | ], 102 | 103 | // this will load roles from the user_role table in a database 104 | // format: user_role(role_id(varchar], parent(varchar)) 105 | \BjyAuthorize\Provider\Role\LaminasDb::class => [ 106 | 'table' => 'user_role', 107 | 'identifier_field_name' => 'id', 108 | 'role_id_field' => 'role_id', 109 | 'parent_role_field' => 'parent_id', 110 | ], 111 | 112 | // this will load roles from 113 | // the 'BjyAuthorize\Provider\Role\ObjectRepositoryProvider' service 114 | \BjyAuthorize\Provider\Role\ObjectRepositoryProvider::class => [ 115 | // class name of the entity representing the role 116 | 'role_entity_class' => 'My\Role\Entity', 117 | // service name of the object manager 118 | 'object_manager' => 'My\Doctrine\Common\Persistence\ObjectManager', 119 | ], 120 | ], 121 | 122 | // resource providers provide a list of resources that will be tracked 123 | // in the ACL. like roles, they can be hierarchical 124 | 'resource_providers' => [ 125 | \BjyAuthorize\Provider\Resource\Config::class => [ 126 | 'pants' => [], 127 | ], 128 | ], 129 | 130 | /* rules can be specified here with the format: 131 | * [roles (array), resource, privilege (array|string), assertion] 132 | * assertions will be loaded using the service manager and must implement 133 | * Laminas\Acl\Assertion\AssertionInterface. 134 | * *if you use assertions, define them using the service manager!* 135 | */ 136 | 'rule_providers' => [ 137 | \BjyAuthorize\Provider\Rule\Config::class => [ 138 | 'allow' => [ 139 | // allow guests and users (and admins, through inheritance) 140 | // the "wear" privilege on the resource "pants" 141 | [['guest', 'user'], 'pants', 'wear'], 142 | ], 143 | 144 | // Don't mix allow/deny rules if you are using role inheritance. 145 | // There are some weird bugs. 146 | 'deny' => [ 147 | // ... 148 | ], 149 | ], 150 | ], 151 | 152 | /* Currently, only controller and route guards exist 153 | * 154 | * Consider enabling either the controller or the route guard depending on your needs. 155 | */ 156 | 'guards' => [ 157 | /* If this guard is specified here (i.e. it is enabled], it will block 158 | * access to all controllers and actions unless they are specified here. 159 | * You may omit the 'action' index to allow access to the entire controller 160 | */ 161 | \BjyAuthorize\Guard\Controller::class => [ 162 | ['controller' => 'index', 'action' => 'index', 'roles' => ['guest','user']], 163 | ['controller' => 'index', 'action' => 'stuff', 'roles' => ['user']], 164 | // You can also specify an array of actions or an array of controllers (or both) 165 | // allow "guest" and "admin" to access actions "list" and "manage" on these "index", 166 | // "static" and "console" controllers 167 | [ 168 | 'controller' => ['index', 'static', 'console'], 169 | 'action' => ['list', 'manage'], 170 | 'roles' => ['guest', 'admin'], 171 | ], 172 | [ 173 | 'controller' => ['search', 'administration'], 174 | 'roles' => ['staffer', 'admin'], 175 | ], 176 | ['controller' => 'lmcuser', 'roles' => []], 177 | // Below is the default index action used by the LaminasSkeletonApplication 178 | // ['controller' => 'Application\Controller\Index', 'roles' => ['guest', 'user']], 179 | ], 180 | 181 | /* If this guard is specified here (i.e. it is enabled], it will block 182 | * access to all routes unless they are specified here. 183 | */ 184 | \BjyAuthorize\Guard\Route::class => [ 185 | ['route' => 'lmcuser', 'roles' => ['user']], 186 | ['route' => 'lmcuser/logout', 'roles' => ['user']], 187 | ['route' => 'lmcuser/login', 'roles' => ['guest']], 188 | ['route' => 'lmcuser/register', 'roles' => ['guest']], 189 | // Below is the default index action used by the LaminasSkeletonApplication 190 | ['route' => 'home', 'roles' => ['guest', 'user']], 191 | ], 192 | ], 193 | ], 194 | ]; 195 | ``` 196 | 197 | ## Helpers and Plugins 198 | 199 | There are view helpers and controller plugins registered for this module. 200 | In either a controller or a view script, you can call 201 | ```$this->isAllowed($resource[, $privilege])```, which will query the ACL 202 | using the currently authenticated (or default) user's roles. 203 | 204 | Whenever you need to stop processing your action you can throw an UnAuthorizedException and users will see you message on a 403 page. 205 | 206 | ```php 207 | function cafeAction() { 208 | if (!$this->isAllowed('alcohol', 'consume')) { 209 | throw new \BjyAuthorize\Exception\UnAuthorizedException('Grow a beard first!'); 210 | } 211 | 212 | // party on ... 213 | } 214 | ``` 215 | 216 | ## License 217 | 218 | Released under the MIT License. See file [LICENSE](./LICENSE) 219 | included with the source code for this project for a copy of the licensing terms. 220 | --------------------------------------------------------------------------------