Description
Symfony provides native support for multiple user providers. This makes it easy to integrate any kind of login handlers, including SSO and existing 3rd party bundles (e.g. FR3DLdapBundle, HWIOauthBundle, FOSUserBundle, BeSimpleSsoAuthBundle, etc.).
However, to be able to use external user providers with eZ, a valid eZ user needs to be injected into the repository. This is mainly for the kernel to be able to manage content-related permissions (but not limited to this).
Depending on your context, you will either want to create an eZ user on-the-fly
, return an existing user, or even always use a generic user.
Solution
Whenever an external user is matched (i.e. that does not come from eZ repository, like coming from LDAP), eZ kernel fires an MVCEvents::INTERACTIVE_LOGIN
event. Every service listening to this event will receive an eZ\Publish\Core\MVC\Symfony\Event\InteractiveLoginEvent
object which contains the original security token (that holds the matched user) and the request.
It's then up to the listener to retrieve an eZ user from the repository and to assign it back to the event object. This user will be injected into the repository and used for the rest of the request.
If no eZ user is returned, the anonymous user will be used.
User exposed and security token
When an external user is matched, a different token will be injected into the security context, the InteractiveLoginToken
. This token holds a UserWrapped
instance which contains the originally matched user and the API user (the one from the eZ repository).
Note that the API user is mainly used for permission checks against the repository and thus stays under the hood.
Customizing the user class
It is possible to customize the user class used by extending ezpublish.security.login_listener
service, which defaults to eZ\Publish\Core\MVC\Symfony\Security\EventListener\SecurityListener
.
You can override getUser()
to return whatever user class you want, as long as it implements eZ\Publish\Core\MVC\Symfony\Security\UserInterface
.
Example
Here is a very simple example using the in-memory user provider.
Implementing the listener
Do not mix MVCEvents::INTERACTIVE_LOGIN
event (specific to eZ Platform) and SecurityEvents::INTERACTIVE_LOGIN
event (fired by Symfony security component)
+ # The "in memory" provider requires an encoder for Symfony\Component\Security\Core\User\User |
+ encoders: |
+ Symfony\Component\Security\Core\User\User:
4 Comments
Gaetano Giunta
A couple of questions:
Jérôme Vieilledent
You mean a full implementation of custom login handler?
André Sousa
It would be great to have a more detailed example of implementation with an external ldap user provider:
I managed so far to install/config BorisMorel LdapBundle: https://github.com/BorisMorel/LdapBundle
I can even connect to the ldap server and authenticate an external user until ez pulish refreshes the page and reloads user from user provider. After reloading user from user provider, symfony debug shows Logged in as external user but Authenticated = No, and redirects to login again.
Do I have to customize the User class provided by the ldap bundle (https://github.com/BorisMorel/LdapBundle/blob/master/User/LdapUser.php) ?
Do I have to generate a new LoginToken ?
Maior Valentin
After implementing a pure symfony user login system with custom UserProvider and AuthenticationProvider using a 3rd party API, failure and succes handlers, I started integrating the bundle in eZ5.
Managed to cable it in and get the user logged in, BUT I realized that I ended up with UserWrapped type and not my custom User Type. This causes a lot of issues when using FormTypes in my Controllers.I am using getUser() in the Controllers which return an UserWrapped which actually contains my customUser in a private wrappedUser to which unfortunately we do not have access.
Documentation suggests to override getUser() to get your custom user class but as long as you implement eZUserInterface, which I can not do as I want to develop a stand alone bundle, so I am implementing directly Symfony's UserInterface.
You can override
getUser()
to return whatever user class you want, as long as it implementseZ\Publish\Core\MVC\Symfony\Security\UserInterface
.I am aware that this means not having a repo user at the end so it might cause issues when working with content due to roles, but that is not the case (at least for the moment).
So what I was thinking is to implement a Listener that will listen to SecurityEvents::INTERACTIVE_LOGIN with a higher priority, and stop propagation afterwards so it does not end up caught in ez SecurityListener, thus not ending up with UserWrapped and InteractiveLoginToken.
What is your opinion on this approach?
Thank you in advance.