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

GenericAuthenticator::supports()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 3
rs 10
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\User;
10
use Chamilo\CoreBundle\Repository\ExtraFieldRepository;
11
use Chamilo\CoreBundle\Repository\ExtraFieldValuesRepository;
12
use Chamilo\CoreBundle\Repository\Node\UserRepository;
13
use Chamilo\CoreBundle\ServiceHelper\AuthenticationConfigHelper;
14
use ExtraField;
15
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
16
use League\OAuth2\Client\Provider\GenericResourceOwner;
17
use League\OAuth2\Client\Token\AccessToken;
18
use League\OAuth2\Client\Tool\ArrayAccessorTrait;
19
use Symfony\Component\HttpFoundation\Request;
20
use Symfony\Component\Routing\RouterInterface;
21
use Symfony\Component\Security\Core\Exception\AuthenticationException;
22
use UnexpectedValueException;
23
24
class GenericAuthenticator extends AbstractAuthenticator
25
{
26
    use ArrayAccessorTrait;
27
28
    public const EXTRA_FIELD_OAUTH2_ID = 'oauth2_id';
29
30
    protected string $providerName = 'generic';
31
32
    public function __construct(
33
        ClientRegistry $clientRegistry,
34
        RouterInterface $router,
35
        UserRepository $userRepository,
36
        AuthenticationConfigHelper $authenticationConfigHelper,
37
        protected readonly ExtraFieldRepository $extraFieldRepository,
38
        protected readonly ExtraFieldValuesRepository $extraFieldValuesRepository,
39
    ) {
40
        parent::__construct(
41
            $clientRegistry,
42
            $router,
43
            $userRepository,
44
            $authenticationConfigHelper
45
        );
46
    }
47
48
    public function supports(Request $request): ?bool
49
    {
50
        return 'chamilo.oauth2_generic_check' === $request->attributes->get('_route');
51
    }
52
53
    protected function userLoader(AccessToken $accessToken): User
54
    {
55
        $providerParams = $this->authenticationConfigHelper->getParams('generic');
56
57
        /** @var GenericResourceOwner $resourceOwner */
58
        $resourceOwner = $this->client->fetchUserFromToken($accessToken);
59
        $resourceOwnerData = $resourceOwner->toArray();
60
        $resourceOwnerId = $resourceOwner->getId();
61
62
        if (empty($resourceOwnerId)) {
63
            throw new UnexpectedValueException('Value for the resource owner identifier not found at the configured key');
64
        }
65
66
        $fieldType = (int) ExtraField::getExtraFieldTypeFromString('user');
67
        $extraField = $this->extraFieldRepository->findByVariable($fieldType, self::EXTRA_FIELD_OAUTH2_ID);
68
69
        $existingUserExtraFieldValue = $this->extraFieldValuesRepository->findByVariableAndValue(
70
            $extraField,
71
            $resourceOwnerId
72
        );
73
74
        if (null === $existingUserExtraFieldValue) {
75
            $username = $this->getValueByKey(
76
                $resourceOwnerData,
77
                $providerParams['resource_owner_username_field'],
78
                "oauth2user_$resourceOwnerId"
79
            );
80
81
            /** @var User $user */
82
            $user = $this->userRepository->findOneBy(['username' => $username]);
83
84
            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...
85
                if (!$providerParams['allow_create_new_users']) {
86
                    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.');
87
                }
88
89
                // set default values, real values are set in self::updateUserInfo method
90
                $user = (new User())
91
                    ->setFirstname('OAuth2 User default firstname')
92
                    ->setLastname('OAuth2 User default firstname')
93
                    ->setEmail('oauth2user_'.$resourceOwnerId.'@'.(gethostname() ?: 'localhost'))
94
                    ->setUsername($username)
95
                    ->setPlainPassword($username)
96
                    ->setStatus(STUDENT)
97
                    ->setCreatorId($this->userRepository->getRootUser()->getId())
98
                ;
99
            }
100
101
            $this->saveUserInfo($user, $resourceOwnerData, $providerParams);
102
103
            $this->extraFieldValuesRepository->updateItemData(
104
                $extraField,
105
                $user,
106
                $resourceOwnerId
107
            );
108
        } else {
109
            /** @var User $user */
110
            $user = $this->userRepository->find(
111
                $existingUserExtraFieldValue->getItemId()
112
            );
113
114
            if ($providerParams['allow_update_user_info']) {
115
                $this->saveUserInfo($user, $resourceOwnerData, $providerParams);
116
            }
117
        }
118
119
        return $user;
120
    }
121
122
    /**
123
     * Set user information from the resource owner's data or the user itself.
124
     */
125
    public function saveUserInfo(User $user, array $resourceOwnerData, array $providerParams): void
126
    {
127
        $status = $this->getUserStatus($resourceOwnerData, $user->getStatus(), $providerParams);
128
129
        $user
130
            ->setFirstname(
131
                $this->getValueByKey(
132
                    $resourceOwnerData,
133
                    $providerParams['resource_owner_firstname_field'],
134
                    $user->getFirstname()
135
                )
136
            )
137
            ->setLastname(
138
                $this->getValueByKey(
139
                    $resourceOwnerData,
140
                    $providerParams['resource_owner_lastname_field'],
141
                    $user->getLastname()
142
                )
143
            )
144
            ->setUsername(
145
                $this->getValueByKey(
146
                    $resourceOwnerData,
147
                    $providerParams['resource_owner_username_field'],
148
                    $user->getUsername()
149
                )
150
            )
151
            ->setEmail(
152
                $this->getValueByKey(
153
                    $resourceOwnerData,
154
                    $providerParams['resource_owner_email_field'],
155
                    $user->getEmail()
156
                )
157
            )
158
            ->setAuthSource('oauth2')
159
            ->setStatus($status)
160
            ->setRoleFromStatus($status)
161
        ;
162
163
        $this->userRepository->updateUser($user);
164
        // updateAccessUrls ?
165
    }
166
167
    private function getUserStatus(array $resourceOwnerData, int $defaultStatus, array $providerParams): int
168
    {
169
        $status = $this->getValueByKey(
170
            $resourceOwnerData,
171
            $providerParams['resource_owner_status_field'],
172
            $defaultStatus
173
        );
174
175
        $responseStatus = [];
176
177
        if ($teacherStatus = $providerParams['resource_owner_teacher_status_field']) {
178
            $responseStatus[COURSEMANAGER] = $teacherStatus;
179
        }
180
181
        if ($sessAdminStatus = $providerParams['resource_owner_sessadmin_status_field']) {
182
            $responseStatus[SESSIONADMIN] = $sessAdminStatus;
183
        }
184
185
        if ($drhStatus = $providerParams['resource_owner_hr_status_field']) {
186
            $responseStatus[DRH] = $drhStatus;
187
        }
188
189
        if ($studentStatus = $providerParams['resource_owner_status_status_field']) {
190
            $responseStatus[STUDENT] = $studentStatus;
191
        }
192
193
        if ($anonStatus = $providerParams['resource_owner_anon_status_field']) {
194
            $responseStatus[ANONYMOUS] = $anonStatus;
195
        }
196
197
        $map = array_flip($responseStatus);
198
199
        return $map[$status] ?? $status;
200
    }
201
}
202