PasswordIdentifier   A
last analyzed

Complexity

Total Complexity 14

Size/Duplication

Total Lines 161
Duplicated Lines 0 %

Test Coverage

Coverage 87.5%

Importance

Changes 0
Metric Value
wmc 14
eloc 38
dl 0
loc 161
ccs 35
cts 40
cp 0.875
rs 10
c 0
b 0
f 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
A setPasswordField() 0 5 1
A findIdentity() 0 10 2
A setUsernameFields() 0 5 1
A __construct() 0 6 1
A setUsernameField() 0 3 1
A needsPasswordRehash() 0 3 1
A identify() 0 15 4
A checkPassword() 0 19 3
1
<?php
2
3
/**
4
 * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
5
 * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
6
 *
7
 * Licensed under The MIT License
8
 * For full copyright and license information, please see the LICENSE.txt
9
 * Redistributions of files must retain the above copyright notice.
10
 *
11
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
12
 * @link          https://cakephp.org CakePHP(tm) Project
13
 * @since         1.0.0
14
 * @license       https://opensource.org/licenses/mit-license.php MIT License
15
 */
16
17
declare(strict_types=1);
18
19
namespace Phauthentic\Authentication\Identifier;
20
21
use ArrayAccess;
22
use ArrayObject;
23
use Phauthentic\Authentication\Identifier\Resolver\ResolverInterface;
24
use Phauthentic\PasswordHasher\PasswordHasherInterface;
25
26
/**
27
 * Password Identifier
28
 *
29
 * Identifies authentication credentials with password.
30
 *
31
 * When configuring PasswordIdentifier you can pass in config to which fields,
32
 * model and additional conditions are used.
33
 */
34
class PasswordIdentifier extends AbstractIdentifier
35
{
36
    /**
37
     * Credential fields
38
     *
39
     * @var array<string, mixed>
40
     */
41
    protected array $credentialFields = [
42
        IdentifierInterface::CREDENTIAL_USERNAME => 'username',
43
        IdentifierInterface::CREDENTIAL_PASSWORD => 'password'
44
    ];
45
46
    /**
47
     * Resolver
48
     *
49
     * @var \Phauthentic\Authentication\Identifier\Resolver\ResolverInterface
50
     */
51
    protected ResolverInterface $resolver;
52
53
    /**
54
     * Password Hasher
55
     *
56
     * @var \Phauthentic\PasswordHasher\PasswordHasherInterface
57
     */
58
    protected PasswordHasherInterface $passwordHasher;
59
60
    /**
61
     * Whether or not the user authenticated by this class
62
     * requires their password to be rehashed with another algorithm.
63
     *
64
     * @var bool
65
     */
66
    protected bool $needsPasswordRehash = false;
67
68
    /**
69
     * Constructor
70
     *
71
     * @param ResolverInterface $resolver Resolver instance.
72 256
     * @param PasswordHasherInterface $passwordHasher Password hasher.
73
     */
74
    public function __construct(
75
        ResolverInterface $resolver,
76 256
        PasswordHasherInterface $passwordHasher
77 256
    ) {
78 256
        $this->resolver = $resolver;
79
        $this->passwordHasher = $passwordHasher;
80
    }
81
82
    /**
83
     * Set the username fields used to to get the credentials from.
84
     *
85
     * @param array<int, string> $usernames An array of fields.
86 4
     * @return $this
87
     */
88 4
    public function setUsernameFields(array $usernames): self
89
    {
90 4
        $this->credentialFields[self::CREDENTIAL_USERNAME] = $usernames;
91
92
        return $this;
93
    }
94
95
    /**
96
     * Set the single username field used to to get the credentials from.
97
     *
98
     * @param string $username Username field.
99
     * @return $this
100
     */
101
    public function setUsernameField(string $username): self
102
    {
103
        return $this->setUsernameFields([$username]);
104
    }
105
106
    /**
107
     * Sets the password field.
108
     *
109
     * @param string $password Password field.
110
     * @return $this
111
     */
112
    public function setPasswordField(string $password): self
113
    {
114
        $this->credentialFields[self::CREDENTIAL_PASSWORD] = $password;
115
116
        return $this;
117
    }
118
119
    /**
120 120
     * {@inheritDoc}
121
     */
122 120
    public function identify(array $credentials): ?ArrayAccess
123 4
    {
124
        if (!isset($credentials[self::CREDENTIAL_USERNAME])) {
125
            return null;
126 116
        }
127 116
128 76
        $data = $this->findIdentity($credentials[self::CREDENTIAL_USERNAME]);
129 76
        if (array_key_exists(self::CREDENTIAL_PASSWORD, $credentials)) {
130 24
            $password = $credentials[self::CREDENTIAL_PASSWORD];
131
            if (!$this->checkPassword($data, $password)) {
132
                return null;
133
            }
134 92
        }
135
136
        return $data;
137
    }
138
139
    /**
140
     * Find a user record using the username and password provided.
141
     * Input passwords will be hashed even when a user doesn't exist. This
142
     * helps mitigate timing attacks that are attempting to find valid usernames.
143
     *
144
     * @param \ArrayAccess|null $data The identity or null.
145
     * @param string|null $password The password.
146 76
     * @return bool
147
     */
148 76
    protected function checkPassword(?ArrayAccess $data, $password): bool
149
    {
150 76
        $passwordField = $this->credentialFields[self::CREDENTIAL_PASSWORD];
151 8
152 8
        if ($data === null) {
153
            $data = new ArrayObject([
154
                $passwordField => ''
155
            ]);
156 76
        }
157 76
158 76
        $hasher = $this->passwordHasher;
159 24
        $hashedPassword = $data[$passwordField];
160
        if (!$hasher->check((string)$password, $hashedPassword)) {
161
            return false;
162 52
        }
163
164 52
        $this->needsPasswordRehash = $hasher->needsRehash($hashedPassword);
165
166
        return true;
167
    }
168
169
    /**
170
     * Check if a password needs to be re-hashed
171
     *
172 4
     * @return bool
173
     */
174 4
    public function needsPasswordRehash(): bool
175
    {
176
        return $this->needsPasswordRehash;
177
    }
178
179
    /**
180
     * Find a user record using the username/identifier provided.
181
     *
182
     * @param string $identifier The username/identifier.
183 116
     * @return \ArrayAccess|null
184
     */
185 116
    protected function findIdentity($identifier): ?ArrayAccess
186
    {
187 116
        $fields = $this->credentialFields[self::CREDENTIAL_USERNAME];
188 116
189 116
        $conditions = [];
190
        foreach ((array)$fields as $field) {
191
            $conditions[$field] = $identifier;
192 116
        }
193
194
        return $this->resolver->find($conditions);
195
    }
196
}
197