├── .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 |
--------------------------------------------------------------------------------
/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 |
10 | - Menu 1
11 | - Menu 2
12 | Menu 3
13 |
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 |
62 | isAllowed('menu', 'menu_menu1')) { ?>
63 | - Menu 1
64 |
65 |
66 | isAllowed('menu', 'menu_menu2')) { ?>
67 | - Menu 2
68 |
69 |
70 | isAllowed('menu', 'menu_menu3')) { ?>
71 | - Menu 3
72 |
73 |
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 | [](https://github.com/kokspflanze/BjyAuthorize/actions/workflows/continous-integration.yml)
4 | [](https://packagist.org/packages/kokspflanze/bjy-authorize)
5 | [](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 | 
27 |
28 | And here's how it would look like with BjyAuthorize enabled:
29 |
30 | 
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 |
--------------------------------------------------------------------------------