Passed
Push — master ( 37676a...b0c137 )
by Angel Fernando Quiroz
13:06 queued 02:44
created

LdapAuthenticator::__construct()   A

Complexity

Conditions 5
Paths 2

Size

Total Lines 39
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 21
c 1
b 0
f 0
nc 2
nop 8
dl 0
loc 39
rs 9.2728

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\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
        $params = $this->authConfigHelper->getLdapConfig(
60
            $this->accessUrlHelper->getCurrent()
61
        );
62
63
        if (!$params['enabled']) {
64
            return;
65
        }
66
67
        $this->isEnabled = true;
68
69
        $dnString = $params['dn_string'] ?? '{user_identifier}';
70
        $searchDn = $params['search_dn'] ?? '';
71
        $searchPassword = $params['search_password'] ?? '';
72
        $queryString = $params['query_string'] ?? null;
73
74
        $this->dataCorrespondence = array_filter($params['data_correspondence']) ?: [];
75
76
        $this->ldapBadge = new LdapBadge(Ldap::class, $dnString, $searchDn, $searchPassword, $queryString);
77
78
        $this->userProvider = new LdapUserProvider(
79
            $ldap,
80
            $params['base_dn'],
81
            $searchDn ?: '',
82
            $searchPassword ?: null,
83
            ['ROLE_STUDENT'],
84
            $params['uid_key'] ?? null,
85
            $params['filter'] ?? null,
86
            $params['password_attribute'] ?? null,
87
            array_values($this->dataCorrespondence + [$params['password_attribute']]),
88
        );
89
    }
90
91
    public function supports(Request $request): ?bool
92
    {
93
        if (
94
            !str_contains($request->getRequestFormat() ?? '', 'json')
95
            && !str_contains($request->getContentTypeFormat() ?? '', 'json')
96
        ) {
97
            return false;
98
        }
99
100
        return 'login_ldap_check' === $request->attributes->get('_route')
101
            && $request->headers->has('Authorization')
102
            && $request->isMethod('POST');
103
    }
104
105
    public function authenticate(Request $request): Passport
106
    {
107
        try {
108
            if (!$this->isEnabled) {
109
                throw new BadRequestHttpException('Authentication method not enabled.');
110
            }
111
112
            $data = json_decode($request->getContent());
113
114
            if (!$data instanceof stdClass) {
115
                throw new BadRequestHttpException('Invalid JSON.');
116
            }
117
118
            $credentials = $this->getCredentials($data);
119
        } catch (BadRequestHttpException $e) {
120
            $request->setRequestFormat('json');
121
122
            throw $e;
123
        }
124
125
        $userBadge = new UserBadge($credentials['username'], $this->userProvider->loadUserByIdentifier(...));
126
127
        $passport = new Passport(
128
            $userBadge,
129
            new PasswordCredentials($credentials['password']),
130
            [new RememberMeBadge((array) $data)]
131
        );
132
        // $passport->addBadge(new PasswordUpgradeBadge($credentials['password'], $this->userProvider));
133
        $passport->addBadge($this->ldapBadge);
134
135
        return $passport;
136
    }
137
138
    public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
139
    {
140
        return $this->securityController->loginJson(
141
            $request,
142
            $this->tokenStorage,
143
            $this->translator
144
        );
145
    }
146
147
    public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
148
    {
149
        $errorMessage = strtr($exception->getMessageKey(), $exception->getMessageData());
150
151
        return new JsonResponse(['error' => $errorMessage], Response::HTTP_UNAUTHORIZED);
152
    }
153
154
    public function createToken(Passport $passport, string $firewallName): TokenInterface
155
    {
156
        /** @var LdapUser $ldapUser */
157
        $ldapUser = $passport->getUser();
158
159
        $user = $this->createUser($ldapUser);
160
161
        return new UsernamePasswordToken(
162
            $user,
163
            $firewallName,
164
            $user->getRoles()
165
        );
166
    }
167
168
    private function getCredentials(stdClass $data): array
169
    {
170
        $credentials = [];
171
172
        try {
173
            $credentials['username'] = $data->username;
174
175
            if (!\is_string($credentials['username'])) {
176
                throw new BadRequestHttpException(\sprintf('The key "%s" must be a string.', 'username'));
177
            }
178
        } catch (AccessException $e) {
179
            throw new BadRequestHttpException(\sprintf('The key "%s" must be provided.', 'username'), $e);
180
        }
181
182
        try {
183
            $credentials['password'] = $data->password;
184
            $data->password = null;
185
186
            if (!\is_string($credentials['password'])) {
187
                throw new BadRequestHttpException(\sprintf('The key "%s" must be a string.', 'password'));
188
            }
189
        } catch (AccessException $e) {
190
            throw new BadRequestHttpException(\sprintf('The key "%s" must be provided.', 'password'), $e);
191
        }
192
193
        if ('' === $credentials['username'] || '' === $credentials['password']) {
194
            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

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