eZ Platform Discussions

Configure security/user provider for Sonata Admin


#1

Hi there,

I have a sonata admin (3.40) alongside an ez platform CMS (2.3).
Both have their own user provider.

Sonata uses fos_userbundle with my own Entity.

When I log into the sonata, the token has a UserWrapped instance with the original user and api user in it.

Then Sonata obviously throws errors:
Neither the property “id” nor one of the methods “id()”, “getid()”/“isid()”/“hasid()” or “__call()” exist and have public access in class “eZ\Publish\Core\MVC\Symfony\Security\UserWrapped”.

I authenticate my user against a completely separate firewall, why would ez publish overwrite my user?
And even if it overwrites it, why would my User cease to work? Why are the UserWrapped methods not “dispatched” to the “Original User”? How is this supposed to ever work?

Is there a configuration where I can say, “please bypass the ezplatform securitylistener” for this firewall, because there is nothing for you there?


#2

security.yml

security:
    providers:
        ezpublish:
            id: ezpublish.security.user_provider
        fos_userbundle:
            name: fos_userbundle
            id: fos_user.user_provider.username_email

    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false

        ezpublish_admin:
            provider: ezpublish
            host: ^cms\.acme\.local$
            anonymous: ~
            ezpublish_rest_session: ~
            form_login:
                require_previous_session: false
            logout: ~

        events_admin:
            host: ^events\.acme\.local$
            provider: fos_userbundle
            form_login:
                login_path: sonata_user_admin_security_login
                check_path: sonata_user_admin_security_check
                always_use_default_target_path: true
                default_target_path: sonata_admin_dashboard
                use_forward:    false
                failure_path:   null
            anonymous: ~
            logout:
                path: sonata_user_admin_security_logout

        ezpublish_rest:
            pattern: ^/api/ezp/v2
            stateless: true
            ezpublish_http_basic:
                realm: eZ Publish REST API

        main:
            anonymous: ~

    access_control:
        # ADMIN EVENTS
        - { path: ^/(login|logout|resetting), host: ^events\.acme\.local$, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { host: ^events\.acme\.local$, role: [ROLE_ADMIN, ROLE_SONATA_ADMIN] }

    acl:
        connection: events

#3
  1. TRY

I followed https://doc.ezplatform.com/en/2.3/cookbook/authenticating_a_user_with_multiple_user_providers/#customizing-the-user-class

But this is completely misleading.

I’m supposed to override the SecurityListener::getUser(UserInterface $originalUser, APIUser $apiUser) method from SecurityListener, which should return an \eZ\Publish\Core\MVC\Symfony\Security\UserInterface

But this above-mentioned getUser() method is actually NEVER called if my custom User class is already an instance \eZ\Publish\Core\MVC\Symfony\Security\UserInterface, because of the following method in the same SecurityListener class.

 public function onInteractiveLogin(BaseInteractiveLoginEvent $event)
    {
        $token = $event->getAuthenticationToken();
        $originalUser = $token->getUser();
        if ($originalUser instanceof eZUser || !$originalUser instanceof UserInterface) {
            return;
        }
   # [...]

#4
  1. TRY

If my custom User class were to implement eZ\Publish\Core\MVC\Symfony\Security\UserInterface, then comes another strange behaviour here: RepositoryAuthenticationProvider::checkAuthentication

$currentUser = $token->getUser();
        if ($currentUser instanceof UserInterface) {
            if ($currentUser->getAPIUser()->passwordHash !== $user->getAPIUser()->passwordHash) {
                throw new BadCredentialsException('The credentials were changed from another session.');
            }

            $apiUser = $currentUser->getAPIUser();
        } else {
            try {
                $apiUser = $this->repository->getUserService()->loadUserByCredentials($token->getUsername(), $token->getCredentials());
            } catch (NotFoundException $e) {
                throw new BadCredentialsException('Invalid credentials', 0, $e);
            }
        }

Obviously, the first condition will not be met, because I only have a string username during the login.
But then the provider is trying to load an EZ PLATFORM USER for my own already authenticated user?
And if not found (why would it be found? that’s a sonata user authenticated against a fos user provider), then throws a BadCredentials exception.

Can somebody enlighten me how is that supposed to be configured?


#5

Hello Andras. Thanks for posting your question, I’ve pinged someone from the Product team to have a look at your question(s).


#6

I came up with the two following ugly hacks to achieve this:

class UserWrapped extends \eZ\Publish\Core\MVC\Symfony\Security\UserWrapped
{
    public function __call($method, $arguments)
    {
        return call_user_func_array([$this->getWrappedUser(), $method], $arguments);
    }
    
    public function getId()
    {
        return $this->getWrappedUser()->getId();
    }
}

This user is then returned by the SecurityListener::getUser() method, like suggested.


#7

Ugly hack two.
Override SecurityListener::onInteractiveLogin and based on the $providerKey (i.e. the sonata firewall name) call parent or do nothing.

  
    public function onInteractiveLogin(BaseInteractiveLoginEvent $event)
    {
        $token       = $event->getAuthenticationToken();
        $providerKey = method_exists($token, 'getProviderKey') ? $token->getProviderKey() : __CLASS__;
        
        // this listener is only here for sonata
        if ($providerKey !== 'events_admin') {
            parent::onInteractiveLogin($event);
        }
    }


#8

If any of it is a suggested solution, then perhaps we could put together a Cookbook entry for such cases.
If not, then I am open for “ez” suggestions.
Thanks and sorry for the lengthy post.


#9

Hi Andras! I see you have spent some time digging into this. Does it work now, with your “ugly hacks”?

These parts of the kernel were written by Jerome V. but @andrerom has done things here too, perhaps you can advise, André?

why would ez publish overwrite my user?

As I read it, eZ Platform has to have an eZ user object in order to perform user access controls. That’s why the foreign user is wrapped together with an eZ user. The SecurityListener advises you to listen to the InteractiveLoginEvent and return a valid eZ user corresponding to your foreign user, creating it here if necessary. The last method in the doc example shows this. But that’s as far as my understanding goes.