Passed
Push — master ( 5e96c0...23583c )
by Angel Fernando Quiroz
08:21
created

GenericAuthenticator::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 18
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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