Completed
Push — master ( ac1fa0...9301b0 )
by Neomerx
02:58
created

CsrfTokenStorage::setTokenStorageKey()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 1
crap 1
1
<?php namespace Limoncello\Application\Packages\Csrf;
2
3
/**
4
 * Copyright 2015-2018 [email protected]
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 * http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
19
use ArrayAccess;
20
use Exception;
21
use Limoncello\Application\Contracts\Csrf\CsrfTokenStorageInterface;
22
23
/**
24
 * @package Limoncello\Application
25
 */
26
class CsrfTokenStorage implements CsrfTokenStorageInterface
27
{
28
    /**
29
     * Number of random bytes in a token.
30
     */
31
    protected const TOKEN_BYTE_LENGTH = 16;
32
33
    /**
34
     * @var ArrayAccess
35
     */
36
    private $sessionStorage;
37
38
    /**
39
     * @var string
40
     */
41
    private $tokenStorageKey;
42
43
    /**
44
     * @var null|int
45
     */
46
    private $maxTokens = null;
47
48
    /**
49
     * @var int
50
     */
51
    private $maxTokensGcThreshold;
52
53
    /**
54
     * @param ArrayAccess $sessionStorage
55
     * @param string      $tokenStorageKey
56
     * @param int|null    $maxTokens
57
     * @param int         $maxTokensGcThreshold
58
     */
59 3
    public function __construct(
60
        ArrayAccess $sessionStorage,
61
        string $tokenStorageKey,
62
        ?int $maxTokens,
63
        int $maxTokensGcThreshold
64
    ) {
65 3
        $this->setSessionStorage($sessionStorage)
66 3
            ->setTokenStorageKey($tokenStorageKey)
67 3
            ->setMaxTokens($maxTokens)
68 3
            ->setMaxTokensGcThreshold($maxTokensGcThreshold);
69
    }
70
71
    /**
72
     * @inheritdoc
73
     *
74
     * @throws Exception
75
     */
76 2
    public function create(): string
77
    {
78 2
        $tokenStorage = $this->getTokenStorage();
79 2
        $value        = $this->createTokenValue();
80 2
        $timestamp    = $this->createTokenTimestamp();
81
82 2
        $tokenStorage[$value] = $timestamp;
83
84
        // check if we should limit number to stored tokens
85 2
        $maxTokens = $this->getMaxTokens();
86 2
        if ($maxTokens !== null &&
87 2
            count($tokenStorage) > $maxTokens + $this->getMaxTokensGcThreshold()
88
        ) {
89
            // sort by timestamp and take last $maxTokens
90 1
            asort($tokenStorage, SORT_NUMERIC);
91 1
            $tokenStorage = array_slice($tokenStorage, -$maxTokens, null, true);
92
            // minus means count from the end ---------^
93
        }
94
95 2
        $this->setTokenStorage($tokenStorage);
96
97 2
        return $value;
98
    }
99
100
    /**
101
     * @inheritdoc
102
     */
103 1
    public function check(string $token): bool
104
    {
105 1
        $tokenStorage = $this->getTokenStorage();
106 1
        $tokenFound   = array_key_exists($token, $tokenStorage);
107 1
        if ($tokenFound === true) {
108
            // remove the token so it cannot be used again
109 1
            unset($tokenStorage[$token]);
110 1
            $this->setTokenStorage($tokenStorage);
111
        }
112
113 1
        return $tokenFound;
114
    }
115
116
    /**
117
     * @return ArrayAccess
118
     */
119 2
    protected function getSessionStorage(): ArrayAccess
120
    {
121 2
        return $this->sessionStorage;
122
    }
123
124
    /**
125
     * @param ArrayAccess $sessionStorage
126
     *
127
     * @return self
128
     */
129 3
    protected function setSessionStorage(ArrayAccess $sessionStorage): self
130
    {
131 3
        $this->sessionStorage = $sessionStorage;
132
133 3
        return $this;
134
    }
135
136
    /**
137
     * @return string
138
     */
139 2
    protected function getTokenStorageKey(): string
140
    {
141 2
        return $this->tokenStorageKey;
142
    }
143
144
    /**
145
     * @param string $tokenStorageKey
146
     *
147
     * @return self
148
     */
149 3
    protected function setTokenStorageKey(string $tokenStorageKey): self
150
    {
151 3
        assert(empty($tokenStorageKey) === false);
152
153 3
        $this->tokenStorageKey = $tokenStorageKey;
154
155 3
        return $this;
156
    }
157
158
    /**
159
     * @return int|null
160
     */
161 2
    protected function getMaxTokens(): ?int
162
    {
163 2
        return $this->maxTokens;
164
    }
165
166
    /**
167
     * @param int|null $maxTokens
168
     *
169
     * @return self
170
     */
171 3
    protected function setMaxTokens(?int $maxTokens): self
172
    {
173 3
        assert($maxTokens === null || $maxTokens > 0);
174
175 3
        $this->maxTokens = $maxTokens > 0 ? $maxTokens : null;
176
177 3
        return $this;
178
    }
179
180
    /**
181
     * @return int
182
     */
183 2
    protected function getMaxTokensGcThreshold(): int
184
    {
185 2
        return $this->maxTokensGcThreshold;
186
    }
187
188
    /**
189
     * @param int $maxTokensGcThreshold
190
     *
191
     * @return self
192
     */
193 3
    protected function setMaxTokensGcThreshold(int $maxTokensGcThreshold): self
194
    {
195 3
        assert($maxTokensGcThreshold >= 0);
196
197 3
        $this->maxTokensGcThreshold = $maxTokensGcThreshold >= 0 ? $maxTokensGcThreshold : 0;
198
199 3
        return $this;
200
    }
201
202
    /**
203
     * @return string
204
     *
205
     * @throws Exception
206
     */
207 2
    protected function createTokenValue(): string
208
    {
209 2
        $value = bin2hex(random_bytes(static::TOKEN_BYTE_LENGTH));
210
211 2
        return $value;
212
    }
213
214
    /**
215
     * Additional information that would be stored with a token. For example, could be creation timestamp.
216
     *
217
     * @return int
218
     */
219 2
    protected function createTokenTimestamp(): int
220
    {
221 2
        return time();
222
    }
223
224
    /**
225
     * @return array
226
     */
227 2
    protected function getTokenStorage(): array
228
    {
229 2
        $sessionStorage = $this->getSessionStorage();
230 2
        $storageKey     = $this->getTokenStorageKey();
231
232
        $tokenStorage
233 2
            = $sessionStorage->offsetExists($storageKey) === true ? $sessionStorage->offsetGet($storageKey) : [];
234
235 2
        return $tokenStorage;
236
    }
237
238
    /**
239
     * Replace whole token storage.
240
     *
241
     * @param array $tokenStorage
242
     *
243
     * @return self
244
     */
245 2
    protected function setTokenStorage(array $tokenStorage): self
246
    {
247 2
        $this->getSessionStorage()->offsetSet($this->getTokenStorageKey(), $tokenStorage);
248
249 2
        return $this;
250
    }
251
}
252