diff --git a/docs/en/3-0-migration-guide.rst b/docs/en/3-0-migration-guide.rst new file mode 100644 index 00000000..a3682bc7 --- /dev/null +++ b/docs/en/3-0-migration-guide.rst @@ -0,0 +1,27 @@ +3.0 Migration Guide +################### + +Authorization 3.0 contains new features and a few breaking changes. + +Breaking Changes +================ + +The following interfaces now have appropriate parameter and return types added: + +- ``AuthorizationServiceInterface`` +- ``IdentityInterface`` +- ``BeforePolicyInterface`` +- ``RequestPolicyInterface.php`` +- ``ResolverInterface`` + +Multiple optional arguments for ``applyScope`` +---------------------------------------------- + +``IdentityInterface::applyScope`` as well as ``AuthorizationServiceInterface::applyScope`` +allow multiple optional arguments to be added. + +Removed methods +--------------- + +- ``AuthorizationService::resultTypeCheck`` - has been replaced with an ``assert()`` call + diff --git a/src/AuthorizationService.php b/src/AuthorizationService.php index b6a11c90..e009a76e 100644 --- a/src/AuthorizationService.php +++ b/src/AuthorizationService.php @@ -106,13 +106,13 @@ protected function performCheck(?IdentityInterface $user, string $action, mixed /** * @inheritDoc */ - public function applyScope(?IdentityInterface $user, string $action, $resource): mixed + public function applyScope(?IdentityInterface $user, string $action, mixed $resource, mixed ...$optionalArgs): mixed { $this->authorizationChecked = true; $policy = $this->resolver->getPolicy($resource); $handler = $this->getScopeHandler($policy, $action); - return $handler($user, $resource); + return $handler($user, $resource, ...$optionalArgs); } /** diff --git a/src/AuthorizationServiceInterface.php b/src/AuthorizationServiceInterface.php index 02967c34..b1bec6f9 100644 --- a/src/AuthorizationServiceInterface.php +++ b/src/AuthorizationServiceInterface.php @@ -60,9 +60,15 @@ public function canResult(?IdentityInterface $user, string $action, mixed $resou * @param \Authorization\IdentityInterface|null $user The user to check permissions for. * @param string $action The action/operation being performed. * @param mixed $resource The resource being operated on. + * @param mixed $optionalArgs Multiple additional arguments which are passed to the scope * @return mixed The modified resource. */ - public function applyScope(?IdentityInterface $user, string $action, mixed $resource): mixed; + public function applyScope( + ?IdentityInterface $user, + string $action, + mixed $resource, + mixed ...$optionalArgs + ): mixed; /** * Return a boolean based on whether or not this object diff --git a/src/Controller/Component/AuthorizationComponent.php b/src/Controller/Component/AuthorizationComponent.php index 3b9c3bfd..b282ab29 100644 --- a/src/Controller/Component/AuthorizationComponent.php +++ b/src/Controller/Component/AuthorizationComponent.php @@ -151,9 +151,10 @@ protected function performCheck( * * @param mixed $resource The resource to apply a scope to. * @param string|null $action The action to apply a scope for. + * @param mixed $optionalArgs Multiple additional arguments which are passed to the scope * @return mixed */ - public function applyScope(mixed $resource, ?string $action = null): mixed + public function applyScope(mixed $resource, ?string $action = null, mixed ...$optionalArgs): mixed { $request = $this->getController()->getRequest(); if ($action === null) { @@ -164,7 +165,7 @@ public function applyScope(mixed $resource, ?string $action = null): mixed throw new MissingIdentityException('Identity must exist for applyScope() call.'); } - return $identity->applyScope($action, $resource); + return $identity->applyScope($action, $resource, ...$optionalArgs); } /** diff --git a/src/IdentityDecorator.php b/src/IdentityDecorator.php index fd5dbab3..6eb9c79f 100644 --- a/src/IdentityDecorator.php +++ b/src/IdentityDecorator.php @@ -76,9 +76,9 @@ public function canResult(string $action, $resource): ResultInterface /** * @inheritDoc */ - public function applyScope(string $action, $resource): mixed + public function applyScope(string $action, mixed $resource, mixed ...$optionalArgs): mixed { - return $this->authorization->applyScope($this, $action, $resource); + return $this->authorization->applyScope($this, $action, $resource, ...$optionalArgs); } /** diff --git a/src/IdentityInterface.php b/src/IdentityInterface.php index a441b4be..15f5d488 100644 --- a/src/IdentityInterface.php +++ b/src/IdentityInterface.php @@ -53,9 +53,10 @@ public function canResult(string $action, mixed $resource): ResultInterface; * * @param string $action The action/operation being performed. * @param mixed $resource The resource being operated on. + * @param mixed $optionalArgs Multiple additional arguments which are passed to the scope * @return mixed The modified resource. */ - public function applyScope(string $action, mixed $resource): mixed; + public function applyScope(string $action, mixed $resource, mixed ...$optionalArgs): mixed; /** * Get the decorated identity diff --git a/tests/TestCase/AuthorizationServiceTest.php b/tests/TestCase/AuthorizationServiceTest.php index 1b1b4563..a7a05d81 100644 --- a/tests/TestCase/AuthorizationServiceTest.php +++ b/tests/TestCase/AuthorizationServiceTest.php @@ -21,10 +21,13 @@ use Authorization\Policy\BeforePolicyInterface; use Authorization\Policy\Exception\MissingMethodException; use Authorization\Policy\MapResolver; +use Authorization\Policy\OrmResolver; use Authorization\Policy\Result; use Authorization\Policy\ResultInterface; +use Cake\Datasource\QueryInterface; use Cake\TestSuite\TestCase; use TestApp\Model\Entity\Article; +use TestApp\Model\Table\ArticlesTable; use TestApp\Policy\ArticlePolicy; use TestApp\Policy\MagicCallPolicy; @@ -173,6 +176,38 @@ public function testApplyScopeMethodMissing() $result = $service->applyScope($user, 'nope', $article); } + public function testApplyScopeAdditionalArguments() + { + $service = new AuthorizationService(new OrmResolver()); + $user = new IdentityDecorator($service, [ + 'id' => 9, + 'role' => 'admin', + ]); + + $articles = new ArticlesTable(); + $query = $this->createMock(QueryInterface::class); + $query->method('getRepository') + ->willReturn($articles); + + $query->expects($this->exactly(2)) + ->method('where') + ->with([ + 'identity_id' => 9, + 'firstArg' => 'first argument', + 'secondArg' => false, + ]) + ->willReturn($query); + + $result = $service->applyScope($user, 'additionalArguments', $query, 'first argument', false); + $this->assertInstanceOf(QueryInterface::class, $result); + $this->assertSame($query, $result); + + // Test with named args as well + $result = $service->applyScope($user, 'additionalArguments', $query, firstArg: 'first argument', secondArg: false); + $this->assertInstanceOf(QueryInterface::class, $result); + $this->assertSame($query, $result); + } + public function testBeforeFalse() { $entity = new Article(); diff --git a/tests/TestCase/Controller/Component/AuthorizationComponentTest.php b/tests/TestCase/Controller/Component/AuthorizationComponentTest.php index c56cce44..82da73a4 100644 --- a/tests/TestCase/Controller/Component/AuthorizationComponentTest.php +++ b/tests/TestCase/Controller/Component/AuthorizationComponentTest.php @@ -284,6 +284,28 @@ public function testApplyScopExplicitAction() $this->assertSame($query, $result); } + public function testApplyScopeAdditionalArguments() + { + $articles = new ArticlesTable(); + $query = $this->createMock(QueryInterface::class); + $query->method('getRepository') + ->willReturn($articles); + + $query->expects($this->once()) + ->method('where') + ->with([ + 'identity_id' => 1, + 'firstArg' => 'first argument', + 'secondArg' => false, + ]) + ->willReturn($query); + + $result = $this->Auth->applyScope($query, 'additionalArguments', 'first argument', false); + + $this->assertInstanceOf(QueryInterface::class, $result); + $this->assertSame($query, $result); + } + public function testAuthorizeSuccessCheckExplicitAction() { $article = new Article(['user_id' => 1]); diff --git a/tests/TestCase/IdentityDecoratorTest.php b/tests/TestCase/IdentityDecoratorTest.php index 89411503..69549d7a 100644 --- a/tests/TestCase/IdentityDecoratorTest.php +++ b/tests/TestCase/IdentityDecoratorTest.php @@ -67,6 +67,19 @@ public function testApplyScopeDelegation() $this->assertTrue($identity->applyScope('update', $resource)); } + public function testApplyScopeAdditionalParams() + { + $resource = new stdClass(); + $auth = $this->createMock(AuthorizationServiceInterface::class); + $identity = new IdentityDecorator($auth, ['id' => 1]); + + $auth->expects($this->once()) + ->method('applyScope') + ->with($identity, 'update', $resource, 'first argument', false) + ->will($this->returnValue(true)); + $this->assertTrue($identity->applyScope('update', $resource, 'first argument', false)); + } + public function testCall() { $data = new Article(['id' => 1]); diff --git a/tests/test_app/TestApp/Policy/ArticlesTablePolicy.php b/tests/test_app/TestApp/Policy/ArticlesTablePolicy.php index 447c8898..844e13c8 100644 --- a/tests/test_app/TestApp/Policy/ArticlesTablePolicy.php +++ b/tests/test_app/TestApp/Policy/ArticlesTablePolicy.php @@ -36,4 +36,13 @@ public function scopeModify(IdentityInterface $user, QueryInterface $query) 'identity_id' => $user['id'], ]); } + + public function scopeAdditionalArguments(IdentityInterface $user, QueryInterface $query, $firstArg, $secondArg) + { + return $query->where([ + 'identity_id' => $user['id'], + 'firstArg' => $firstArg, + 'secondArg' => $secondArg, + ]); + } }