Skip to content

Conversation

@mnocon
Copy link
Contributor

@mnocon mnocon commented Nov 25, 2025

Things done:

  1. CustomController is renamed to CustomLimitationController
  2. I've decided that instead of splicing a new code sample for the doc with many include_file calls, it's cheaper to create a new short code sample based on the existing one. Reasoning: PHPStan and Rector make the maintenence of PHP files easier than fixing fragmented include_file calls manually.

@mnocon mnocon force-pushed the fix-performAccessCheck branch from 8451cec to f2b1166 Compare November 25, 2025 15:08
@github-actions
Copy link

Preview of modified files

Preview of modified Markdown:

@mnocon mnocon self-assigned this Nov 25, 2025
@mnocon mnocon marked this pull request as ready for review November 26, 2025 08:21
@mnocon mnocon requested a review from adriendupuis November 26, 2025 08:22
@github-actions
Copy link

code_samples/ change report

Before (on target branch)After (in current PR)

code_samples/back_office/limitation/src/Controller/CustomController.php


code_samples/back_office/limitation/src/Controller/CustomController.php

docs/permissions/permission_overview.md@42:``` php hl_lines="15-19"
docs/permissions/permission_overview.md@43:[[= include_file('code_samples/back_office/limitation/src/Controller/CustomController.php', 0, 20) =]]
docs/permissions/permission_overview.md@44:```

001⫶<?php declare(strict_types=1);
002⫶
003⫶namespace App\Controller;
004⫶
005⫶use Ibexa\Contracts\AdminUi\Controller\Controller;
006⫶use Ibexa\Contracts\User\Controller\AuthenticatedRememberedCheckTrait;
007⫶use Ibexa\Core\MVC\Symfony\Security\Authorization\Attribute;
008⫶
009⫶class CustomController extends Controller
010⫶{
011⫶ use AuthenticatedRememberedCheckTrait {
012⫶ AuthenticatedRememberedCheckTrait::performAccessCheck as public traitPerformAccessCheck;
013⫶ }
014⫶
015❇️ public function performAccessCheck(): void
016❇️ {
017❇️ $this->traitPerformAccessCheck();
018❇️ $this->denyAccessUnlessGranted(new Attribute('custom_module', 'custom_function_2'));
019❇️ }
020⫶}


code_samples/back_office/limitation/src/Controller/CustomLimitationController.php

docs/permissions/custom_policies.md@253:```php
docs/permissions/custom_policies.md@253:```php
docs/permissions/custom_policies.md@254:[[= include_file('code_samples/back_office/limitation/src/Controller/CustomController.php') =]]
docs/permissions/custom_policies.md@254:[[= include_file('code_samples/back_office/limitation/src/Controller/CustomLimitationController.php') =]]
docs/permissions/custom_policies.md@255:```

001⫶<?php declare(strict_types=1);
002⫶
003⫶namespace App\Controller;
004⫶
005⫶use App\Security\Limitation\CustomLimitationValue;
006⫶use Ibexa\Contracts\AdminUi\Controller\Controller;
007⫶use Ibexa\Contracts\AdminUi\Permission\PermissionCheckerInterface;
008⫶use Ibexa\Contracts\Core\Repository\PermissionResolver;
009⫶use Ibexa\Contracts\User\Controller\AuthenticatedRememberedCheckTrait;
docs/permissions/custom_policies.md@255:```

001⫶<?php declare(strict_types=1);
002⫶
003⫶namespace App\Controller;
004⫶
005⫶use App\Security\Limitation\CustomLimitationValue;
006⫶use Ibexa\Contracts\AdminUi\Controller\Controller;
007⫶use Ibexa\Contracts\AdminUi\Permission\PermissionCheckerInterface;
008⫶use Ibexa\Contracts\Core\Repository\PermissionResolver;
009⫶use Ibexa\Contracts\User\Controller\AuthenticatedRememberedCheckTrait;
010⫶use Ibexa\Contracts\User\Controller\RestrictedControllerInterface;
011⫶use Ibexa\Core\MVC\Symfony\Security\Authorization\Attribute;
012⫶use Symfony\Component\HttpFoundation\Request;
013⫶use Symfony\Component\HttpFoundation\Response;
014⫶
015⫶class CustomController extends Controller implements RestrictedControllerInterface
016⫶{
017⫶ use AuthenticatedRememberedCheckTrait {
018⫶ AuthenticatedRememberedCheckTrait::performAccessCheck as public traitPerformAccessCheck;
019⫶ }
020⫶
021⫶ public function __construct(
022⫶ // ...,
023⫶ private readonly PermissionResolver $permissionResolver,
024⫶ private readonly PermissionCheckerInterface $permissionChecker
025⫶ ) {
026⫶ }
027⫶
028⫶ // Controller actions...
029⫶ public function customAction(Request $request): Response
030⫶ {
031⫶ // ...
032⫶ if ($this->getCustomLimitationValue()) {
033⫶ // Action only for user having the custom limitation checked
034⫶ }
035⫶
036⫶ return new Response('<html><body>...</body></html>');
037⫶ }
038⫶
039⫶ private function getCustomLimitationValue(): bool
040⫶ {
041⫶ $hasAccess = $this->permissionResolver->hasAccess('custom_module', 'custom_function_2');
042⫶
043⫶ if (is_bool($hasAccess)) {
044⫶ return $hasAccess;
045⫶ }
046⫶
047⫶ $customLimitationValues = $this->permissionChecker->getRestrictions(
048⫶ $hasAccess,
049⫶ CustomLimitationValue::class
050⫶ );
051⫶
052⫶ return $customLimitationValues['value'] ?? false;
053⫶ }
054⫶
055⫶ public function performAccessCheck(): void
056⫶ {
057⫶ $this->traitPerformAccessCheck();
058⫶ $this->denyAccessUnlessGranted(new Attribute('custom_module', 'custom_function_2'));
059⫶ }
060⫶}


code_samples/back_office/limitation/src/Controller/CustomLimitationController.php
010⫶use Ibexa\Core\MVC\Symfony\Security\Authorization\Attribute;
011⫶use Symfony\Component\HttpFoundation\Request;
012⫶use Symfony\Component\HttpFoundation\Response;
013⫶
014⫶class CustomLimitationController extends Controller
015⫶{
016⫶ use AuthenticatedRememberedCheckTrait {
017⫶ AuthenticatedRememberedCheckTrait::performAccessCheck as public traitPerformAccessCheck;
018⫶ }
019⫶
020⫶ public function __construct(
021⫶ // ...,
022⫶ private readonly PermissionResolver $permissionResolver,
023⫶ private readonly PermissionCheckerInterface $permissionChecker
024⫶ ) {
025⫶ }
026⫶
027⫶ // Controller actions...
028⫶ public function customAction(Request $request): Response
029⫶ {
030⫶ // ...
031⫶ if ($this->getCustomLimitationValue()) {
032⫶ // Action only for user having the custom limitation checked
033⫶ }
034⫶
035⫶ return new Response('<html><body>...</body></html>');
036⫶ }
037⫶
038⫶ private function getCustomLimitationValue(): bool
039⫶ {
040⫶ $hasAccess = $this->permissionResolver->hasAccess('custom_module', 'custom_function_2');
041⫶
042⫶ if (is_bool($hasAccess)) {
043⫶ return $hasAccess;
044⫶ }
045⫶
046⫶ $customLimitationValues = $this->permissionChecker->getRestrictions(
047⫶ $hasAccess,
048⫶ CustomLimitationValue::class
049⫶ );
050⫶
051⫶ return $customLimitationValues['value'] ?? false;
052⫶ }
053⫶
054⫶ public function performAccessCheck(): void
055⫶ {
056⫶ $this->traitPerformAccessCheck();
057⫶ $this->denyAccessUnlessGranted(new Attribute('custom_module', 'custom_function_2'));
058⫶ }
059⫶}


Download colorized diff

Comment on lines +39 to +40
In the following example the user doesn't have access to the controller unless they have the `section/view` policy and are [logged in using the "rememeber me cookie"]([[= symfony_doc =]]/security.html#checking-to-see-if-a-user-is-logged-in).
It uses the [`AuthenticatedRememberedCheckTrait`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-User-Controller-AuthenticatedRememberedCheckTrait.html) for the latter check.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the description paragraph,
the policy must match example's new Attribute('custom_module', 'custom_function_2'),
the functions should be introduced in the same order than they're used in the example,
and there was a typo in "rememeber".

Suggested change
In the following example the user doesn't have access to the controller unless they have the `section/view` policy and are [logged in using the "rememeber me cookie"]([[= symfony_doc =]]/security.html#checking-to-see-if-a-user-is-logged-in).
It uses the [`AuthenticatedRememberedCheckTrait`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-User-Controller-AuthenticatedRememberedCheckTrait.html) for the latter check.
In the following example the user doesn't have access to the controller unless they are [logged in using the "remember me cookie"]([[= symfony_doc =]]/security.html#checking-to-see-if-a-user-is-logged-in) and have the `custom_module/custom_function_2` policy.
It uses the [`AuthenticatedRememberedCheckTrait::performAccessCheck()`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-User-Controller-AuthenticatedRememberedCheckTrait.html#method_performAccessCheck) for the cookie check.

I still don't get why we need to use the trait directly when we extends Admin UI Controller which already uses it so parent::performAccessCheck would work. https://doc.ibexa.co/en/5.0/api/php_api/php_api_reference/classes/Ibexa-Contracts-AdminUi-Controller-Controller.html.

Where you implements the RestrictedControllerInterface you don't inherit performAccessCheck, got to implement it, and to use the trait's one, here you need the method alias to have a method of the same name and still use it.

Comment on lines +5 to +18
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\Core\MVC\Symfony\Security\Authorization\Attribute;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class CustomLimitationController extends Controller
{
use AuthenticatedRememberedCheckTrait {
AuthenticatedRememberedCheckTrait::performAccessCheck as public traitPerformAccessCheck;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's a front office controller and we don't want to extend that. Could it simply implements the RestrictedControllerInterface?

Suggested change
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\Core\MVC\Symfony\Security\Authorization\Attribute;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class CustomLimitationController extends Controller
{
use AuthenticatedRememberedCheckTrait {
AuthenticatedRememberedCheckTrait::performAccessCheck as public traitPerformAccessCheck;
}
use App\Security\Limitation\CustomLimitationValue;
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\Component\HttpFoundation\Response;
class CustomLimitationController implements RestrictedControllerInterface
{
use AuthenticatedRememberedCheckTrait {
AuthenticatedRememberedCheckTrait::performAccessCheck as public traitPerformAccessCheck;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants