Skip to content
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
services:
App\EventSubscriber\InteractiveLoginSubscriber:
App\EventSubscriber\AuthenticationTokenCreatedSubscriber:
arguments:
$userMap:
from_memory_user: generic_customer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@

use Ibexa\Contracts\Core\Repository\UserService;
use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface;
//use Ibexa\Core\MVC\Symfony\Security\User;
use Ibexa\Core\MVC\Symfony\Security\UserWrapped;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Core\User\InMemoryUser;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\Security\Http\SecurityEvents;
use Symfony\Component\Security\Http\Event\AuthenticationTokenCreatedEvent;

final readonly class InteractiveLoginSubscriber implements EventSubscriberInterface
final readonly class AuthenticationTokenCreatedSubscriber implements EventSubscriberInterface
{
/** @param array<string, string> $userMap */
public function __construct(
Expand All @@ -24,17 +22,18 @@ public function __construct(
public static function getSubscribedEvents(): array
{
return [
SecurityEvents::INTERACTIVE_LOGIN => 'onInteractiveLogin',
AuthenticationTokenCreatedEvent::class => ['onAuthenticationTokenCreated', 11],
];
}

public function onInteractiveLogin(InteractiveLoginEvent $event): void
public function onAuthenticationTokenCreated(AuthenticationTokenCreatedEvent $event): void
{
$tokenUser = $event->getAuthenticationToken()->getUser();
$token = $event->getAuthenticatedToken();
$tokenUser = $token->getUser();
if (!$tokenUser instanceof InMemoryUser) {
return;
}
$userIdentifier = $event->getAuthenticationToken()->getUserIdentifier();
$userIdentifier = $token->getUserIdentifier();
$ibexaUser = null;
if (array_key_exists($userIdentifier, $this->userMap)) {
$ibexaUser = $this->userService->loadUserByLogin($this->userMap[$userIdentifier]);
Expand All @@ -43,7 +42,6 @@ public function onInteractiveLogin(InteractiveLoginEvent $event): void
$anonymousUserId = (int)$this->configResolver->getParameter('anonymous_user_id');
$ibexaUser = $this->userService->loadUser($anonymousUserId);
}
//$event->getAuthenticationToken()->setUser(new User($ibexaUser));
$event->getAuthenticationToken()->setUser(new UserWrapped($tokenUser, $ibexaUser));
$token->setUser(new UserWrapped($tokenUser, $ibexaUser));

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@konradoboza Did you mean something like this?:

Suggested change
$token->setUser(new UserWrapped($tokenUser, $ibexaUser));
$this->permissionResolver->setCurrentUserReference($ibexaUser);
$token->setUser(new UserWrapped($tokenUser, $ibexaUser));

(after injecting the PermissionResolver in constructor as well)

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.

Well, I am not sure about the priority. It would be good to check if this comes before OnAuthenticationTokenCreatedRepositoryUserSubscriber so setting the repository user isn't needed here as it will be done anyway.

}
}
5 changes: 2 additions & 3 deletions deptrac.baseline.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ deptrac:
App\EventListener\TextAnchorMenuTabListener:
- Ibexa\AdminUi\Menu\ContentEditAnchorMenuBuilder
- Ibexa\AdminUi\Menu\Event\ConfigureMenuEvent
App\EventSubscriber\AuthenticationTokenCreatedSubscriber:
- Ibexa\Core\MVC\Symfony\Security\UserWrapped
Comment thread
adriendupuis marked this conversation as resolved.
App\EventSubscriber\BreadcrumbsMenuSubscriber:
- Ibexa\Bundle\Storefront\Menu\Builder\BreadcrumbsMenuBuilder
App\EventSubscriber\FormFieldDefinitionSubscriber:
Expand All @@ -128,9 +130,6 @@ deptrac:
- Ibexa\FormBuilder\Event\FormEvents
App\EventSubscriber\HelpMenuSubscriber:
- Ibexa\AdminUi\Menu\Event\ConfigureMenuEvent
App\EventSubscriber\InteractiveLoginSubscriber:
#- Ibexa\Core\MVC\Symfony\Security\User
- Ibexa\Core\MVC\Symfony\Security\UserWrapped
App\EventSubscriber\MyMenuSubscriber:
- Ibexa\AdminUi\Menu\Event\ConfigureMenuEvent
- Ibexa\AdminUi\Menu\MainMenuBuilder
Expand Down
22 changes: 16 additions & 6 deletions docs/users/user_authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,34 +9,34 @@
Symfony provides native support for [multiple user providers]([[= symfony_doc =]]/security/user_providers.html).
This makes it easier to integrate any kind of login handlers, including SSO and existing third party bundles (for example, [FR3DLdapBundle](https://github.com/Maks3w/FR3DLdapBundle), [HWIOauthBundle](https://github.com/hwi/HWIOAuthBundle), [FOSUserBundle](https://github.com/FriendsOfSymfony/FOSUserBundle), or [BeSimpleSsoAuthBundle](https://github.com/BeSimple/BeSimpleSsoAuthBundle)).

However, to be able to use *external* user providers with [[= product_name =]], a valid Ibexa user needs to be injected into the repository.

Check failure on line 12 in docs/users/user_authentication.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/users/user_authentication.md#L12

[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/users/user_authentication.md", "range": {"start": {"line": 12, "column": 89}}}, "severity": "ERROR"}

Check notice on line 12 in docs/users/user_authentication.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/users/user_authentication.md#L12

[Ibexa.Passive] Try to avoid passive tense, when possible.
Raw output
{"message": "[Ibexa.Passive] Try to avoid passive tense, when possible.", "location": {"path": "docs/users/user_authentication.md", "range": {"start": {"line": 12, "column": 109}}}, "severity": "INFO"}
This is mainly for the kernel to be able to manage content-related permissions (but not limited to this).

Depending on your context, you either want to create and return an Ibexa user, or return an existing user, even a generic one.

Check failure on line 15 in docs/users/user_authentication.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/users/user_authentication.md#L15

[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/users/user_authentication.md", "range": {"start": {"line": 15, "column": 68}}}, "severity": "ERROR"}

Whenever a user is matched, Symfony initiates a `SecurityEvents::INTERACTIVE_LOGIN` event.
Every service listening to this event receives an `InteractiveLoginEvent` object which contains the original security token (that holds the matched user) and the request.
Whenever a user is matched and authenticated, Symfony initiates an `AuthenticationTokenCreatedEvent`.

Check notice on line 17 in docs/users/user_authentication.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/users/user_authentication.md#L17

[Ibexa.Passive] Try to avoid passive tense, when possible.
Raw output
{"message": "[Ibexa.Passive] Try to avoid passive tense, when possible.", "location": {"path": "docs/users/user_authentication.md", "range": {"start": {"line": 17, "column": 17}}}, "severity": "INFO"}
Every service listening to this event receives an object which contains the original security token (that holds the matched user) and a [passport]([[= symfony_doc =]]/security/custom_authenticator.html#security-passports).

Then, it's up to a listener to retrieve an Ibexa user from the repository.

Check failure on line 20 in docs/users/user_authentication.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/users/user_authentication.md#L20

[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/users/user_authentication.md", "range": {"start": {"line": 20, "column": 44}}}, "severity": "ERROR"}

This Ibexa user can be

Check failure on line 22 in docs/users/user_authentication.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/users/user_authentication.md#L22

[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/users/user_authentication.md", "range": {"start": {"line": 22, "column": 6}}}, "severity": "ERROR"}

- embedded into `Ibexa\Core\MVC\Symfony\Security\User` while forgetting about the original user
- wrapped into `Ibexa\Core\MVC\Symfony\Security\UserWrapped` with the original user if needed

Finally, this user is assigned back into the event's token for the rest of the request.
Finally, this user is assigned back into the event's token for the rest of the process.

Check notice on line 27 in docs/users/user_authentication.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/users/user_authentication.md#L27

[Ibexa.Passive] Try to avoid passive tense, when possible.
Raw output
{"message": "[Ibexa.Passive] Try to avoid passive tense, when possible.", "location": {"path": "docs/users/user_authentication.md", "range": {"start": {"line": 27, "column": 20}}}, "severity": "INFO"}

### User mapping example

The following example uses the [memory user provider]([[= symfony_doc =]]/security/user_providers.html#memory-user-provider),

Check notice on line 31 in docs/users/user_authentication.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/users/user_authentication.md#L31

[Ibexa.SentenceLength] Keep your sentences to less than 30 words.
Raw output
{"message": "[Ibexa.SentenceLength] Keep your sentences to less than 30 words.", "location": {"path": "docs/users/user_authentication.md", "range": {"start": {"line": 31, "column": 1}}}, "severity": "INFO"}
maps memory user to Ibexa repository user,

Check failure on line 32 in docs/users/user_authentication.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/users/user_authentication.md#L32

[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/users/user_authentication.md", "range": {"start": {"line": 32, "column": 21}}}, "severity": "ERROR"}
and [chains]([[= symfony_doc =]]/security/user_providers.html#chain-user-provider) with the Ibexa user provider to be able to use both:

Check failure on line 33 in docs/users/user_authentication.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/users/user_authentication.md#L33

[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/users/user_authentication.md", "range": {"start": {"line": 33, "column": 93}}}, "severity": "ERROR"}

Create as `src/EventSubscriber/InteractiveLoginSubscriber.php` subscribing to the `SecurityEvents::INTERACTIVE_LOGIN` event
Create as `src/EventSubscriber/AuthenticationTokenCreatedSubscriber.php` subscribing to the `AuthenticationTokenCreatedEvent` event
and mapping when needed an in-memory authenticated user to an Ibexa user:

Check failure on line 36 in docs/users/user_authentication.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/users/user_authentication.md#L36

[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/users/user_authentication.md", "range": {"start": {"line": 36, "column": 63}}}, "severity": "ERROR"}

``` php
[[= include_file('code_samples/user_management/in_memory/src/EventSubscriber/InteractiveLoginSubscriber.php') =]]
[[= include_file('code_samples/user_management/in_memory/src/EventSubscriber/AuthenticationTokenCreatedSubscriber.php') =]]
```

In `config/packages/security.yaml`,
Expand All @@ -49,16 +49,26 @@
[[= include_file('code_samples/user_management/in_memory/config/packages/security.yaml') =]]
```

In the `config/services.yaml` file, declare the subscriber as a service to pass your user map

Check notice on line 52 in docs/users/user_authentication.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/users/user_authentication.md#L52

[Ibexa.SentenceLength] Keep your sentences to less than 30 words.
Raw output
{"message": "[Ibexa.SentenceLength] Keep your sentences to less than 30 words.", "location": {"path": "docs/users/user_authentication.md", "range": {"start": {"line": 52, "column": 1}}}, "severity": "INFO"}
(it's automatically tagged `kernel.event_subscriber` as implementing the `EventSubscriberInterface`, the config resolver and user service injections are auto-wired):

``` yaml
[[= include_file('code_samples/user_management/in_memory/config/services.yaml') =]]
```

You can list the subscribers with the following command to check their order:

``` bash
php bin/console debug:event-dispatcher AuthenticationTokenCreatedEvent
```

Notice that the example subscriber priority is `11` so it's executed before
the `Ibexa\Core\MVC\Symfony\Security\Authentication\EventSubscriber\OnAuthenticationTokenCreatedRepositoryUserSubscriber`
which set the Ibexa user as the current user.

Check failure on line 67 in docs/users/user_authentication.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/users/user_authentication.md#L67

[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/users/user_authentication.md", "range": {"start": {"line": 67, "column": 15}}}, "severity": "ERROR"}

From the back office, create the mapped users.
For the example, a new user with the login `generic_customer` and a random password for the mapping to work,
this account can be in the **Customers** or the **Anonymous users** group.

You can now log in with a in-memory user.
You can now log in with an in-memory user.
In the Symfony debug toolbar, you should see the in-memory user as this example uses `UserWrapped`.
Loading