CookieAuthenticator::setRememberMeField()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 2
c 0
b 0
f 0
dl 0
loc 5
ccs 2
cts 2
cp 1
rs 10
cc 1
nc 1
nop 1
crap 1
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\Authenticator;
20
21
use ArrayAccess;
22
use Phauthentic\Authentication\Authenticator\Storage\StorageInterface;
23
use Phauthentic\Authentication\Identifier\IdentifierInterface;
24
use Phauthentic\PasswordHasher\PasswordHasherInterface;
25
use Phauthentic\Authentication\UrlChecker\UrlCheckerInterface;
26
use Psr\Http\Message\ResponseInterface;
27
use Psr\Http\Message\ServerRequestInterface;
28
29
/**
30
 * Cookie Authenticator
31
 *
32
 * Authenticates an identity based on a cookies data.
33
 */
34
class CookieAuthenticator extends AbstractAuthenticator implements PersistenceInterface
35
{
36
    use CredentialFieldsTrait;
37
    use UrlAwareTrait;
38
39
    /**
40
     * Password hasher
41
     *
42
     * @var \Phauthentic\PasswordHasher\PasswordHasherInterface
43
     */
44
    protected $passwordHasher;
45
46
    /**
47
     * Storage Implementation
48
     *
49
     * @var \Phauthentic\Authentication\Authenticator\Storage\StorageInterface
50
     */
51
    protected $storage;
52
53
    /**
54
     * "Remember me" field
55
     *
56
     * @var string
57
     */
58
    protected $rememberMeField = 'remember_me';
59
60
    /**
61 40
     * {@inheritDoc}
62
     */
63
    public function __construct(
64
        IdentifierInterface $identifier,
65
        StorageInterface $storage,
66
        PasswordHasherInterface $passwordHasher,
67 40
        UrlCheckerInterface $urlChecker
68
    ) {
69 40
        parent::__construct($identifier);
70 40
71 40
        $this->storage = $storage;
72 40
        $this->passwordHasher = $passwordHasher;
73
        $this->urlChecker = $urlChecker;
74
    }
75
76
    /**
77
     * Sets "remember me" form field name.
78
     *
79
     * @param string $field Field name.
80 4
     * @return $this
81
     */
82 4
    public function setRememberMeField(string $field): self
83
    {
84 4
        $this->rememberMeField = $field;
85
86
        return $this;
87
    }
88
89
    /**
90 20
     * {@inheritDoc}
91
     */
92 20
    public function authenticate(ServerRequestInterface $request): ResultInterface
93
    {
94 20
        $token = $this->storage->read($request);
95 4
96 4
        if ($token === null) {
97
            return new Result(null, Result::FAILURE_CREDENTIALS_MISSING, [
98
                'Login credentials not found'
99
            ]);
100 16
        }
101 4
102 4
        if (!is_array($token) || count($token) !== 2) {
103
            return new Result(null, Result::FAILURE_CREDENTIALS_INVALID, [
104
                'Cookie token is invalid.'
105
            ]);
106 12
        }
107
108 12
        [$username, $tokenHash] = $token;
109 12
110
        $data = $this->identifier->identify([
111
            IdentifierInterface::CREDENTIAL_USERNAME => $username,
112 12
        ]);
113 4
114
        if (empty($data)) {
115
            return new Result(null, Result::FAILURE_IDENTITY_NOT_FOUND, $this->identifier->getErrors());
116 8
        }
117 4
118 4
        if (!$this->checkToken($data, $tokenHash)) {
119
            return new Result(null, Result::FAILURE_CREDENTIALS_INVALID, [
120
                'Cookie token does not match'
121
            ]);
122 4
        }
123
124
        return new Result($data, Result::SUCCESS);
125
    }
126
127
    /**
128 16
     * {@inheritDoc}
129
     * @throws \JsonException
130 16
     */
131 16
    public function persistIdentity(
132
        ServerRequestInterface $request,
133 16
        ResponseInterface $response,
134 8
        ArrayAccess $data
135
    ): ResponseInterface {
136
        $field = $this->rememberMeField;
137 8
        $bodyData = $request->getParsedBody();
138
139 8
        if (!$this->checkUrl($request) || !is_array($bodyData) || empty($bodyData[$field])) {
140
            return $response;
141
        }
142
143
        $token = $this->createToken($data);
144
145 4
        return $this->storage->write($request, $response, $token);
146
    }
147 4
148
    /**
149
     * {@inheritDoc}
150
     */
151
    public function clearIdentity(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
152
    {
153
        return $this->storage->clear($request, $response);
154
    }
155
156
    /**
157
     * Creates a plain part of a cookie token.
158 16
     *
159
     * Returns concatenated username and password hash.
160 16
     *
161 16
     * @param \ArrayAccess $data Identity data.
162
     * @return string
163 16
     */
164
    protected function createPlainToken(ArrayAccess $data): string
165
    {
166
        $usernameField = $this->credentialFields[IdentifierInterface::CREDENTIAL_USERNAME];
167
        $passwordField = $this->credentialFields[IdentifierInterface::CREDENTIAL_PASSWORD];
168
169
        return $data[$usernameField] . $data[$passwordField];
170
    }
171
172
    /**
173
     * Creates a full cookie token serialized as a JSON sting.
174 8
     * Cookie token consists of a username and hashed username + password hash.
175
     *
176 8
     * @param \ArrayAccess $data Identity data.
177 8
     * @return string
178
     * @throws \JsonException
179 8
     */
180
    protected function createToken(ArrayAccess $data): string
181 8
    {
182
        $plain = $this->createPlainToken($data);
183
        $hash = $this->passwordHasher->hash($plain);
184
185
        $usernameField = $this->credentialFields[IdentifierInterface::CREDENTIAL_USERNAME];
186
187
        return (string)json_encode([$data[$usernameField], $hash], JSON_THROW_ON_ERROR);
188
    }
189
190
    /**
191 8
     * Checks whether a token hash matches the identity data.
192
     *
193 8
     * @param \ArrayAccess $data Identity data.
194
     * @param string $tokenHash Hashed part of a cookie token.
195 8
     * @return bool
196
     */
197
    protected function checkToken(ArrayAccess $data, string $tokenHash): bool
198
    {
199
        $plain = $this->createPlainToken($data);
200
201
        return $this->passwordHasher->check($plain, $tokenHash);
202
    }
203
}
204