Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,56 +2,16 @@

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\Component\HttpFoundation\Response;

class CustomController extends Controller implements RestrictedControllerInterface
class CustomController extends Controller
{
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('<html><body>...</body></html>');
}

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;
}

public function performAccessCheck(): void
{
$this->traitPerformAccessCheck();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php declare(strict_types=1);

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\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;
}
Comment on lines +5 to +18
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;
}


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('<html><body>...</body></html>');
}

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;
}

public function performAccessCheck(): void
{
$this->traitPerformAccessCheck();
$this->denyAccessUnlessGranted(new Attribute('custom_module', 'custom_function_2'));
}
}
2 changes: 1 addition & 1 deletion docs/permissions/custom_policies.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,5 +251,5 @@ 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_file('code_samples/back_office/limitation/src/Controller/CustomController.php') =]]
[[= include_file('code_samples/back_office/limitation/src/Controller/CustomLimitationController.php') =]]
```
17 changes: 6 additions & 11 deletions docs/permissions/permission_overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,23 +34,18 @@

## 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.

Check failure on line 37 in docs/permissions/permission_overview.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/permissions/permission_overview.md#L37

[Ibexa.VariablesGlobal] Use global variable '[[= product_name_base =]]' instead of 'Ibexa'
Raw output
{"message": "[Ibexa.VariablesGlobal] Use global variable '[[= product_name_base =]]' instead of 'Ibexa'", "location": {"path": "docs/permissions/permission_overview.md", "range": {"start": {"line": 37, "column": 140}}}, "severity": "ERROR"}

Check failure on line 37 in docs/permissions/permission_overview.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/permissions/permission_overview.md#L37

[Ibexa.DomainTerms] Use 'back office' instead of 'AdminUi'
Raw output
{"message": "[Ibexa.DomainTerms] Use 'back office' instead of 'AdminUi'", "location": {"path": "docs/permissions/permission_overview.md", "range": {"start": {"line": 37, "column": 296}}}, "severity": "ERROR"}

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 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.

Check failure on line 40 in docs/permissions/permission_overview.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/permissions/permission_overview.md#L40

[Ibexa.VariablesGlobal] Use global variable '[[= product_name_base =]]' instead of 'Ibexa'
Raw output
{"message": "[Ibexa.VariablesGlobal] Use global variable '[[= product_name_base =]]' instead of 'Ibexa'", "location": {"path": "docs/permissions/permission_overview.md", "range": {"start": {"line": 40, "column": 90}}}, "severity": "ERROR"}
Comment on lines +39 to +40
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.


``` php
use Ibexa\Core\MVC\Symfony\Security\Authorization\Attribute;

public function performAccessCheck(): void
{
parent::performAccessCheck();
$this->denyAccessUnlessGranted(new Attribute('section', 'view'));
}
``` php hl_lines="15-19"
[[= include_file('code_samples/back_office/limitation/src/Controller/CustomController.php', 0, 20) =]]
```

`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`.
Expand Down
Loading