Passed
Push — b2.0.0 ( 59ba4e...bb1327 )
by Sebastian
02:46
created

SynchronizerTokenProvider::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 17
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 8
c 1
b 0
f 0
dl 0
loc 17
ccs 9
cts 9
cp 1
rs 10
cc 1
nc 1
nop 4
crap 1
1
<?php
2
3
/**
4
 * Linna Cross-site Request Forgery Guard
5
 *
6
 * @author Sebastian Rapetti <[email protected]>
7
 * @copyright (c) 2020, Sebastian Rapetti
8
 * @license http://opensource.org/licenses/MIT MIT License
9
 */
10
declare(strict_types=1);
11
12
namespace Linna\CsrfGuard\Provider;
13
14
use Linna\CsrfGuard\Exception\BadExpireException;
15
use Linna\CsrfGuard\Exception\BadStorageSizeException;
16
use Linna\CsrfGuard\Exception\BadTokenLenghtException;
17
use Linna\CsrfGuard\Exception\BadExpireTrait;
18
use Linna\CsrfGuard\Exception\BadStorageSizeTrait;
19
use Linna\CsrfGuard\Exception\BadTokenLenghtTrait;
20
21
/**
22
 * Syncronizer token provider
23
 */
24
class SynchronizerTokenProvider implements TokenProviderInterface
25
{
26
    use BadExpireTrait, BadStorageSizeTrait, BadTokenLenghtTrait;
27
28
    /**
29
     * @var string CSRF_TOKEN_STORAGE Token storage key name in session array
30
     */
31
    private const CSRF_TOKEN_STORAGE = 'csrf_syncronizer_token';
32
33
    /**
34
     * @var string $sessionId Session id of the current session
35
     */
36
    private string $sessionId = '';
37
38
    /**
39
     * @var int $expire Token validity in seconds, default 600 -> 10 minutes
40
     */
41
    private int $expire = 0;
42
43
    /**
44
     * @var int $tokenLenght Token lenght in chars
45
     */
46
    private int $tokenLenght = 32;
47
48
    /**
49
     * @var int $storageSize Maximum token nonces stored in session
50
     */
51
    private int $storageSize = 10;
52
53
    /**
54
     * Class constructor.
55
     *
56
     * @param string $sessionId   Session id of the current session
57
     * @param int    $expire      Token validity in seconds, default 600 -> 10 minutes
58
     * @param int    $storageSize Maximum token nonces stored in session
59
     * @param int    $tokenLenght Token lenght in bytes
60
     *
61
     * @throws BadExpireException      If $expire is less than 0 and greater than 86400
62
     * @throws BadStorageSizeException If $storageSize is less than 2 and greater than 64
63
     * @throws BadTokenLenghtException If $tokenLenght is less than 16 and greater than 128
64
     */
65 7
    public function __construct(string $sessionId, int $expire = 600, int $storageSize = 10, int $tokenLenght = 32)
66
    {
67
        // from BadExpireTrait, BadStorageSizeTrait, BadTokenLenghtException
68
        /** @throws BadExpireException */
69 7
        $this->checkBadExpire($expire);
70
        /** @throws BadStorageSizeException */
71 5
        $this->checkBadStorageSize($storageSize);
72
        /** @throws BadTokenLenghtException */
73 3
        $this->checkBadTokenLenght($tokenLenght);
74
75 1
        $this->sessionId = $sessionId;
76 1
        $this->expire = $expire;
77 1
        $this->tokenLenght = $tokenLenght;
78 1
        $this->storageSize = $storageSize;
79
80
        //if any token stored, initialize the session storage
81 1
        $_SESSION[static::CSRF_TOKEN_STORAGE] ??= [];
82 1
    }
83
84
    /**
85
     * Return new Synchronizer based Token.
86
     *
87
     * @return string A hex token
88
     */
89
    public function getToken(): string
90
    {
91
        //generate new token
92
        $token = \bin2hex(\random_bytes($this->tokenLenght));
93
        $time = \base_convert((string) \time(), 10, 16);
94
95
        //store new token
96
        $_SESSION[static::CSRF_TOKEN_STORAGE][] = $token.$time;
97
98
        //check if the storage is growt beyond the maximun size
99
        if (\count($_SESSION[static::CSRF_TOKEN_STORAGE]) > $this->storageSize) {
100
            //remove the oldest stores nonce
101
            \array_shift($_SESSION[static::CSRF_TOKEN_STORAGE]);
102
        }
103
104
        return $token;
105
    }
106
107
    /**
108
     * Validate Synchronizer based Token.
109
     *
110
     * @param string $token Token must be validated, hex format
111
     *
112
     * @return bool
113
     */
114
    public function validate(string $token): bool
115
    {
116
        //reference to token storage in session
117
        $tokens = &$_SESSION[static::CSRF_TOKEN_STORAGE];
118
        //get current size of token storage in session
119
        $size = \count($tokens);
120
        //bytes to b16 chars
121
        $chars = $this->tokenLenght * 2;
122
        //current time
123
        $time = \time();
124
125
        for ($i = $size -1; $i > -1; $i--) {
126
            //get token from storage
127
            //if token in storage doesn't match user token continue
128
            if (($tmpToken = \substr($tokens[$i], 0, $chars)) !== $token) {
0 ignored issues
show
Unused Code introduced by
The assignment to $tmpToken is dead and can be removed.
Loading history...
129
                continue;
130
            }
131
132
            //get time from storage
133
            $tmpTime = \substr($tokens[$i], $chars);
134
            //timestamp from token time
135
            $timestamp = (int) \base_convert($tmpTime, 16, 10);
136
137
            //token expiration check
138
            if (($timestamp + $this->expire) > $time) {
139
                return true;
140
            }
141
        }
142
143
        return false;
144
    }
145
}
146