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

LdapAuthenticator::__construct()   A

Complexity

Conditions 5
Paths 2

Size

Total Lines 37
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 5
eloc 20
c 3
b 0
f 0
nc 2
nop 8
dl 0
loc 37
rs 9.2888

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