Skip to content
Merged
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,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('<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;
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('<html><body>Access granted</body></html>');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?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\Core\MVC\Symfony\Security\Authorization\Attribute;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class CustomLimitationController extends Controller
{
public function __construct(
// ...,
private readonly PermissionResolver $permissionResolver,
private readonly PermissionCheckerInterface $permissionChecker
) {
}

// Controller actions...
#[Route(
'/custom-limitation',
name: 'app.custom_limitation',
defaults: ['siteaccess_group_whitelist' => '%admin_group_name%']
)]
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;
}

#[\Override]
public function performAccessCheck(): void
{
parent::performAccessCheck();
$this->denyAccessUnlessGranted(new Attribute('custom_module', 'custom_function_2'));
}
}
2 changes: 2 additions & 0 deletions deptrac.baseline.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions docs/infrastructure_and_maintenance/request_lifecycle.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

<a id="siteaccess_group_whitelist">
!!! note "Permission control"

Another `kernel.request` event listener is the `Ibexa\AdminUi\EventListener\RequestListener` (priority 13).
Expand Down
7 changes: 5 additions & 2 deletions docs/permissions/custom_policies.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Really good to explain that part.

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.
Expand Down
20 changes: 9 additions & 11 deletions docs/permissions/permission_overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -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') =]]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

OK, I understand why a renamed file is in fact still there 😅

```

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