Passed
Push — master ( a717b5...dd05c8 )
by Florian
03:07 queued 01:19
created

PasswordIdentifier::checkPassword()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 19
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 3

Importance

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