diff --git a/composer.json b/composer.json index 8dce7d7..32e6433 100644 --- a/composer.json +++ b/composer.json @@ -73,7 +73,7 @@ "stan": "@phpstan", "stan-baseline": "tools/phpstan --generate-baseline", "stan-setup": "phive install", - "rector-setup": "cp composer.json composer.backup && composer require --dev rector/rector:\"~2.3.1\" && mv composer.backup composer.json", + "rector-setup": "cp composer.json composer.backup && composer require --dev rector/rector --with-dependencies && mv composer.backup composer.json", "rector-check": "vendor/bin/rector process --dry-run", "rector-fix": "vendor/bin/rector process", "test": "phpunit", diff --git a/docs/en/component.md b/docs/en/component.md index 30d456b..278e85c 100644 --- a/docs/en/component.md +++ b/docs/en/component.md @@ -34,6 +34,12 @@ $this->loadComponent('Authorization.Authorization', [ ]); ``` +The same can be done at runtime, for example in `beforeFilter()`: + +```php +$this->Authorization->skipAuthorizationActions('login', 'logout'); +``` + By default, every action requires authorization when authorization checking is enabled. diff --git a/src/Controller/Component/AuthorizationComponent.php b/src/Controller/Component/AuthorizationComponent.php index c87a5e8..24c1d46 100644 --- a/src/Controller/Component/AuthorizationComponent.php +++ b/src/Controller/Component/AuthorizationComponent.php @@ -19,6 +19,7 @@ use Authorization\AuthorizationServiceInterface; use Authorization\Exception\ForbiddenException; use Authorization\IdentityInterface; +use Authorization\Policy\Result; use Authorization\Policy\ResultInterface; use Cake\Controller\Component; use Cake\Http\ServerRequest; @@ -130,6 +131,16 @@ protected function performCheck( $action = $this->getDefaultAction($request); } + $skipAuthorization = $this->checkAction($action, 'skipAuthorization'); + if ($skipAuthorization) { + $this->skipAuthorization(); + + return match ($method) { + 'can' => true, + 'canResult' => new Result(true), + }; + } + $identity = $this->getIdentity($request); if (!$identity instanceof IdentityInterface) { return $this->getService($request)->{$method}(null, $action, $resource); @@ -178,6 +189,22 @@ public function skipAuthorization() return $this; } + /** + * Adds actions that should skip the automatic authorization check. + * + * Actions registered here are marked as authorized in `authorizeAction()`, + * which runs on the configured `authorizationEvent`. + * + * @param string ...$actions Controller actions to skip authorization for. + * @return $this + */ + public function skipAuthorizationActions(string ...$actions) + { + $this->_config['skipAuthorization'] = array_merge($this->_config['skipAuthorization'], $actions); + + return $this; + } + /** * Allows to map controller action to another authorization policy action. * diff --git a/tests/TestCase/Controller/Component/AuthorizationComponentTest.php b/tests/TestCase/Controller/Component/AuthorizationComponentTest.php index b1d17e6..5b8aefd 100644 --- a/tests/TestCase/Controller/Component/AuthorizationComponentTest.php +++ b/tests/TestCase/Controller/Component/AuthorizationComponentTest.php @@ -550,6 +550,24 @@ public function testAuthorizeModel(): void $this->assertEquals(['foo', 'bar', 'baz'], $this->Auth->getConfig('authorizeModel')); } + public function testSkipAuthorizationActions(): void + { + $this->Auth->skipAuthorizationActions('foo', 'bar'); + $this->assertEquals(['foo', 'bar'], $this->Auth->getConfig('skipAuthorization')); + + $this->Auth->skipAuthorizationActions('baz'); + $this->assertEquals(['foo', 'bar', 'baz'], $this->Auth->getConfig('skipAuthorization')); + } + + public function testSkipAuthorizationActionsAppliedOnAuthorizeAction(): void + { + $service = $this->Controller->getRequest()->getAttribute('authorization'); + + $this->Auth->skipAuthorizationActions('edit'); + $this->Auth->authorizeAction(); + $this->assertTrue($service->authorizationChecked()); + } + public function testMapAction(): void { $this->Auth->mapAction('foo', 'bar');