Passed
Pull Request — master (#5753)
by Angel Fernando Quiroz
07:27
created

GenericAuthenticator::userLoader()   B

Complexity

Conditions 8
Paths 6

Size

Total Lines 71
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 8
eloc 41
c 2
b 0
f 0
nc 6
nop 1
dl 0
loc 71
rs 8.0195

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
declare(strict_types=1);
6
7
namespace Chamilo\CoreBundle\Security\Authenticator\OAuth2;
8
9
use Chamilo\CoreBundle\Entity\AccessUrl;
10
use Chamilo\CoreBundle\Entity\AccessUrlRelUser;
11
use Chamilo\CoreBundle\Entity\User;
12
use Chamilo\CoreBundle\Repository\ExtraFieldRepository;
13
use Chamilo\CoreBundle\Repository\ExtraFieldValuesRepository;
14
use Chamilo\CoreBundle\Repository\Node\AccessUrlRepository;
15
use Chamilo\CoreBundle\Repository\Node\UserRepository;
16
use Chamilo\CoreBundle\ServiceHelper\AccessUrlHelper;
17
use Chamilo\CoreBundle\ServiceHelper\AuthenticationConfigHelper;
18
use Doctrine\ORM\EntityManagerInterface;
19
use ExtraField;
20
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
21
use League\OAuth2\Client\Provider\GenericResourceOwner;
22
use League\OAuth2\Client\Token\AccessToken;
23
use League\OAuth2\Client\Tool\ArrayAccessorTrait;
24
use Symfony\Component\HttpFoundation\Request;
25
use Symfony\Component\Routing\RouterInterface;
26
use Symfony\Component\Security\Core\Exception\AuthenticationException;
27
use UnexpectedValueException;
28
29
class GenericAuthenticator extends AbstractAuthenticator
30
{
31
    use ArrayAccessorTrait;
32
33
    public const EXTRA_FIELD_OAUTH2_ID = 'oauth2_id';
34
35
    protected string $providerName = 'generic';
36
37
    public function __construct(
38
        ClientRegistry $clientRegistry,
39
        RouterInterface $router,
40
        UserRepository $userRepository,
41
        AuthenticationConfigHelper $authenticationConfigHelper,
42
        AccessUrlHelper $urlHelper,
43
        protected readonly ExtraFieldRepository $extraFieldRepository,
44
        protected readonly ExtraFieldValuesRepository $extraFieldValuesRepository,
45
        protected readonly AccessUrlRepository $accessUrlRepository,
46
        protected readonly EntityManagerInterface $entityManager,
47
    ) {
48
        parent::__construct(
49
            $clientRegistry,
50
            $router,
51
            $userRepository,
52
            $authenticationConfigHelper,
53
            $urlHelper,
54
        );
55
    }
56
57
    public function supports(Request $request): ?bool
58
    {
59
        return 'chamilo.oauth2_generic_check' === $request->attributes->get('_route');
60
    }
61
62
    protected function userLoader(AccessToken $accessToken): User
63
    {
64
        $providerParams = $this->authenticationConfigHelper->getParams('generic');
65
66
        /** @var GenericResourceOwner $resourceOwner */
67
        $resourceOwner = $this->client->fetchUserFromToken($accessToken);
68
        $resourceOwnerData = $resourceOwner->toArray();
69
        $resourceOwnerId = $resourceOwner->getId();
70
71
        if (empty($resourceOwnerId)) {
72
            throw new UnexpectedValueException('Value for the resource owner identifier not found at the configured key');
73
        }
74
75
        $fieldType = (int) ExtraField::getExtraFieldTypeFromString('user');
76
        $extraField = $this->extraFieldRepository->findByVariable($fieldType, self::EXTRA_FIELD_OAUTH2_ID);
77
78
        $existingUserExtraFieldValue = $this->extraFieldValuesRepository->findByVariableAndValue(
79
            $extraField,
80
            $resourceOwnerId
81
        );
82
83
        if (null === $existingUserExtraFieldValue) {
84
            $username = $this->getValueByKey(
85
                $resourceOwnerData,
86
                $providerParams['resource_owner_username_field'],
87
                "oauth2user_$resourceOwnerId"
88
            );
89
90
            /** @var User $user */
91
            $user = $this->userRepository->findOneBy(['username' => $username]);
92
93
            if (!$user || 'platform' !== $user->getAuthSource()) {
0 ignored issues
show
introduced by
$user is of type Chamilo\CoreBundle\Entity\User, thus it always evaluated to true.
Loading history...
94
                if (!$providerParams['allow_create_new_users']) {
95
                    throw new AuthenticationException('This user doesn\'t have an account yet and auto-provisioning is not enabled. Please contact this portal administration team to request access.');
96
                }
97
98
                // set default values, real values are set in self::updateUserInfo method
99
                $user = (new User())
100
                    ->setFirstname('OAuth2 User default firstname')
101
                    ->setLastname('OAuth2 User default firstname')
102
                    ->setEmail('oauth2user_'.$resourceOwnerId.'@'.(gethostname() ?: 'localhost'))
103
                    ->setUsername($username)
104
                    ->setPlainPassword($username)
105
                    ->setStatus(STUDENT)
106
                    ->setCreatorId($this->userRepository->getRootUser()->getId())
107
                ;
108
            }
109
110
            $this->saveUserInfo($user, $resourceOwnerData, $providerParams);
111
112
            $this->extraFieldValuesRepository->updateItemData(
113
                $extraField,
114
                $user,
115
                $resourceOwnerId
116
            );
117
118
            $this->updateUrls($user, $resourceOwnerData, $providerParams);
119
        } else {
120
            /** @var User $user */
121
            $user = $this->userRepository->find(
122
                $existingUserExtraFieldValue->getItemId()
123
            );
124
125
            if ($providerParams['allow_update_user_info']) {
126
                $this->saveUserInfo($user, $resourceOwnerData, $providerParams);
127
128
                $this->updateUrls($user, $resourceOwnerData, $providerParams);
129
            }
130
        }
131
132
        return $user;
133
    }
134
135
    /**
136
     * Set user information from the resource owner's data or the user itself.
137
     */
138
    public function saveUserInfo(User $user, array $resourceOwnerData, array $providerParams): void
139
    {
140
        $status = $this->getUserStatus($resourceOwnerData, $user->getStatus(), $providerParams);
141
142
        $user
143
            ->setFirstname(
144
                $this->getValueByKey(
145
                    $resourceOwnerData,
146
                    $providerParams['resource_owner_firstname_field'],
147
                    $user->getFirstname()
148
                )
149
            )
150
            ->setLastname(
151
                $this->getValueByKey(
152
                    $resourceOwnerData,
153
                    $providerParams['resource_owner_lastname_field'],
154
                    $user->getLastname()
155
                )
156
            )
157
            ->setUsername(
158
                $this->getValueByKey(
159
                    $resourceOwnerData,
160
                    $providerParams['resource_owner_username_field'],
161
                    $user->getUsername()
162
                )
163
            )
164
            ->setEmail(
165
                $this->getValueByKey(
166
                    $resourceOwnerData,
167
                    $providerParams['resource_owner_email_field'],
168
                    $user->getEmail()
169
                )
170
            )
171
            ->setAuthSource('oauth2')
172
            ->setStatus($status)
173
            ->setRoleFromStatus($status)
174
        ;
175
176
        $this->userRepository->updateUser($user);
177
178
        $url = $this->urlHelper->getCurrent();
179
        $url->addUser($user);
180
    }
181
182
    private function getUserStatus(array $resourceOwnerData, int $defaultStatus, array $providerParams): int
183
    {
184
        $status = $this->getValueByKey(
185
            $resourceOwnerData,
186
            $providerParams['resource_owner_status_field'],
187
            $defaultStatus
188
        );
189
190
        $responseStatus = [];
191
192
        if ($teacherStatus = $providerParams['resource_owner_teacher_status_field']) {
193
            $responseStatus[COURSEMANAGER] = $teacherStatus;
194
        }
195
196
        if ($sessAdminStatus = $providerParams['resource_owner_sessadmin_status_field']) {
197
            $responseStatus[SESSIONADMIN] = $sessAdminStatus;
198
        }
199
200
        if ($drhStatus = $providerParams['resource_owner_hr_status_field']) {
201
            $responseStatus[DRH] = $drhStatus;
202
        }
203
204
        if ($studentStatus = $providerParams['resource_owner_status_status_field']) {
205
            $responseStatus[STUDENT] = $studentStatus;
206
        }
207
208
        if ($anonStatus = $providerParams['resource_owner_anon_status_field']) {
209
            $responseStatus[ANONYMOUS] = $anonStatus;
210
        }
211
212
        $map = array_flip($responseStatus);
213
214
        return $map[$status] ?? $status;
215
    }
216
217
    private function updateUrls(User $user, array $resourceOwnerData, array $providerParams): void
218
    {
219
        if (!($urlsField = $providerParams['resource_owner_urls_field'])) {
220
            return;
221
        }
222
223
        $availableUrls = [];
224
225
        $urls = $this->accessUrlRepository->findAll();
226
227
        /** @var AccessUrl $existingUrl */
228
        foreach ($urls as $existingUrl) {
229
            $availableUrls[(string) $existingUrl->getId()] = $existingUrl->getId();
230
            $availableUrls[$existingUrl->getUrl()] = $existingUrl->getId();
231
        }
232
233
        $allowedUrlIds = [];
234
235
        foreach ($this->getValueByKey($resourceOwnerData, $urlsField) as $value) {
236
            if (array_key_exists($value, $availableUrls)) {
237
                $allowedUrlIds[] = $availableUrls[$value];
238
            } else {
239
                $newValue = ($value[-1] === '/') ? substr($value, 0, -1) : $value.'/';
240
241
                if (array_key_exists($newValue, $availableUrls)) {
242
                    $allowedUrlIds[] = $availableUrls[$newValue];
243
                }
244
            }
245
        }
246
247
        $grantedUrlIds = [];
248
249
        foreach ($this->accessUrlRepository->findByUser($user) as $grantedUrl) {
250
            $grantedUrlIds[] = $grantedUrl->getId();
251
        }
252
253
        $urlRelUserRepo = $this->entityManager->getRepository(AccessUrlRelUser::class);
254
255
        foreach (array_diff($grantedUrlIds, $allowedUrlIds) as $extraUrlId) {
256
            $urlRelUser = $urlRelUserRepo->findOneBy(['user' => $user, 'url' => $extraUrlId]);
257
258
            if ($urlRelUser) {
259
                $this->entityManager->remove($urlRelUser);
260
            }
261
        }
262
263
        $this->entityManager->flush();
264
265
        foreach (array_diff($allowedUrlIds, $grantedUrlIds) as $missingUrlId) {
266
            /** @var AccessUrl $missingUrl */
267
            $missingUrl = $this->accessUrlRepository->find($missingUrlId);
268
            $missingUrl->addUser($user);
269
        }
270
271
        $this->entityManager->flush();
272
    }
273
}
274