Passed
Push — b2.0.0 ( 2f7a91 )
by Sebastian
08:01
created

SynchronizerTokenProvider::__construct()   B

Complexity

Conditions 7
Paths 4

Size

Total Lines 22
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 7.7656

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 11
c 1
b 0
f 0
dl 0
loc 22
ccs 9
cts 12
cp 0.75
rs 8.8333
cc 7
nc 4
nop 4
crap 7.7656
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
18
/**
19
 * Syncronizer token provider
20
 */
21
class SynchronizerTokenProvider implements TokenProviderInterface
22
{
23
    /**
24
     * @var string CSRF_TOKEN_STORAGE Token storage key name in session array
25
     */
26
    private const CSRF_TOKEN_STORAGE = 'csrf_syncronizer_token';
27
28
    /**
29
     * @var string $sessionId Session id of the current session
30
     */
31
    private string $sessionId = '';
32
33
    /**
34
     * @var $expire Token validity in seconds, default 600 -> 10 minutes
0 ignored issues
show
Documentation Bug introduced by
The doc comment $expire at position 0 could not be parsed: Unknown type name '$expire' at position 0 in $expire.
Loading history...
35
     */
36
    private int $expire = 0;
37
38
    /**
39
     * @var $tokenLenght Token lenght in chars
0 ignored issues
show
Documentation Bug introduced by
The doc comment $tokenLenght at position 0 could not be parsed: Unknown type name '$tokenLenght' at position 0 in $tokenLenght.
Loading history...
40
     */
41
    private int $tokenLenght = 32;
42
43
    /**
44
     * @var $storageSize Maximum token nonces stored in session
0 ignored issues
show
Documentation Bug introduced by
The doc comment $storageSize at position 0 could not be parsed: Unknown type name '$storageSize' at position 0 in $storageSize.
Loading history...
45
     */
46
    private int $storageSize = 10;
47
48
    /**
49
     * Class constructor.
50
     *
51
     * @param string $sessionId   Session id of the current session
52
     * @param int    $expire      Token validity in seconds, default 600 -> 10 minutes
53
     * @param int    $storageSize Maximum token nonces stored in session
54
     * @param int    $tokenLenght Token lenght in bytes
55
     *
56
     * @throws BadExpireException      If $expire is less than 0 and greater than 86400
57
     * @throws BadStorageSizeException If $storageSize is less than 2 and greater than 64
58
     * @throws BadTokenLenghtException If $tokenLenght is less than 16 and greater than 128
59
     */
60 1
    public function __construct(string $sessionId, int $expire = 600, int $storageSize = 10, int $tokenLenght = 32)
61
    {
62
        // expire maximum tim is one day
63 1
        if ($expire < 0 || $expire > 86400) {
64
            throw new BadExpireException('Expire time must be between 0 and PHP_INT_MAX');
65
        }
66
67 1
        if ($storageSize < 2 || $storageSize > 128) {
68
            throw new BadStorageSizeException('Storage size must be between 2 and 128');
69
        }
70
71 1
        if ($tokenLenght < 16 || $tokenLenght > 128) {
72
            throw new BadTokenLenghtException('Token lenght must be between 16 and 128');
73
        }
74
75 1
        $this->sessionId = $sessionId;
76 1
        $this->expire = $expire;
77 1
        $this->tokenLenght = $tokenLenght;
78 1
        $this->storageSize = $storageSize;
79
80
        //if no nonce 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 nonce
92
        $token = \bin2hex(\random_bytes($this->tokenLenght));
93
        $time = \base_convert((string) \time(), 10, 16);
94
95
        //store new nonce
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