Failed Conditions
Branch issue#666 (91903a)
by Guilherme
08:25
created

FOSUBUserProvider::setUserInfo()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 8
nc 1
nop 3
dl 0
loc 12
ccs 0
cts 9
cp 0
crap 2
rs 9.4285
c 0
b 0
f 0
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 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 14
    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 14
        parent::__construct($userManager, $properties);
79 14
        $this->userManager = $userManager;
0 ignored issues
show
Documentation Bug introduced by
$userManager is of type FOS\UserBundle\Model\UserManagerInterface, but the property $userManager was declared to be of type LoginCidadao\CoreBundle\...ser\Manager\UserManager. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
80 14
        $this->session = $session;
81 14
        $this->dispatcher = $dispatcher;
82 14
        $this->formFactory = $formFactory;
83 14
        $this->validator = $validator;
84 14
        $this->requestStack = $requestStack;
85 14
    }
86
87
    /**
88
     * {@inheritDoc}
89
     */
90
    public function connect(UserInterface $user, UserResponseInterface $response)
91
    {
92
        $username = $response->getUsername();
93
94
        $service = $response->getResourceOwner()->getName();
95
96
        $setter = 'set'.ucfirst($service);
97
        $setter_id = $setter.'Id';
98
        $setter_token = $setter.'AccessToken';
99
        $setter_username = $setter.'Username';
100
101
        $existingUser = $this->userManager->findUserBy(array("{$service}Id" => $username));
102
        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

102
        if ($existingUser instanceof UserInterface && $existingUser->/** @scrutinizer ignore-call */ getId() != $user->getId()) {
Loading history...
103
            throw new AlreadyLinkedAccount();
104
        }
105
106
        $screenName = $response->getNickname();
107
        $user->$setter_id($username);
108
        $user->$setter_token($response->getAccessToken());
109
        $user->$setter_username($screenName);
110
111
        if ($service === 'facebook') {
112
            $this->setFacebookData($user, $response->getResponse());
113
        }
114
115
        $this->userManager->updateUser($user);
116
    }
117
118
    /**
119
     * {@inheritDoc}
120
     */
121
    public function loadUserByOAuthUserResponse(UserResponseInterface $response)
122
    {
123
        $userInfo = $this->getUserInfo($response);
124
        $service = $response->getResourceOwner()->getName();
125
126
        $user = $this->userManager->findUserBy(array("{$service}Id" => $userInfo['id']));
127
128
        if ($user instanceof PersonInterface) {
129
            $user = parent::loadUserByOAuthUserResponse($response);
130
131
            $serviceName = $response->getResourceOwner()->getName();
132
            $setter = 'set'.ucfirst($serviceName).'AccessToken';
133
134
            $user->$setter($response->getAccessToken());
135
136
            return $user;
137
        }
138
139
        $userInfo = $this->checkEmail($service, $userInfo);
140
141
        /** @var PersonInterface $user */
142
        $user = $this->userManager->createUser();
143
        $this->setUserInfo($user, $userInfo, $service);
144
145
        if ($userInfo['first_name']) {
146
            $user->setFirstName($userInfo['first_name']);
147
        }
148
        if ($userInfo['family_name']) {
149
            $user->setSurname($userInfo['family_name']);
150
        }
151
152
        if ($service === 'facebook') {
153
            $this->setFacebookData($user, $response->getResponse());
154
        }
155
156
        $username = Uuid::uuid4()->toString();
157
        if (!UsernameValidator::isUsernameValid($username)) {
158
            $username = UsernameValidator::getValidUsername();
159
        }
160
161
        $availableUsername = $this->userManager->getNextAvailableUsername(
162
            $username,
163
            10,
164
            Uuid::uuid4()->toString()
165
        );
166
167
        $user->setUsername($availableUsername);
168
        $user->setEmail($userInfo['email']);
169
        $user->setPassword('');
170
        $user->setEnabled(true);
171
        $this->userManager->updateCanonicalFields($user);
172
173
        /** @var ConstraintViolationList $errors */
174
        $errors = $this->validator->validate($user, ['LoginCidadaoProfile']);
0 ignored issues
show
Bug introduced by
array('LoginCidadaoProfile') of type array<integer,string> is incompatible with the type Symfony\Component\Valida...nt\Validator\Constraint expected by parameter $constraints of Symfony\Component\Valida...orInterface::validate(). ( Ignorable by Annotation )

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

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