Search code examples
phpzend-frameworkzend-framework2

ZF2 - Assuming other user's identity


Before I dive into reinventing the wheel, I'd first like to check if ZF2 supports, either out-of-the-box or with a 3rd party library, this particular use case where admins log in as other user, or assume their identity.

If not, as I'm not familiar with ZF2 internal design, how would I go into implementing this, with the only constraint being that the system is already built, so I can't change components (controllers, auth services, etc) into supporting it.

My first thought would be to make a mechanism to switch the logged user information stored in the session storage, with the one whose identity I want to assume. Then, write to the session, under a different namespace, the original user information (admin) so that it can be reverted.

Going by this approach, I am expecting components like Zend\Authentication\AuthenticationService return the user whose identity I'm assuming. So, in every call I make to $this->identity()->getId() (identity being a controller plugin for AuthenticationService, that returns the User) in other controllers, the business logic will work normally.

Having said this, the questions would be:

  1. Is there a solution already for this?
  2. Is my approach correct in assuming that by overwriting the session storage I can assume other user ID and expect ZF2 components to work accordingly, or is there any considerations regarding ZF2 internal design/infrastructure I haven't taken in consideration that I should?
  3. Maybe there's a better way to do this?

Solution

  • I think you would need to create your own AuthenticationAdaptor.

    class AdminUserLoginAsUser implements \Zend\Authentication\Adapter\AdapterInterface
    {
    
        /**
         * @var User
         */
        private $userToLoginAs;
    
        /**
         * @var AdminUser
         */
        private $adminUser;
    
        public function __construct(User $userToLoginAs, AdminUser $adminUser)
        {
    
            $this->userToLoginAs = $userToLoginAs;
            $this->adminUser = $adminUser;
        }
    
        /**
         * Performs an authentication attempt
         *
         * @return \Zend\Authentication\Result
         * @throws \Zend\Authentication\Adapter\Exception\ExceptionInterface If authentication cannot be performed
         */
        public function authenticate()
        {
            return new \Zend\Authentication\Result(
                Result::SUCCESS, $this->user, [
                    'You have assumed control of user.',
                ]
            );
        }
    }
    

    The above class will allow you to login as another user when used with Zend's AuthenticationService class.

    You will need some way of using Zend's AuthenticationService class and I would recommend using an AuthManager that wraps around the AuthenticationService.

    /**
     * The AuthManager service is responsible for user's login/logout and simple access
     * filtering. The access filtering feature checks whether the current visitor
     * is allowed to see the given page or not.
     */
    class AuthManager
    {
        /**
         * Authentication service.
         * @var \Zend\Authentication\AuthenticationService
         */
        private $authService;
    
        /**
         * Session manager.
         * @var Zend\Session\SessionManager
         */
        private $sessionManager;
    
        /**
         * Contents of the 'access_filter' config key.
         * @var array
         */
        private $config;
    
        /**
         * Constructs the service.
         */
        public function __construct($authService, $sessionManager, $config)
        {
            $this->authService = $authService;
            $this->sessionManager = $sessionManager;
            $this->config = $config;
        }
    
        /**
         * Performs a login attempt. If $rememberMe argument is true, it forces the session
         * to last for one month (otherwise the session expires on one hour).
         */
        public function login($email, $password, $rememberMe)
        {
            // Check if user has already logged in. If so, do not allow to log in
            // twice.
            if ($this->authService->getIdentity()!=null) {
                throw new \Exception('Already logged in');
            }
    
            // Authenticate with login/password.
            $authAdapter = $this->authService->getAdapter();
            $authAdapter->setEmail($email);
            $authAdapter->setPassword($password);
            $result = $this->authService->authenticate();
    
            // If user wants to "remember him", we will make session to expire in
            // one month. By default session expires in 1 hour (as specified in our
            // config/global.php file).
            if ($result->getCode()==Result::SUCCESS && $rememberMe) {
                // Session cookie will expire in 1 month (30 days).
                $this->sessionManager->rememberMe(60*60*24*30);
            }
    
            return $result;
        }
    
        public function loginAsUser($user)
        {
            // Check if user has already logged in. If so, do not allow to log in
            // twice.
            if ($this->authService->getIdentity() !== null) {
                throw new \Exception('Not logged in.');
            }
    
            // First need to logout of current user
            $this->authService->clearIdentity();
    
            $authAdapter = $this->authService->setAdapter(new AdminUserLoginAsUser($user, $this->authService->getIdentity()));
            return $this->authService->authenticate();
    
        }
    
        /**
         * Performs user logout.
         */
        public function logout()
        {
            // Allow to log out only when user is logged in.
            if ($this->authService->getIdentity()==null) {
                throw new \Exception('The user is not logged in');
            }
    
            // Remove identity from session.
            $this->authService->clearIdentity();
        }
    
    }
    

    To see how to plug it all together I would recommend looking at the following resources:

    The resources are for zf3 but I think the authenticating of users and managing authentication is very similar to zf2.