diff --git a/code_samples/back_office/limitation/src/Controller/CustomController.php b/code_samples/back_office/limitation/src/Controller/CustomController.php index 4e60672cf5c..67f4c865991 100644 --- a/code_samples/back_office/limitation/src/Controller/CustomController.php +++ b/code_samples/back_office/limitation/src/Controller/CustomController.php @@ -2,60 +2,29 @@ namespace App\Controller; -use App\Security\Limitation\CustomLimitationValue; -use Ibexa\Contracts\AdminUi\Controller\Controller; -use Ibexa\Contracts\AdminUi\Permission\PermissionCheckerInterface; -use Ibexa\Contracts\Core\Repository\PermissionResolver; use Ibexa\Contracts\User\Controller\AuthenticatedRememberedCheckTrait; use Ibexa\Contracts\User\Controller\RestrictedControllerInterface; use Ibexa\Core\MVC\Symfony\Security\Authorization\Attribute; -use Symfony\Component\HttpFoundation\Request; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Attribute\Route; -class CustomController extends Controller implements RestrictedControllerInterface +class CustomController extends AbstractController implements RestrictedControllerInterface { use AuthenticatedRememberedCheckTrait { - AuthenticatedRememberedCheckTrait::performAccessCheck as public traitPerformAccessCheck; - } - - public function __construct( - // ..., - private readonly PermissionResolver $permissionResolver, - private readonly PermissionCheckerInterface $permissionChecker - ) { - } - - // Controller actions... - public function customAction(Request $request): Response - { - // ... - if ($this->getCustomLimitationValue()) { - // Action only for user having the custom limitation checked - } - - return new Response('...'); - } - - private function getCustomLimitationValue(): bool - { - $hasAccess = $this->permissionResolver->hasAccess('custom_module', 'custom_function_2'); - - if (is_bool($hasAccess)) { - return $hasAccess; - } - - $customLimitationValues = $this->permissionChecker->getRestrictions( - $hasAccess, - CustomLimitationValue::class - ); - - return $customLimitationValues['value'] ?? false; + AuthenticatedRememberedCheckTrait::performAccessCheck as private traitPerformAccessCheck; } #[\Override] public function performAccessCheck(): void { $this->traitPerformAccessCheck(); - $this->denyAccessUnlessGranted(new Attribute('custom_module', 'custom_function_2')); + $this->denyAccessUnlessGranted(new Attribute('section', 'view')); + } + + #[Route('/custom-controller', name: 'app.custom_controller')] + public function customAction(): Response + { + return new Response('Access granted'); } } diff --git a/code_samples/back_office/limitation/src/Controller/CustomLimitationController.php b/code_samples/back_office/limitation/src/Controller/CustomLimitationController.php new file mode 100644 index 00000000000..1e4895d1179 --- /dev/null +++ b/code_samples/back_office/limitation/src/Controller/CustomLimitationController.php @@ -0,0 +1,61 @@ + '%admin_group_name%'] + )] + public function customAction(Request $request): Response + { + // ... + if ($this->getCustomLimitationValue()) { + // Action only for user having the custom limitation checked + } + + return new Response('...'); + } + + private function getCustomLimitationValue(): bool + { + $hasAccess = $this->permissionResolver->hasAccess('custom_module', 'custom_function_2'); + + if (is_bool($hasAccess)) { + return $hasAccess; + } + + $customLimitationValues = $this->permissionChecker->getRestrictions( + $hasAccess, + CustomLimitationValue::class + ); + + return $customLimitationValues['value'] ?? false; + } + + #[\Override] + public function performAccessCheck(): void + { + parent::performAccessCheck(); + $this->denyAccessUnlessGranted(new Attribute('custom_module', 'custom_function_2')); + } +} diff --git a/deptrac.baseline.yaml b/deptrac.baseline.yaml index 4a0b3e6f11d..ee925d54e95 100644 --- a/deptrac.baseline.yaml +++ b/deptrac.baseline.yaml @@ -86,6 +86,8 @@ deptrac: App\Controller\CustomFilterController: - Ibexa\Bundle\Core\Controller - Ibexa\Core\MVC\Symfony\View\ContentView + App\Controller\CustomLimitationController: + - Ibexa\Core\MVC\Symfony\Security\Authorization\Attribute App\Controller\PaginationController: - Ibexa\Bundle\Core\Controller - Ibexa\Core\Pagination\Pagerfanta\ContentSearchAdapter diff --git a/docs/infrastructure_and_maintenance/request_lifecycle.md b/docs/infrastructure_and_maintenance/request_lifecycle.md index bd403c5ae24..b6f47bed5c3 100644 --- a/docs/infrastructure_and_maintenance/request_lifecycle.md +++ b/docs/infrastructure_and_maintenance/request_lifecycle.md @@ -111,6 +111,7 @@ If it finds a location, the request receives the attributes **`locationId`** and The `locale_listener` (priority 16) sets the request's **`_locale`** attribute. + !!! note "Permission control" Another `kernel.request` event listener is the `Ibexa\AdminUi\EventListener\RequestListener` (priority 13). diff --git a/docs/permissions/custom_policies.md b/docs/permissions/custom_policies.md index ac1aff60331..68e1f1bad83 100644 --- a/docs/permissions/custom_policies.md +++ b/docs/permissions/custom_policies.md @@ -257,10 +257,13 @@ For example, `translations/ibexa_content_forms_policies.en.yaml`: Check if current user has this custom limitation set to true from a custom controller: -``` php -[[= include_code('code_samples/back_office/limitation/src/Controller/CustomController.php') =]] +```php +[[= include_code('code_samples/back_office/limitation/src/Controller/CustomLimitationController.php') =]] ``` +The `siteaccess_group_whitelist` route default limits the route to the back office SiteAccess group. +For more information, see [Request lifecycle](request_lifecycle.md#siteaccess_group_whitelist). + ## Restrict access to form submissions By default, access to a [Form content item](form_builder_guide.md#forms-management) is controlled by the `content/read` policy. diff --git a/docs/permissions/permission_overview.md b/docs/permissions/permission_overview.md index 5f89078342d..0a9e197b533 100644 --- a/docs/permissions/permission_overview.md +++ b/docs/permissions/permission_overview.md @@ -34,23 +34,21 @@ The more role assignments and complex policies you add for a given user, the mor ## Permissions for custom controllers -You can control access to a custom controller by implementing the `performAccessCheck()` method. +You can control access to a custom controller by implementing the [`RestrictedControllerInterface`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-User-Controller-RestrictedControllerInterface.html) interface directly or, for back office controllers, by extending the [`\Ibexa\Contracts\AdminUi\Controller\Controller`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-AdminUi-Controller-Controller.html) class. -In the following example the user doesn't have access to the controller unless they have the `section/view` policy: +In the following example, the user doesn't have access to the controller unless they are [logged in]([[= symfony_doc =]]/security.html#checking-to-see-if-a-user-is-logged-in) and have the `section/view` policy. +The controller uses [`AuthenticatedRememberedCheckTrait::performAccessCheck()`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-User-Controller-AuthenticatedRememberedCheckTrait.html#method_performAccessCheck), aliased as `traitPerformAccessCheck()`, for the login check. -``` php {skip-validation} -use Ibexa\Core\MVC\Symfony\Security\Authorization\Attribute; - -public function performAccessCheck(): void -{ - parent::performAccessCheck(); - $this->denyAccessUnlessGranted(new Attribute('section', 'view')); -} +``` php hl_lines="14-16 18-23" +[[= include_code('code_samples/back_office/limitation/src/Controller/CustomController.php') =]] ``` +Back office controllers that extend `Ibexa\Contracts\AdminUi\Controller\Controller` already use `AuthenticatedRememberedCheckTrait`. +To add a policy check, override `performAccessCheck()` and call `parent::performAccessCheck()` first, as in the [custom limitation check example](custom_policies.md#custom-limitation-check). + `Attribute` accepts three arguments: -- `module` is the policy module (for example,`content`) +- `module` is the policy module (for example, `content`) - `function` is the function inside the module (for example, `read`) - `limitations` are optional limitations to check against. Here you can provide two keys: - `valueObject` is the object you want to check for, for example `ContentInfo`.