Search code examples
symfonyoauthsymfony-2.1hwioauthbundle

Symfony2: Make users complete registration after login if some fields are missing


I'm using HWIOAuthBundle to let an user login with Oauth, I've created a custom user provider which creates an user in case it doesn't exist:

public function loadUserByOAuthUserResponse(UserResponseInterface $response)
{
    $attr = $response->getResponse();
    switch($response->getResourceOwner()->getName()) {
        case 'google':
            if(!$user = $this->userRepository->findOneByGoogleId($attr['id'])) {
                if(($user = $this->userRepository->findOneByEmail($attr['email'])) && $attr['verified_email']) {
                    $user->setGoogleId($attr['id']);
                    if(!$user->getFirstname()) {
                        $user->setFirstname($attr['given_name']);
                    }
                    if(!$user->getLastname()) {
                        $user->setLastname($attr['family_name']);
                    }
                    $user->setGoogleName($attr['name']);
                }else{
                    $user = new User();
                    $user->setUsername($this->userRepository->createUsernameByEmail($attr['email']));
                    $user->setEmail($attr['email']);
                    $user->setFirstname($attr['given_name']);
                    $user->setLastname($attr['family_name']);
                    $user->setPassword('');
                    $user->setIsActive(true);
                    $user->setGoogleId($attr['id']);
                    $user->setGoogleName($attr['name']);
                    $user->addGroup($this->groupRepository->findOneByRole('ROLE_USER'));
                    $this->entityManager->persist($user);
                }
            }
            break;
        case 'facebook':
            if(!$user = $this->userRepository->findOneByFacebookId($attr['id'])) {
                if(($user = $this->userRepository->findOneByEmail($attr['email'])) && $attr['verified']) {
                    $user->setFacebookId($attr['id']);
                    if(!$user->getFirstname()) {
                        $user->setFirstname($attr['first_name']);
                    }
                    if(!$user->getLastname()) {
                        $user->setLastname($attr['last_name']);
                    }
                    $user->setFacebookUsername($attr['username']);
                }else{
                    $user = new User();
                    $user->setUsername($this->userRepository->createUsernameByEmail($attr['email']));
                    $user->setEmail($attr['email']);
                    $user->setFirstname($attr['first_name']);
                    $user->setLastname($attr['last_name']);
                    $user->setPassword('');
                    $user->setIsActive(true);
                    $user->setFacebookId($attr['id']);
                    $user->setFacebookUsername($attr['username']);
                    $user->addGroup($this->groupRepository->findOneByRole('ROLE_USER'));
                    $this->entityManager->persist($user);
                }
            }
            break;
    }

    $this->entityManager->flush();


    if (null === $user) {
        throw new AccountNotLinkedException(sprintf("User '%s' not found.", $attr['email']));
    }

    return $user;
}

The problem is that twitter for example doesn't give the email or i want some additional fields to be included before a new user is created. Is there a way to redirect an user to a "complete registration" form before creating it?

I've tried to add a request listener, that on each request, if the user is logged, checks if the email is there and if it doesn't it redirects to the complete_registration page, but it will redirect also if the user goes to the homepage, to logout or anything else, I want to redirect him only if he tries to access some user restricted pages.

Or better, don't create it until he gives all the required informations.


Solution

  • I've found the solution by myself, I've manually created a new exception:

    <?php
    
    namespace Acme\UserBundle\Exception;
    
    use Symfony\Component\Security\Core\Exception\AuthenticationException;
    use HWI\Bundle\OAuthBundle\Security\Core\Exception\OAuthAwareExceptionInterface;
    
    /**
     * IncompleteUserException is thrown when the user isn't fully registered (e.g.: missing some informations).
     *
     * @author Alessandro Tagliapietra http://www.alexnetwork.it/
     */
    class IncompleteUserException extends AuthenticationException implements OAuthAwareExceptionInterface
    {
        private $user;
        private $accessToken;
        private $resourceOwnerName;
    
        /**
         * {@inheritdoc}
         */
        public function setAccessToken($accessToken)
        {
            $this->accessToken = $accessToken;
        }
    
        /**
         * {@inheritdoc}
         */
        public function getAccessToken()
        {
            return $this->accessToken;
        }
    
        /**
         * {@inheritdoc}
         */
        public function getResourceOwnerName()
        {
            return $this->resourceOwnerName;
        }
    
        /**
         * {@inheritdoc}
         */
        public function setResourceOwnerName($resourceOwnerName)
        {
            $this->resourceOwnerName = $resourceOwnerName;
        }
    
        public function setUser($user)
        {
            $this->user = $user;
        }
    
        public function getUser($user)
        {
            return $this->user;
        }
    
        public function serialize()
        {
            return serialize(array(
                $this->user,
                $this->accessToken,
                $this->resourceOwnerName,
                parent::serialize(),
            ));
        }
    
        public function unserialize($str)
        {
            list(
                $this->user,
                $this->accessToken,
                $this->resourceOwnerName,
                $parentData
            ) = unserialize($str);
            parent::unserialize($parentData);
        }
    }
    

    In this way, in the custom Oauth user provider when i check if an user exist or I create a new user i check if the required fields are missing:

    if (!$user->getEmail()) {
        $e = new IncompleteUserException("Your account doesn't has a mail set");
        $e->setUser($user);
        throw $e;
    }
    

    In that case the user will be redirected to the login form, with that exception in session, so in the login page I do:

    if($error instanceof IncompleteUserException) {
        $session->set(SecurityContext::AUTHENTICATION_ERROR, $error);
        return $this->redirect($this->generateUrl('register_complete'));
    }
    

    And it will be redirected to a form with the $user in the exception so it can ask only for the missing information and then login the user.