Passed
Push — master ( 388e0b...41d6d2 )
by
unknown
50s queued 10s
created

Authenticator::authenticateByPassword()   B

Complexity

Conditions 10
Paths 12

Size

Total Lines 56

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 56
rs 7.0933
c 0
b 0
f 0
cc 10
nc 12
nop 2

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
namespace Charcoal\User;
4
5
use InvalidArgumentException;
6
use RuntimeException;
7
8
// From PSR-3
9
use Psr\Log\LoggerAwareInterface;
10
use Psr\Log\LoggerAwareTrait;
11
12
// From 'charcoal-factory'
13
use Charcoal\Factory\FactoryInterface;
14
15
/**
16
 * The Authenticator service helps with user authentication / login.
17
 *
18
 * ## Constructor dependencies
19
 *
20
 * Constructor dependencies are passed as an array of `key=>value` pair.
21
 * The required dependencies are:
22
 *
23
 * - `logger` A PSR3 logger instance
24
 * - `user_type` The user object type (FQN or ident)
25
 * - `user_factory` The Factory used to instanciate new users.
26
 * - `token_type` The auth token object type (FQN or ident)
27
 * - `token_factory` The Factory used to instanciate new auth tokens.
28
 */
29
class Authenticator implements LoggerAwareInterface
30
{
31
    use LoggerAwareTrait;
32
33
    /**
34
     * The user object type.
35
     *
36
     * @var string
37
     */
38
    private $userType;
39
40
    /**
41
     * Store the user model factory instance for the current class.
42
     *
43
     * @var FactoryInterface
44
     */
45
    private $userFactory;
46
47
    /**
48
     * The auth-token object type.
49
     *
50
     * @var string
51
     */
52
    private $tokenType;
53
54
    /**
55
     * Store the auth-token model factory instance for the current class.
56
     *
57
     * @var FactoryInterface
58
     */
59
    private $tokenFactory;
60
61
    /**
62
     * @param array $data Class dependencies.
63
     */
64
    public function __construct(array $data)
65
    {
66
        $this->setLogger($data['logger']);
67
        $this->setUserType($data['user_type']);
68
        $this->setUserFactory($data['user_factory']);
69
        $this->setTokenType($data['token_type']);
70
        $this->setTokenFactory($data['token_factory']);
71
    }
72
73
    /**
74
     * Determine if the current user is authenticated.
75
     *
76
     * The user is authenticated via _session ID_ or _auth token_.
77
     *
78
     * @return \Charcoal\User\UserInterface|null Returns the authenticated user object
79
     *     or NULL if not authenticated.
80
     */
81
    public function authenticate()
82
    {
83
        $u = $this->authenticateBySession();
84
        if ($u) {
85
            return $u;
86
        }
87
88
        $u = $this->authenticateByToken();
89
        if ($u) {
90
            return $u;
91
        }
92
93
        return null;
94
    }
95
96
    /**
97
     * Attempt to authenticate a user using the given credentials.
98
     *
99
     * @param string $email    Email, part of necessary credentials.
100
     * @param string $password Password, part of necessary credentials.
101
     * @throws InvalidArgumentException If email or password are invalid or empty.
102
     * @return \Charcoal\User\UserInterface|null Returns the authenticated user object
103
     *     or NULL if not authenticated.
104
     */
105
    public function authenticateByPassword($email, $password)
106
    {
107
        if (!is_string($email) || !is_string($password)) {
108
            throw new InvalidArgumentException(
109
                'Email and password must be strings'
110
            );
111
        }
112
113
        if ($email == '' || $password == '') {
114
            throw new InvalidArgumentException(
115
                'Email and password can not be empty.'
116
            );
117
        }
118
119
        $user = $this->userFactory()->create($this->userType());
120
        if (!$user->source()->tableExists()) {
121
            $user->source()->createTable();
122
        }
123
124
        // Load the user by email
125
        $key = 'email';
126
        $user->loadFrom($key, $email);
127
128
        if ($user[$key] !== $email) {
129
            return null;
130
        }
131
132
        if ($user->active() === false) {
133
            return null;
134
        }
135
136
        // Validate password
137
        if (password_verify($password, $user->password())) {
138
            if (password_needs_rehash($user->password(), PASSWORD_DEFAULT)) {
139
                $this->logger->notice(sprintf(
140
                    'Rehashing password for user "%s" (%s)',
141
                    $user->email(),
142
                    $this->userType()
143
                ));
144
                $hash = password_hash($password, PASSWORD_DEFAULT);
145
                $user->setPassword($hash);
146
                $user->update(['password']);
147
            }
148
149
            $user->login();
150
151
            return $user;
152
        } else {
153
            $this->logger->warning(sprintf(
154
                'Invalid login attempt for user "%s": invalid password.',
155
                $user->email()
156
            ));
157
158
            return null;
159
        }
160
    }
161
162
    /**
163
     * Retrieve the user object type.
164
     *
165
     * @return string
166
     */
167
    protected function userType()
168
    {
169
        return $this->userType;
170
    }
171
172
173
    /**
174
     * Retrieve the user model factory.
175
     *
176
     * @throws RuntimeException If the model factory was not previously set.
177
     * @return FactoryInterface
178
     */
179
    protected function userFactory()
180
    {
181
        return $this->userFactory;
182
    }
183
184
    /**
185
     * Retrieve the auth-token object type.
186
     *
187
     * @return string
188
     */
189
    protected function tokenType()
190
    {
191
        return $this->tokenType;
192
    }
193
194
    /**
195
     * Retrieve the auth-token model factory.
196
     *
197
     * @throws RuntimeException If the token factory was not previously set.
198
     * @return FactoryInterface
199
     */
200
    protected function tokenFactory()
201
    {
202
        return $this->tokenFactory;
203
    }
204
205
    /**
206
     * Set the user object type (model).
207
     *
208
     * @param string $type The user object type.
209
     * @throws InvalidArgumentException If the user object type parameter is not a string.
210
     * @return void
211
     */
212
    private function setUserType($type)
213
    {
214
        if (!is_string($type)) {
215
            throw new InvalidArgumentException(
216
                'User object type must be a string'
217
            );
218
        }
219
220
        $this->userType = $type;
221
    }
222
223
    /**
224
     * Set a user model factory.
225
     *
226
     * @param FactoryInterface $factory The factory used to create new user instances.
227
     * @return void
228
     */
229
    private function setUserFactory(FactoryInterface $factory)
230
    {
231
        $this->userFactory = $factory;
232
    }
233
234
    /**
235
     * Set the authorization token type (model).
236
     *
237
     * @param string $type The auth-token object type.
238
     * @throws InvalidArgumentException If the token object type parameter is not a string.
239
     * @return void
240
     */
241
    private function setTokenType($type)
242
    {
243
        if (!is_string($type)) {
244
            throw new InvalidArgumentException(
245
                'Token object type must be a string'
246
            );
247
        }
248
249
        $this->tokenType = $type;
250
    }
251
252
    /**
253
     * Set a model factory for token-based authentication.
254
     *
255
     * @param FactoryInterface $factory The factory used to create new auth-token instances.
256
     * @return void
257
     */
258
    private function setTokenFactory(FactoryInterface $factory)
259
    {
260
        $this->tokenFactory = $factory;
261
    }
262
263
    /**
264
     * Attempt to authenticate a user using their session ID.
265
     *
266
     * @return \Charcoal\User\UserInterface|null Returns the authenticated user object
267
     *     or NULL if not authenticated.
268
     */
269
    private function authenticateBySession()
270
    {
271
        $u = $this->userFactory()->create($this->userType());
272
        // Call static method on user
273
        $u = call_user_func([get_class($u), 'getAuthenticated'], $this->userFactory());
274
275
        if ($u && $u->id()) {
276
            $u->saveToSession();
277
278
            return $u;
279
        } else {
280
            return null;
281
        }
282
    }
283
284
    /**
285
     * Attempt to authenticate a user using their auth token.
286
     *
287
     * @return \Charcoal\User\UserInterface|null Returns the authenticated user object
288
     *     or NULL if not authenticated.
289
     */
290
    private function authenticateByToken()
291
    {
292
        $tokenType = $this->tokenType();
293
        $authToken = $this->tokenFactory()->create($tokenType);
294
295
        if ($authToken->metadata()->enabled() !== true) {
296
            return null;
297
        }
298
299
        $tokenData = $authToken->getTokenDataFromCookie();
300
        if (!$tokenData) {
301
            return null;
302
        }
303
        $userId = $authToken->getUserIdFromToken($tokenData['ident'], $tokenData['token']);
304
        if (!$userId) {
305
            return null;
306
        }
307
308
        $u = $this->userFactory()->create($this->userType());
309
        $u->load($userId);
310
311
        if ($u->id()) {
312
            $u->saveToSession();
313
            return $u;
314
        } else {
315
            return null;
316
        }
317
    }
318
}
319