From 004e5acb06d39e27ae6360f0a62ce0f772db82bc Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Mon, 15 Jan 2024 20:52:44 +0100 Subject: [PATCH 1/4] add DIC support to OrmResolver --- src/Policy/OrmResolver.php | 25 ++++++++++++++++--- tests/TestCase/Policy/OrmResolverTest.php | 24 ++++++++++++++++++ .../test_app/TestApp/Policy/ArticlePolicy.php | 18 +++++++++++++ .../test_app/TestApp/Service/TestService.php | 12 +++++++++ 4 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 tests/test_app/TestApp/Service/TestService.php diff --git a/src/Policy/OrmResolver.php b/src/Policy/OrmResolver.php index 44b93293..b667e3ae 100644 --- a/src/Policy/OrmResolver.php +++ b/src/Policy/OrmResolver.php @@ -18,6 +18,7 @@ use Authorization\Policy\Exception\MissingPolicyException; use Cake\Core\App; +use Cake\Core\ContainerInterface; use Cake\Datasource\EntityInterface; use Cake\Datasource\QueryInterface; use Cake\Datasource\RepositoryInterface; @@ -43,16 +44,28 @@ class OrmResolver implements ResolverInterface */ protected array $overrides = []; + /** + * The DIC instance from the application + * + * @var \Cake\Core\ContainerInterface|null + */ + protected ?ContainerInterface $container; + /** * Constructor * * @param string $appNamespace The application namespace * @param array $overrides A list of plugin name overrides. + * @param \Cake\Core\ContainerInterface|null $container The DIC instance from the application */ - public function __construct(string $appNamespace = 'App', array $overrides = []) - { + public function __construct( + string $appNamespace = 'App', + array $overrides = [], + ?ContainerInterface $container = null + ) { $this->appNamespace = $appNamespace; $this->overrides = $overrides; + $this->container = $container; } /** @@ -146,7 +159,13 @@ protected function findPolicy(string $class, string $name, string $namespace): m throw new MissingPolicyException([$class]); } - return new $policyClass(); + if ($this->container && $this->container->has($policyClass)) { + $policy = $this->container->get($policyClass); + } else { + $policy = new $policyClass(); + } + + return $policy; } /** diff --git a/tests/TestCase/Policy/OrmResolverTest.php b/tests/TestCase/Policy/OrmResolverTest.php index f22db452..89056a14 100644 --- a/tests/TestCase/Policy/OrmResolverTest.php +++ b/tests/TestCase/Policy/OrmResolverTest.php @@ -16,8 +16,11 @@ */ namespace Authorization\Test\TestCase\Policy; +use Authorization\AuthorizationService; +use Authorization\IdentityDecorator; use Authorization\Policy\Exception\MissingPolicyException; use Authorization\Policy\OrmResolver; +use Cake\Core\Container; use Cake\ORM\Entity; use Cake\ORM\Locator\LocatorAwareTrait; use Cake\TestSuite\TestCase; @@ -27,6 +30,7 @@ use TestApp\Policy\ArticlePolicy; use TestApp\Policy\ArticlesTablePolicy; use TestApp\Policy\TestPlugin\BookmarkPolicy; +use TestApp\Service\TestService; use TestPlugin\Model\Entity\Bookmark; use TestPlugin\Model\Entity\Tag; use TestPlugin\Policy\TagPolicy; @@ -119,4 +123,24 @@ public function testGetPolicyUnknownTable() $resolver = new OrmResolver('TestApp'); $resolver->getPolicy($articles); } + + public function testGetPolicyViaDIC() + { + $container = new Container(); + $container->add(TestService::class); + $container->add(ArticlePolicy::class) + ->addArgument(TestService::class); + + $article = new Article(); + $resolver = new OrmResolver('TestApp', [], $container); + + $service = new AuthorizationService($resolver); + $user = new IdentityDecorator($service, [ + 'role' => 'admin', + ]); + + $policy = $resolver->getPolicy($article); + $this->assertInstanceOf(ArticlePolicy::class, $policy); + $this->assertTrue($policy->canWithInjectedService($user, $article)); + } } diff --git a/tests/test_app/TestApp/Policy/ArticlePolicy.php b/tests/test_app/TestApp/Policy/ArticlePolicy.php index 3ef6346e..cc5b5a68 100644 --- a/tests/test_app/TestApp/Policy/ArticlePolicy.php +++ b/tests/test_app/TestApp/Policy/ArticlePolicy.php @@ -6,9 +6,22 @@ use Authorization\Policy\Result; use Closure; use TestApp\Model\Entity\Article; +use TestApp\Service\TestService; class ArticlePolicy { + /** + * A service class injected via DIC + * + * @var \TestApp\Service\TestService|null + */ + protected ?TestService $service; + + public function __construct(?TestService $service = null) + { + $this->service = $service; + } + /** * Create articles if you're an admin or author * @@ -131,4 +144,9 @@ public function canWithMultipleServices($user, Article $article, Closure $servic { return $service1() && $service2(); } + + public function canWithInjectedService($user, Article $article) + { + return $this->service->serviceLogic(); + } } diff --git a/tests/test_app/TestApp/Service/TestService.php b/tests/test_app/TestApp/Service/TestService.php new file mode 100644 index 00000000..0b0298ca --- /dev/null +++ b/tests/test_app/TestApp/Service/TestService.php @@ -0,0 +1,12 @@ + Date: Sat, 20 Jan 2024 14:36:50 +0100 Subject: [PATCH 2/4] add DIC support to MapResolver --- src/Policy/MapResolver.php | 16 ++++++++++++++- tests/TestCase/Policy/MapResolverTest.php | 25 +++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/Policy/MapResolver.php b/src/Policy/MapResolver.php index 4b584971..0797e4f3 100644 --- a/src/Policy/MapResolver.php +++ b/src/Policy/MapResolver.php @@ -17,6 +17,7 @@ namespace Authorization\Policy; use Authorization\Policy\Exception\MissingPolicyException; +use Cake\Core\ContainerInterface; use InvalidArgumentException; /** @@ -32,6 +33,13 @@ class MapResolver implements ResolverInterface */ protected array $map = []; + /** + * The DIC instance from the application + * + * @var \Cake\Core\ContainerInterface|null + */ + protected ?ContainerInterface $container; + /** * Constructor. * @@ -45,9 +53,11 @@ class MapResolver implements ResolverInterface * ``` * * @param array $map Resource class name to policy map. + * @param \Cake\Core\ContainerInterface|null $container The DIC instance from the application */ - public function __construct(array $map = []) + public function __construct(array $map = [], ?ContainerInterface $container = null) { + $this->container = $container; foreach ($map as $resourceClass => $policy) { $this->map($resourceClass, $policy); } @@ -107,6 +117,10 @@ public function getPolicy($resource): mixed return $policy; } + if ($this->container && $this->container->has($policy)) { + return $this->container->get($policy); + } + return new $policy(); } } diff --git a/tests/TestCase/Policy/MapResolverTest.php b/tests/TestCase/Policy/MapResolverTest.php index 64fd640b..1e7a10f3 100644 --- a/tests/TestCase/Policy/MapResolverTest.php +++ b/tests/TestCase/Policy/MapResolverTest.php @@ -16,12 +16,16 @@ */ namespace Authorization\Test\TestCase\Policy; +use Authorization\AuthorizationService; +use Authorization\IdentityDecorator; use Authorization\Policy\Exception\MissingPolicyException; use Authorization\Policy\MapResolver; +use Cake\Core\Container; use Cake\TestSuite\TestCase; use InvalidArgumentException; use TestApp\Model\Entity\Article; use TestApp\Policy\ArticlePolicy; +use TestApp\Service\TestService; class MapResolverTest extends TestCase { @@ -102,4 +106,25 @@ public function testGetPolicyMissing() $resolver->getPolicy(new Article()); } + + public function testGetPolicyViaDIC() + { + $container = new Container(); + $container->add(TestService::class); + $container->add(ArticlePolicy::class) + ->addArgument(TestService::class); + + $article = new Article(); + $resolver = new MapResolver([], $container); + $resolver->map(Article::class, ArticlePolicy::class); + + $service = new AuthorizationService($resolver); + $user = new IdentityDecorator($service, [ + 'role' => 'admin', + ]); + + $policy = $resolver->getPolicy($article); + $this->assertInstanceOf(ArticlePolicy::class, $policy); + $this->assertTrue($policy->canWithInjectedService($user, $article)); + } } From dda8730ff6ff9da14dc2f35d4fd38e3d438d39f7 Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Sat, 11 Nov 2023 11:17:23 +0100 Subject: [PATCH 3/4] add authorization service to DIC by default --- src/Middleware/AuthorizationMiddleware.php | 7 +++++++ .../AuthorizationMiddlewareTest.php | 20 +++++++++++++++++++ tests/test_app/TestApp/Application.php | 18 ++++++++++++++++- 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/Middleware/AuthorizationMiddleware.php b/src/Middleware/AuthorizationMiddleware.php index 337a050b..5f2f8618 100644 --- a/src/Middleware/AuthorizationMiddleware.php +++ b/src/Middleware/AuthorizationMiddleware.php @@ -18,6 +18,7 @@ use ArrayAccess; use Authentication\IdentityInterface as AuthenIdentityInterface; +use Authorization\AuthorizationService; use Authorization\AuthorizationServiceInterface; use Authorization\AuthorizationServiceProviderInterface; use Authorization\Exception\AuthorizationRequiredException; @@ -26,6 +27,7 @@ use Authorization\IdentityDecorator; use Authorization\IdentityInterface; use Authorization\Middleware\UnauthorizedHandler\UnauthorizedHandlerTrait; +use Cake\Core\ContainerApplicationInterface; use Cake\Core\InstanceConfigTrait; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -104,6 +106,11 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface $service = $this->getAuthorizationService($request); $request = $request->withAttribute('authorization', $service); + if ($this->subject instanceof ContainerApplicationInterface) { + $container = $this->subject->getContainer(); + $container->add(AuthorizationService::class, $service); + } + $attribute = $this->getConfig('identityAttribute'); $identity = $request->getAttribute($attribute); diff --git a/tests/TestCase/Middleware/AuthorizationMiddlewareTest.php b/tests/TestCase/Middleware/AuthorizationMiddlewareTest.php index cd1ee0d9..c2d91b02 100644 --- a/tests/TestCase/Middleware/AuthorizationMiddlewareTest.php +++ b/tests/TestCase/Middleware/AuthorizationMiddlewareTest.php @@ -16,6 +16,7 @@ */ namespace Authorization\Test\TestCase\Middleware; +use Authorization\AuthorizationService; use Authorization\AuthorizationServiceInterface; use Authorization\AuthorizationServiceProviderInterface; use Authorization\Exception\AuthorizationRequiredException; @@ -25,12 +26,14 @@ use Authorization\Middleware\AuthorizationMiddleware; use Cake\Http\Response; use Cake\Http\ServerRequest; +use Cake\Http\ServerRequestFactory; use Cake\TestSuite\TestCase; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use RuntimeException; use stdClass; +use TestApp\Application; use TestApp\Http\TestRequestHandler; use TestApp\Identity; @@ -270,4 +273,21 @@ public function testUnauthorizedHandlerRequireAuthz() $result = $middleware->process($request, $handler); $this->assertSame(200, $result->getStatusCode()); } + + public function testMiddlewareInjectsServiceIntoDIC() + { + $request = ServerRequestFactory::fromGlobals( + ['REQUEST_URI' => '/testpath'], + [], + ['username' => 'mariano', 'password' => 'password'] + ); + $handler = new TestRequestHandler(); + $application = new Application('config'); + + $middleware = new AuthorizationMiddleware($application, ['requireAuthorizationCheck' => false]); + $middleware->process($request, $handler); + + $container = $application->getContainer(); + $this->assertInstanceOf(AuthorizationService::class, $container->get(AuthorizationService::class)); + } } diff --git a/tests/test_app/TestApp/Application.php b/tests/test_app/TestApp/Application.php index ed753482..508e75cf 100644 --- a/tests/test_app/TestApp/Application.php +++ b/tests/test_app/TestApp/Application.php @@ -3,11 +3,18 @@ namespace TestApp; +use Authorization\AuthorizationService; +use Authorization\AuthorizationServiceInterface; +use Authorization\AuthorizationServiceProviderInterface; +use Authorization\Policy\MapResolver; use Cake\Http\BaseApplication; use Cake\Http\MiddlewareQueue; use Cake\Routing\RouteBuilder; +use Psr\Http\Message\ServerRequestInterface; +use TestApp\Model\Entity\Article; +use TestApp\Policy\ArticlePolicy; -class Application extends BaseApplication +class Application extends BaseApplication implements AuthorizationServiceProviderInterface { public function middleware(MiddlewareQueue $middleware): MiddlewareQueue { @@ -23,4 +30,13 @@ public function bootstrap(): void $this->addPlugin('Authorization'); $this->addPlugin('Bake'); } + + public function getAuthorizationService(ServerRequestInterface $request): AuthorizationServiceInterface + { + $resolver = new MapResolver([ + Article::class => ArticlePolicy::class, + ]); + + return new AuthorizationService($resolver); + } } From c45771cd5b65b772978f0cff23ab10eba0a310a9 Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Mon, 22 Jan 2024 21:02:14 +0100 Subject: [PATCH 4/4] add ability to set custom container instance --- src/Middleware/AuthorizationMiddleware.php | 15 ++++++++++- .../AuthorizationMiddlewareTest.php | 26 +++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/Middleware/AuthorizationMiddleware.php b/src/Middleware/AuthorizationMiddleware.php index 5f2f8618..b3f2213b 100644 --- a/src/Middleware/AuthorizationMiddleware.php +++ b/src/Middleware/AuthorizationMiddleware.php @@ -28,6 +28,7 @@ use Authorization\IdentityInterface; use Authorization\Middleware\UnauthorizedHandler\UnauthorizedHandlerTrait; use Cake\Core\ContainerApplicationInterface; +use Cake\Core\ContainerInterface; use Cake\Core\InstanceConfigTrait; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -73,16 +74,25 @@ class AuthorizationMiddleware implements MiddlewareInterface */ protected AuthorizationServiceInterface|AuthorizationServiceProviderInterface $subject; + /** + * The container instance from the application + * + * @var \Cake\Core\ContainerInterface|null + */ + protected ?ContainerInterface $container = null; + /** * Constructor. * * @param \Authorization\AuthorizationServiceInterface|\Authorization\AuthorizationServiceProviderInterface $subject Authorization service or provider instance. * @param array $config Config array. + * @param \Cake\Core\ContainerInterface|null $container The container instance from the application * @throws \InvalidArgumentException */ public function __construct( AuthorizationServiceInterface|AuthorizationServiceProviderInterface $subject, - array $config = [] + array $config = [], + ?ContainerInterface $container = null ) { if ($this->_defaultConfig['identityDecorator'] === null) { $this->_defaultConfig['identityDecorator'] = interface_exists(AuthenIdentityInterface::class) @@ -91,6 +101,7 @@ public function __construct( } $this->subject = $subject; + $this->container = $container; $this->setConfig($config); } @@ -109,6 +120,8 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface if ($this->subject instanceof ContainerApplicationInterface) { $container = $this->subject->getContainer(); $container->add(AuthorizationService::class, $service); + } elseif ($this->container) { + $this->container->add(AuthorizationService::class, $service); } $attribute = $this->getConfig('identityAttribute'); diff --git a/tests/TestCase/Middleware/AuthorizationMiddlewareTest.php b/tests/TestCase/Middleware/AuthorizationMiddlewareTest.php index c2d91b02..591a1684 100644 --- a/tests/TestCase/Middleware/AuthorizationMiddlewareTest.php +++ b/tests/TestCase/Middleware/AuthorizationMiddlewareTest.php @@ -24,6 +24,7 @@ use Authorization\IdentityDecorator; use Authorization\IdentityInterface; use Authorization\Middleware\AuthorizationMiddleware; +use Cake\Core\Container; use Cake\Http\Response; use Cake\Http\ServerRequest; use Cake\Http\ServerRequestFactory; @@ -290,4 +291,29 @@ public function testMiddlewareInjectsServiceIntoDIC() $container = $application->getContainer(); $this->assertInstanceOf(AuthorizationService::class, $container->get(AuthorizationService::class)); } + + public function testMiddlewareInjectsServiceIntoDICViaCustomContainerInstance() + { + $request = ServerRequestFactory::fromGlobals( + ['REQUEST_URI' => '/testpath'], + [], + ['username' => 'mariano', 'password' => 'password'] + ); + $handler = new TestRequestHandler(); + + $service = $this->createMock(AuthorizationServiceInterface::class); + $provider = $this->createMock(AuthorizationServiceProviderInterface::class); + $provider + ->expects($this->once()) + ->method('getAuthorizationService') + ->with($this->isInstanceOf(ServerRequestInterface::class)) + ->willReturn($service); + + $container = new Container(); + + $middleware = new AuthorizationMiddleware($provider, ['requireAuthorizationCheck' => false], $container); + $middleware->process($request, $handler); + + $this->assertEquals($service, $container->get(AuthorizationService::class)); + } }