SynchronizerTokenProvider::getToken()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 16
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 0
dl 0
loc 16
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * This file is part of the Linna Csrf Guard.
7
 *
8
 * @author Sebastian Rapetti <[email protected]>
9
 * @copyright (c) 2020, Sebastian Rapetti
10
 * @license http://opensource.org/licenses/MIT MIT License
11
 */
12
13
namespace Linna\CsrfGuard\Provider;
14
15
use Linna\CsrfGuard\Exception\BadExpireException;
16
use Linna\CsrfGuard\Exception\BadStorageSizeException;
17
use Linna\CsrfGuard\Exception\BadTokenLengthException;
18
use Linna\CsrfGuard\Exception\BadExpireTrait;
19
use Linna\CsrfGuard\Exception\BadStorageSizeTrait;
20
use Linna\CsrfGuard\Exception\BadTokenLengthTrait;
21
use Linna\CsrfGuard\Exception\SessionNotStartedException;
22
use Linna\CsrfGuard\Exception\SessionNotStartedTrait;
23
24
/**
25
 * Syncronizer token provider.
26
 *
27
 * <p>A random token with the expire time in this type of tokek, the token with the time are stored in session but only
28
 * the token is returned.</p>
29
 *
30
 * <p>
31
 * The difficulty about guess the token is proportional to his length, the formula is <code>1/16^(token_length*2)</code>.
32
 * Using a token of 16 byte means <code>1/16^(16*2)</code>, <code>1/16^32</code>.<br/>Who tray to guess the token has
33
 * a possibility of <code>1/(a number greater than the number of atoms in universe)<code/>.
34
 * </p>
35
 */
36
class SynchronizerTokenProvider implements TokenProviderInterface
37
{
38
    use BadExpireTrait;
39
    use BadStorageSizeTrait;
40
    use BadTokenLengthTrait;
41
    use SessionNotStartedTrait;
42
43
    /** @var string CSRF_TOKEN_STORAGE Token storage key name in session array. */
44
    private const CSRF_TOKEN_STORAGE = 'csrf_syncronizer_token';
45
46
    /**
47
     * Class constructor.
48
     *
49
     * @param int $expire      Token validity in seconds, default 600 -> 10 minutes.
50
     * @param int $storageSize Maximum token nonces stored in session.
51
     * @param int $tokenLength The desidered token length in bytes, consider that the time is added to the token.
52
     *
53
     * @throws BadExpireException      If <code>$expire</code> is less than 0 and greater than 86400.
54
     * @throws BadStorageSizeException If <code>$storageSize</code> is less than 2 and greater than 64.
55
     * @throws BadTokenLengthException If <code>$tokenLength</code> is less than 16 and greater than 128.
56
     */
57
    public function __construct(
58
        /** @var int $expire Token validity in seconds, default 600 -> 10 minutes. */
59
        private int $expire = 600,
60
        /** @var int $tokenLength Token length in chars. */
61
        private int $storageSize = 10,
62
        /** @var int $storageSize Maximum token nonces stored in session. */
63
        private int $tokenLength = 32
64
    ) {
65
        // from BadExpireTrait, BadStorageSizeTrait, BadTokenLengthException and SessionNotStartedTrait
66
        /** @throws BadExpireException */
67
        $this->checkBadExpire($expire);
68
        /** @throws BadStorageSizeException */
69
        $this->checkBadStorageSize($storageSize);
70
        /** @throws BadTokenLengthException */
71
        $this->checkBadTokenLength($tokenLength);
72
        /** @throws SessionNotStartedException */
73
        $this->checkSessionNotStarted();
74
75
        $this->expire = $expire;
76
        $this->tokenLength = $tokenLength;
77
        $this->storageSize = $storageSize;
78
79
        //if any token stored, initialize the session storage
80
        $_SESSION[self::CSRF_TOKEN_STORAGE] ??= [];
81
    }
82
83
    /**
84
     * Return new Synchronizer based Token.
85
     *
86
     * @return string The token in hex format.
87
     */
88
    public function getToken(): string
89
    {
90
        //generate new token
91
        $token = \bin2hex(\random_bytes(\max(1, $this->tokenLength)));
92
        $time = \dechex(\time());
93
94
        //store new token
95
        $_SESSION[self::CSRF_TOKEN_STORAGE][] = $token.$time;
96
97
        //check if the storage is growt beyond the maximun size
98
        if (\count($_SESSION[self::CSRF_TOKEN_STORAGE]) > $this->storageSize) {
99
            //remove the oldest stores nonce
100
            \array_shift($_SESSION[self::CSRF_TOKEN_STORAGE]);
101
        }
102
103
        return $token;
104
    }
105
106
    /**
107
     * Validate Synchronizer based Token.
108
     *
109
     * @param string $token Token must be validated, hex format.
110
     *
111
     * @return bool True if the token is valid, false otherwise.
112
     */
113
    public function validate(string $token): bool
114
    {
115
        //reference to token storage in session
116
        $tokens = &$_SESSION[self::CSRF_TOKEN_STORAGE];
117
        //get current size of token storage in session
118
        $size = \count($tokens);
119
        //bytes to b16 chars
120
        $chars = $this->tokenLength * 2;
121
        //current time
122
        $time = \time();
123
124
        for ($i = $size -1; $i > -1; $i--) {
125
            //get token from storage
126
            //if token in storage doesn't match user token continue
127
            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...
128
                continue;
129
            }
130
131
            //get time from storage
132
            //subtract the length of the token to the token+time
133
            $tmpTime = \substr($tokens[$i], $chars);
134
            //timestamp from token time
135
            $timestamp = \hexdec($tmpTime);
136
137
            //token expiration check
138
            if (($timestamp + $this->expire) >= $time) {
139
                return true;
140
            }
141
142
            //remove token expired from session
143
            unset($tokens[$i]);
144
        }
145
146
        return false;
147
    }
148
}
149