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

GenericAuthenticator::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 17
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 6
c 2
b 0
f 0
nc 1
nop 9
dl 0
loc 17
rs 10

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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