Passed
Push — master ( c6ea32...68912b )
by
unknown
11:54
created

LdapAuthenticator::createUser()   B

Complexity

Conditions 11
Paths 14

Size

Total Lines 54
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

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

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