Passed
Push — master ( 47f766...f7067c )
by Angel Fernando Quiroz
08:26
created

LdapAuthenticator::createUser()   B

Complexity

Conditions 11
Paths 14

Size

Total Lines 54
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 11
eloc 35
c 2
b 0
f 0
nc 14
nop 1
dl 0
loc 54
rs 7.3166

How to fix   Long Method    Complexity   

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\Ldap;
8
9
use Chamilo\CoreBundle\Controller\SecurityController;
10
use Chamilo\CoreBundle\Entity\User;
11
use Chamilo\CoreBundle\Helpers\AccessUrlHelper;
12
use Chamilo\CoreBundle\Helpers\AuthenticationConfigHelper;
13
use Chamilo\CoreBundle\Repository\Node\UserRepository;
14
use Doctrine\ORM\EntityManagerInterface;
15
use stdClass;
16
use Symfony\Component\HttpFoundation\JsonResponse;
17
use Symfony\Component\HttpFoundation\Request;
18
use Symfony\Component\HttpFoundation\Response;
19
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
20
use Symfony\Component\Ldap\Ldap;
21
use Symfony\Component\Ldap\Security\LdapBadge;
22
use Symfony\Component\Ldap\Security\LdapUser;
23
use Symfony\Component\Ldap\Security\LdapUserProvider;
24
use Symfony\Component\PropertyAccess\Exception\AccessException;
25
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
26
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
27
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
28
use Symfony\Component\Security\Core\Exception\AuthenticationException;
29
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
30
use Symfony\Component\Security\Http\Authenticator\InteractiveAuthenticatorInterface;
31
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge;
32
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
33
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
34
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
35
use Symfony\Contracts\Translation\TranslatorInterface;
36
37
/**
38
 * Based on Symfony\Component\Ldap\Security\LdapAuthenticator.
39
 */
40
class LdapAuthenticator extends AbstractAuthenticator implements InteractiveAuthenticatorInterface
41
{
42
    private LdapBadge $ldapBadge;
43
    private LdapUserProvider $userProvider;
44
45
    private array $dataCorrespondence = [];
46
47
    private bool $isEnabled = false;
48
49
    public function __construct(
50
        protected readonly AuthenticationConfigHelper $authConfigHelper,
51
        private readonly AccessUrlHelper $accessUrlHelper,
52
        private readonly UserRepository $userRepo,
53
        private readonly EntityManagerInterface $entityManager,
54
        private readonly SecurityController $securityController,
55
        private readonly TokenStorageInterface $tokenStorage,
56
        private readonly TranslatorInterface $translator,
57
        Ldap $ldap,
58
    ) {
59
        $ldapConfig = $this->authConfigHelper->getLdapConfig();
60
61
        if (!$ldapConfig['enabled']) {
62
            return;
63
        }
64
65
        $this->isEnabled = true;
66
67
        $dnString = $ldapConfig['dn_string'] ?? '{user_identifier}';
68
        $searchDn = $ldapConfig['search_dn'] ?? '';
69
        $searchPassword = $ldapConfig['search_password'] ?? '';
70
        $queryString = $ldapConfig['query_string'] ?? null;
71
72
        $this->dataCorrespondence = array_filter($ldapConfig['data_correspondence']) ?: [];
73
74
        $this->ldapBadge = new LdapBadge(Ldap::class, $dnString, $searchDn, $searchPassword, $queryString);
75
76
        $this->userProvider = new LdapUserProvider(
77
            $ldap,
78
            $ldapConfig['base_dn'],
79
            $searchDn ?: '',
80
            $searchPassword ?: null,
81
            ['ROLE_STUDENT'],
82
            $ldapConfig['uid_key'] ?? null,
83
            $ldapConfig['filter'] ?? null,
84
            $ldapConfig['password_attribute'] ?? null,
85
            array_values($this->dataCorrespondence + [$ldapConfig['password_attribute']]),
86
        );
87
    }
88
89
    public function supports(Request $request): ?bool
90
    {
91
        if (
92
            !str_contains($request->getRequestFormat() ?? '', 'json')
93
            && !str_contains($request->getContentTypeFormat() ?? '', 'json')
94
        ) {
95
            return false;
96
        }
97
98
        return 'login_ldap_check' === $request->attributes->get('_route')
99
            && $request->headers->has('Authorization')
100
            && $request->isMethod('POST');
101
    }
102
103
    public function authenticate(Request $request): Passport
104
    {
105
        try {
106
            if (!$this->isEnabled) {
107
                throw new BadRequestHttpException('Authentication method not enabled.');
108
            }
109
110
            $data = json_decode($request->getContent());
111
112
            if (!$data instanceof stdClass) {
113
                throw new BadRequestHttpException('Invalid JSON.');
114
            }
115
116
            $credentials = $this->getCredentials($data);
117
        } catch (BadRequestHttpException $e) {
118
            $request->setRequestFormat('json');
119
120
            throw $e;
121
        }
122
123
        $userBadge = new UserBadge($credentials['username'], $this->userProvider->loadUserByIdentifier(...));
124
125
        $passport = new Passport(
126
            $userBadge,
127
            new PasswordCredentials($credentials['password']),
128
            [new RememberMeBadge((array) $data)]
129
        );
130
        // $passport->addBadge(new PasswordUpgradeBadge($credentials['password'], $this->userProvider));
131
        $passport->addBadge($this->ldapBadge);
132
133
        return $passport;
134
    }
135
136
    public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
137
    {
138
        return $this->securityController->loginJson(
139
            $request,
140
            $this->tokenStorage,
141
            $this->translator
142
        );
143
    }
144
145
    public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
146
    {
147
        $errorMessage = strtr($exception->getMessageKey(), $exception->getMessageData());
148
149
        return new JsonResponse(['error' => $errorMessage], Response::HTTP_UNAUTHORIZED);
150
    }
151
152
    public function createToken(Passport $passport, string $firewallName): TokenInterface
153
    {
154
        /** @var LdapUser $ldapUser */
155
        $ldapUser = $passport->getUser();
156
157
        $user = $this->createUser($ldapUser);
158
159
        return new UsernamePasswordToken(
160
            $user,
161
            $firewallName,
162
            $user->getRoles()
163
        );
164
    }
165
166
    private function getCredentials(stdClass $data): array
167
    {
168
        $credentials = [];
169
170
        try {
171
            $credentials['username'] = $data->username;
172
173
            if (!\is_string($credentials['username'])) {
174
                throw new BadRequestHttpException(\sprintf('The key "%s" must be a string.', 'username'));
175
            }
176
        } catch (AccessException $e) {
177
            throw new BadRequestHttpException(\sprintf('The key "%s" must be provided.', 'username'), $e);
178
        }
179
180
        try {
181
            $credentials['password'] = $data->password;
182
            $data->password = null;
183
184
            if (!\is_string($credentials['password'])) {
185
                throw new BadRequestHttpException(\sprintf('The key "%s" must be a string.', 'password'));
186
            }
187
        } catch (AccessException $e) {
188
            throw new BadRequestHttpException(\sprintf('The key "%s" must be provided.', 'password'), $e);
189
        }
190
191
        if ('' === $credentials['username'] || '' === $credentials['password']) {
192
            trigger_deprecation('symfony/security', '6.2', 'Passing an empty string as username or password parameter is deprecated.');
0 ignored issues
show
Bug introduced by
The function trigger_deprecation was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

192
            /** @scrutinizer ignore-call */ 
193
            trigger_deprecation('symfony/security', '6.2', 'Passing an empty string as username or password parameter is deprecated.');
Loading history...
193
        }
194
195
        return $credentials;
196
    }
197
198
    private function createUser(LdapUser $ldapUser): User
199
    {
200
        $currentAccessUrl = $this->accessUrlHelper->getCurrent();
201
202
        $user = $this->userRepo->findOneBy(['username' => $ldapUser->getUserIdentifier()]);
203
204
        if (!$user) {
205
            $user = (new User())
206
                ->setCreatorId($this->userRepo->getRootUser()->getId())
207
                ->addAuthSourceByAuthentication('extldap', $currentAccessUrl)
208
            ;
209
        }
210
211
        $ldapFields = $ldapUser->getExtraFields();
212
213
        $fieldsMap = [
214
            'firstname' => 'setFirstname',
215
            'lastname' => 'setLastname',
216
            'email' => 'setEmail',
217
            'active' => 'setActive',
218
            'role' => 'setRoles',
219
            'locale' => 'setLocale',
220
            'phone' => 'setPhone',
221
        ];
222
223
        foreach ($fieldsMap as $key => $setter) {
224
            if (isset($this->dataCorrespondence[$key]) && $fieldKey = $this->dataCorrespondence[$key]) {
225
                $value = $ldapFields[$fieldKey][0] ?? '';
226
                if ($key === 'active') {
227
                    $user->$setter((int) $value);
228
                } elseif ($key === 'role') {
229
                    $user->$setter([$value]);
230
                } else {
231
                    $user->$setter($value);
232
                }
233
            } elseif ($key === 'firstname' || $key === 'lastname' || $key === 'email') {
234
                $user->$setter('');
235
            } elseif ($key === 'role') {
236
                $user->setRoles($ldapUser->getRoles());
237
            }
238
        }
239
240
        $user
241
            ->setUsername($ldapUser->getUserIdentifier())
242
            ->setPlainPassword($ldapUser->getPassword())
243
        ;
244
245
        $this->userRepo->updateUser($user);
246
247
        $currentAccessUrl->addUser($user);
248
249
        $this->entityManager->flush();
250
251
        return $user;
252
    }
253
254
    public function isInteractive(): bool
255
    {
256
        return true;
257
    }
258
}
259