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

LdapAuthenticator::__construct()   B

Complexity

Conditions 6
Paths 3

Size

Total Lines 43
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 24
c 1
b 0
f 0
nc 3
nop 8
dl 0
loc 43
rs 8.9137

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\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