Completed
Push — master ( 4b4e40...af8835 )
by Craig
10:40 queued 04:02
created

getRegistrationTemplateName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Zikula package.
7
 *
8
 * Copyright Zikula Foundation - https://ziku.la/
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Zikula\ZAuthModule\AuthenticationMethod;
15
16
use Exception;
17
use Symfony\Component\HttpFoundation\RequestStack;
18
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
19
use Symfony\Component\Validator\Validator\ValidatorInterface;
20
use Symfony\Contracts\Translation\TranslatorInterface;
21
use Zikula\ExtensionsModule\Api\ApiInterface\VariableApiInterface;
22
use Zikula\UsersModule\AuthenticationMethodInterface\NonReEntrantAuthenticationMethodInterface;
23
use Zikula\ZAuthModule\Api\ApiInterface\PasswordApiInterface;
24
use Zikula\ZAuthModule\Entity\AuthenticationMappingEntity;
25
use Zikula\ZAuthModule\Entity\RepositoryInterface\AuthenticationMappingRepositoryInterface;
26
use Zikula\ZAuthModule\Form\Type\RegistrationType;
27
use Zikula\ZAuthModule\ZAuthConstant;
28
29
abstract class AbstractNativeAuthenticationMethod implements NonReEntrantAuthenticationMethodInterface
30
{
31
    /**
32
     * @var AuthenticationMappingRepositoryInterface
33
     */
34
    private $mappingRepository;
35
36
    /**
37
     * @var RequestStack
38
     */
39
    private $requestStack;
40
41
    /**
42
     * @var TranslatorInterface
43
     */
44
    protected $translator;
45
46
    /**
47
     * @var VariableApiInterface
48
     */
49
    private $variableApi;
50
51
    /**
52
     * @var ValidatorInterface
53
     */
54
    private $validator;
55
56
    /**
57
     * @var PasswordApiInterface
58
     */
59
    private $passwordApi;
60
61
    /**
62
     * @var EncoderFactoryInterface
63
     */
64
    private $encoderFactory;
65
66
    public function __construct(
67
        AuthenticationMappingRepositoryInterface $mappingRepository,
68
        RequestStack $requestStack,
69
        TranslatorInterface $translator,
70
        VariableApiInterface $variableApi,
71
        ValidatorInterface $validator,
72
        PasswordApiInterface $passwordApi,
73
        EncoderFactoryInterface $encoderFactory
74
    ) {
75
        $this->mappingRepository = $mappingRepository;
76
        $this->requestStack = $requestStack;
77
        $this->translator = $translator;
78
        $this->variableApi = $variableApi;
79
        $this->validator = $validator;
80
        $this->passwordApi = $passwordApi;
81
        $this->encoderFactory = $encoderFactory;
82
    }
83
84
    public function getRegistrationFormClassName(): string
85
    {
86
        return RegistrationType::class;
87
    }
88
89
    public function getRegistrationTemplateName(): string
90
    {
91
        return '@ZikulaZAuthModule/Authentication/register.html.twig';
92
    }
93
94
    protected function authenticateByField(array $data, string $field = 'uname'): ?int
95
    {
96
        if (!isset($data[$field])) {
97
            return null;
98
        }
99
100
        $mapping = $this->getMapping($field, $data[$field]);
101
        if (!$mapping->getPass()) {
102
            return null;
103
        }
104
        $passwordEncoder = $this->encoderFactory->getEncoder($mapping);
105
106
        if ($mapping && $this->passwordApi->passwordsMatch($data['pass'], $mapping->getPass())) {
107
            // old way - remove in Core-4.0.0
108
            // convert old encoding to new
109
            $this->updatePassword($mapping, $data['pass']);
110
111
            return $mapping->getUid();
112
        } elseif ($mapping && $passwordEncoder->isPasswordValid($mapping->getPass(), $data['pass'], null)) {
113
            // new way
114
            if ($passwordEncoder->needsRehash($mapping->getPass())) { // check to update hash to newer algo
115
                $this->updatePassword($mapping, $data['pass']);
116
            }
117
118
            return $mapping->getUid();
119
        }
120
121
        $request = $this->requestStack->getCurrentRequest();
122
        if ($request->hasSession() && ($session = $request->getSession())) {
123
            $session->getFlashBag()->add('error', 'Login failed.');
124
        }
125
126
        return null;
127
    }
128
129
    private function updatePassword(AuthenticationMappingEntity $mapping, string $unHashedPassword)
130
    {
131
        $mapping->setPass($this->encoderFactory->getEncoder($mapping)->encodePassword($unHashedPassword, null));
132
        $this->mappingRepository->persistAndFlush($mapping);
133
    }
134
135
    /**
136
     * Get a AuthenticationMappingEntity if it exists. If not, check for existing UserEntity and
137
     * migrate data from UserEntity to AuthenticationMappingEntity and return that.
138
     *
139
     * @throws Exception
140
     */
141
    private function getMapping(string $field, string $value): ?AuthenticationMappingEntity
142
    {
143
        /** @var AuthenticationMappingEntity $mapping */
144
        $mapping = $this->mappingRepository->findOneBy([$field => $value]);
145
        if (isset($mapping) && (
146
            ('email' === $field && ZAuthConstant::AUTHENTICATION_METHOD_UNAME === $mapping->getMethod())
147
            || ('uname' === $field && ZAuthConstant::AUTHENTICATION_METHOD_EMAIL === $mapping->getMethod()))
148
        ) {
149
            // mapping exists but method is set to opposite. allow either if possible.
150
            $mapping->setMethod(ZAuthConstant::AUTHENTICATION_METHOD_EITHER);
151
            $errors = $this->validator->validate($mapping);
152
            if (count($errors) > 0) {
153
                // the error is probably only because of duplicate email... so....
154
                $request = $this->requestStack->getCurrentRequest();
155
                if ($request->hasSession() && ($session = $request->getSession())) {
156
                    $session->getFlashBag()->add(
157
                        'error',
158
                        'The email you are trying to authenticate with is in use by another user. You can only login by username.'
159
                    );
160
                }
161
                $mapping = null;
162
            } else {
163
                $this->mappingRepository->persistAndFlush($mapping);
164
            }
165
        }
166
167
        return $mapping;
168
    }
169
170
    public function register(array $data = []): bool
171
    {
172
        $mapping = new AuthenticationMappingEntity();
173
        $mapping->setUid($data['uid']);
174
        $mapping->setUname($data['uname']);
175
        $mapping->setEmail($data['email']);
176
177
        if (empty($data['pass'])) {
178
            $mapping->setPass('');
179
        } else {
180
            $mapping->setPass($this->encoderFactory->getEncoder($mapping)->encodePassword($data['pass'], null));
181
        }
182
183
        $mapping->setMethod($this->getAlias());
184
185
        $userMustVerify = $this->variableApi->get('ZikulaZAuthModule', ZAuthConstant::MODVAR_EMAIL_VERIFICATION_REQUIRED, ZAuthConstant::DEFAULT_EMAIL_VERIFICATION_REQUIRED);
186
        $request = $this->requestStack->getCurrentRequest();
187
        if ($request->hasSession() && ($session = $request->getSession())) {
188
            $userMustVerify = $session->has(ZAuthConstant::MODVAR_EMAIL_VERIFICATION_REQUIRED)
189
                ? 'Y' === $session->get(ZAuthConstant::MODVAR_EMAIL_VERIFICATION_REQUIRED)
190
                : $userMustVerify;
191
        }
192
193
        $mapping->setVerifiedEmail(!$userMustVerify);
194
        $errors = $this->validator->validate($mapping);
195
        if ($request->hasSession() && ($session = $request->getSession()) && 0 < count($errors)) {
196
            foreach ($errors as $error) {
197
                $session->getFlashBag()->add('error', $error->getMessage());
198
            }
199
200
            return false;
201
        }
202
        $this->mappingRepository->persistAndFlush($mapping);
203
204
        return true;
205
    }
206
}
207