Passed
Push — master ( aaf0f5...8d53d2 )
by Guilherme
01:45 queued 12s
created

FOSUBUserProvider   A

Complexity

Total Complexity 39

Size/Duplication

Total Lines 274
Duplicated Lines 0 %

Test Coverage

Coverage 90.91%

Importance

Changes 0
Metric Value
eloc 115
dl 0
loc 274
ccs 110
cts 121
cp 0.9091
rs 9.28
c 0
b 0
f 0
wmc 39

11 Methods

Rating   Name   Duplication   Size   Complexity  
A checkEmail() 0 15 4
A getUserInfo() 0 14 1
A loadUserByOAuthUserResponse() 0 14 2
A __construct() 0 16 1
A setUserInfo() 0 18 3
A connect() 0 19 4
A checkErrors() 0 11 6
A createOAuthUser() 0 36 2
C setFacebookData() 0 22 13
A setServiceData() 0 14 2
A setAccessToken() 0 5 1
1
<?php
2
/**
3
 * This file is part of the login-cidadao project or it's bundles.
4
 *
5
 * (c) Guilherme Donato <guilhermednt on github>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace LoginCidadao\CoreBundle\Security\User\Provider;
12
13
use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface;
14
use HWI\Bundle\OAuthBundle\Security\Core\User\FOSUBUserProvider as BaseClass;
15
use LoginCidadao\CoreBundle\Model\PersonInterface;
16
use LoginCidadao\CoreBundle\Security\Exception\DuplicateEmailException;
17
use LoginCidadao\CoreBundle\Security\User\Manager\UserManager;
18
use Ramsey\Uuid\Uuid;
19
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
20
use Symfony\Component\HttpFoundation\RedirectResponse;
21
use Symfony\Component\HttpFoundation\RequestStack;
22
use Symfony\Component\Security\Core\User\UserInterface;
23
use FOS\UserBundle\Model\UserManagerInterface;
24
use LoginCidadao\CoreBundle\Security\Exception\AlreadyLinkedAccount;
25
use Symfony\Component\HttpFoundation\Session\SessionInterface;
26
use LoginCidadao\CoreBundle\Security\Exception\MissingEmailException;
27
use FOS\UserBundle\Event\FormEvent;
28
use FOS\UserBundle\FOSUserEvents;
29
use FOS\UserBundle\Form\Factory\FactoryInterface;
30
use FOS\UserBundle\Event\FilterUserResponseEvent;
31
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
32
use LoginCidadao\ValidationBundle\Validator\Constraints\UsernameValidator;
33
use Symfony\Component\Validator\ConstraintViolationList;
34
use Symfony\Component\Validator\Validator\ValidatorInterface;
35
36
class FOSUBUserProvider extends BaseClass
37
{
38
39
    /** @var UserManagerInterface|UserManager */
40
    protected $userManager;
41
42
    /** @var SessionInterface */
43
    private $session;
44
45
    /** @var EventDispatcherInterface */
46
    private $dispatcher;
47
48
    /** @var FactoryInterface */
49
    private $formFactory;
50
51
    /** @var ValidatorInterface */
52
    private $validator;
53
54
    /** @var RequestStack */
55
    private $requestStack;
56
57
    /**
58
     * Constructor.
59
     *
60
     * @param UserManagerInterface $userManager FOSUB user provider.
61
     * @param SessionInterface $session
62
     * @param EventDispatcherInterface $dispatcher
63
     * @param FactoryInterface $formFactory
64
     * @param ValidatorInterface $validator
65
     * @param RequestStack $requestStack
66
     * @param array $properties Property mapping.
67
     * @internal param ContainerInterface $container
68
     */
69 4
    public function __construct(
70
        UserManagerInterface $userManager,
71
        SessionInterface $session,
72
        EventDispatcherInterface $dispatcher,
73
        FactoryInterface $formFactory,
74
        ValidatorInterface $validator,
75
        RequestStack $requestStack,
76
        array $properties
77
    ) {
78 4
        parent::__construct($userManager, $properties);
79 4
        $this->userManager = $userManager;
80 4
        $this->session = $session;
81 4
        $this->dispatcher = $dispatcher;
82 4
        $this->formFactory = $formFactory;
83 4
        $this->validator = $validator;
84 4
        $this->requestStack = $requestStack;
85 4
    }
86
87
    /**
88
     * {@inheritDoc}
89
     * @throws AlreadyLinkedAccount
90
     */
91 2
    public function connect(UserInterface $user, UserResponseInterface $response)
92
    {
93 2
        $username = $response->getUsername();
94
95 2
        $service = $response->getResourceOwner()->getName();
96
97
        /** @var PersonInterface|null $existingUser */
98 2
        $existingUser = $this->userManager->findUserBy(["{$service}Id" => $username]);
99 2
        if ($existingUser instanceof UserInterface && $existingUser->getId() != $user->getId()) {
0 ignored issues
show
Bug introduced by
The method getId() does not exist on Symfony\Component\Security\Core\User\UserInterface. It seems like you code against a sub-type of Symfony\Component\Security\Core\User\UserInterface such as HWI\Bundle\OAuthBundle\Tests\Fixtures\User or FOS\OAuthServerBundle\Te...\TestBundle\Entity\User or FOS\UserBundle\Model\UserInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

99
        if ($existingUser instanceof UserInterface && $existingUser->getId() != $user->/** @scrutinizer ignore-call */ getId()) {
Loading history...
100 1
            throw new AlreadyLinkedAccount();
101
        }
102
103 1
        $user = $this->setServiceData($user, $response);
104
105 1
        if ($service === 'facebook') {
106 1
            $this->setFacebookData($user, $response->getData());
107
        }
108
109 1
        $this->userManager->updateUser($user);
110 1
    }
111
112
    /**
113
     * {@inheritDoc}
114
     * @throws MissingEmailException
115
     * @throws DuplicateEmailException
116
     * @throws \Exception
117
     */
118 2
    public function loadUserByOAuthUserResponse(UserResponseInterface $response)
119
    {
120 2
        $userInfo = $this->getUserInfo($response);
121 2
        $service = $response->getResourceOwner()->getName();
122
123 2
        $user = $this->userManager->findUserBy(["{$service}Id" => $userInfo['id']]);
124
125 2
        if ($user instanceof PersonInterface) {
126 1
            $this->setAccessToken($user, $service, $response->getAccessToken());
127
128 1
            return $user;
129
        }
130
131 1
        return $this->createOAuthUser($userInfo, $service, $response->getData());
132
    }
133
134
    /**
135
     * @param array $userInfo
136
     * @param string $service
137
     * @param array $oauthData
138
     * @return PersonInterface
139
     * @throws DuplicateEmailException
140
     * @throws MissingEmailException
141
     */
142 1
    private function createOAuthUser(array $userInfo, string $service, array $oauthData)
143
    {
144 1
        $userInfo = $this->checkEmail($service, $userInfo);
145
146
        /** @var PersonInterface $user */
147 1
        $user = $this->userManager->createUser();
148 1
        $this->setUserInfo($user, $userInfo, $service);
149
150 1
        if ($service === 'facebook') {
151 1
            $this->setFacebookData($user, $oauthData);
152
        }
153
154 1
        $username = Uuid::uuid4()->toString();
155
156 1
        $user->setUsername($username);
157 1
        $user->setEmail($userInfo['email']);
158 1
        $user->setPassword('');
159 1
        $user->setEnabled(true);
160 1
        $this->userManager->updateCanonicalFields($user);
161
162 1
        $this->checkErrors($user, $service);
163
164 1
        $form = $this->formFactory->createForm();
165 1
        $form->setData($user);
166
167 1
        $request = $this->requestStack->getCurrentRequest();
168 1
        $eventResponse = new RedirectResponse('/');
169 1
        $event = new FormEvent($form, $request);
0 ignored issues
show
Bug introduced by
It seems like $request can also be of type null; however, parameter $request of FOS\UserBundle\Event\FormEvent::__construct() does only seem to accept Symfony\Component\HttpFoundation\Request, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

169
        $event = new FormEvent($form, /** @scrutinizer ignore-type */ $request);
Loading history...
170 1
        $this->dispatcher->dispatch(FOSUserEvents::REGISTRATION_SUCCESS, $event);
171
172 1
        $this->userManager->updateUser($user);
173
174 1
        $event = new FilterUserResponseEvent($user, $request, $eventResponse);
0 ignored issues
show
Bug introduced by
It seems like $request can also be of type null; however, parameter $request of FOS\UserBundle\Event\Fil...nseEvent::__construct() does only seem to accept Symfony\Component\HttpFoundation\Request, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

174
        $event = new FilterUserResponseEvent($user, /** @scrutinizer ignore-type */ $request, $eventResponse);
Loading history...
175 1
        $this->dispatcher->dispatch(FOSUserEvents::REGISTRATION_COMPLETED, $event);
176
177 1
        return $user;
178
    }
179
180 2
    private function getUserInfo(UserResponseInterface $response)
181
    {
182 2
        $fullName = explode(' ', $response->getRealName(), 2);
183
184
        $userInfo = [
185 2
            'id' => $response->getUsername(),
186 2
            'email' => $response->getEmail(),
187 2
            'username' => $response->getNickname(),
188 2
            'first_name' => $fullName[0],
189 2
            'family_name' => $fullName[1],
190 2
            'access_token' => $response->getAccessToken(),
191
        ];
192
193 2
        return $userInfo;
194
    }
195
196
    /**
197
     * @param PersonInterface $person
198
     * @param array $userInfo
199
     * @param string $service
200
     * @return PersonInterface
201
     */
202 1
    private function setUserInfo(PersonInterface $person, array $userInfo, string $service)
203
    {
204 1
        $setter = 'set'.ucfirst($service);
205 1
        $setter_id = $setter.'Id';
206 1
        $setter_username = $setter.'Username';
207
208 1
        $person->$setter_id($userInfo['id']);
209 1
        $this->setAccessToken($person, $service, $userInfo['access_token']);
210 1
        $person->$setter_username($userInfo['username']);
211
212 1
        if ($userInfo['first_name']) {
213 1
            $person->setFirstName($userInfo['first_name']);
214
        }
215 1
        if ($userInfo['family_name']) {
216 1
            $person->setSurname($userInfo['family_name']);
217
        }
218
219 1
        return $person;
220
    }
221
222
    /**
223
     * @param $service
224
     * @param $userInfo
225
     * @return mixed
226
     * @throws MissingEmailException
227
     */
228 1
    private function checkEmail($service, $userInfo)
229
    {
230 1
        $emailKey = "{$service}.email";
231 1
        $userInfoKey = "{$service}.userinfo";
232 1
        if (!$userInfo['email'] || $this->session->has($emailKey)) {
233
            if (!$this->session->get($emailKey)) {
234
                $this->session->set($userInfoKey, $userInfo);
235
                throw new MissingEmailException($service);
236
            }
237
            $userInfo['email'] = $this->session->get($emailKey);
238
            $this->session->remove($emailKey);
239
            $this->session->remove($userInfoKey);
240
        }
241
242 1
        return $userInfo;
243
    }
244
245 2
    private function setFacebookData($person, array $data)
246
    {
247 2
        if ($person instanceof PersonInterface) {
248 2
            if (isset($data['id'])) {
249 2
                $person->setFacebookId($data['id']);
250 2
                $person->addRole('ROLE_FACEBOOK');
251
            }
252 2
            if (isset($data['first_name']) && is_null($person->getFirstName())) {
253 2
                $person->setFirstName($data['first_name']);
254
            }
255 2
            if (isset($data['last_name']) && is_null($person->getSurname())) {
256 2
                $person->setSurname($data['last_name']);
257
            }
258 2
            if (isset($data['email']) && is_null($person->getEmail())) {
259 2
                $person->setEmail($data['email']);
260
            }
261 2
            if (isset($data['birthday']) && is_null($person->getBirthdate())) {
262 2
                $date = \DateTime::createFromFormat('m/d/Y', $data['birthday']);
263 2
                $person->setBirthdate($date);
264
            }
265 2
            if (isset($data['username']) && is_null($person->getFacebookUsername())) {
266 2
                $person->setFacebookUsername($data['username']);
267
            }
268
        }
269 2
    }
270
271 1
    private function setServiceData(UserInterface $user, UserResponseInterface $response): UserInterface
272
    {
273 1
        if ($user instanceof PersonInterface) {
274 1
            $service = $response->getResourceOwner()->getName();
275 1
            $setter = 'set'.ucfirst($service);
276 1
            $setter_id = $setter.'Id';
277 1
            $setter_username = $setter.'Username';
278
279 1
            $user->$setter_id($response->getUsername());
280 1
            $this->setAccessToken($user, $service, $response->getAccessToken());
281 1
            $user->$setter_username($response->getNickname());
282
        }
283
284 1
        return $user;
285
    }
286
287 3
    private function setAccessToken(UserInterface $user, string $serviceName, string $accessToken)
288
    {
289 3
        $setter = 'set'.ucfirst($serviceName).'AccessToken';
290
291 3
        return $user->$setter($accessToken);
292
    }
293
294
    /**
295
     * @param UserInterface $user
296
     * @param string $service
297
     * @throws DuplicateEmailException
298
     */
299 1
    private function checkErrors(UserInterface $user, string $service)
300
    {
301
        /** @var ConstraintViolationList $errors */
302 1
        $errors = $this->validator->validate($user, null, ['LoginCidadaoProfile']);
303 1
        if (count($errors) > 0) {
304
            foreach ($errors as $error) {
305
                if ($error->getPropertyPath() === 'email'
306
                    && method_exists($error, 'getConstraint')
307
                    && $error->getConstraint() instanceof UniqueEntity
308
                ) {
309
                    throw new DuplicateEmailException($service);
310
                }
311
            }
312
        }
313 1
    }
314
}
315