Completed
Push — master ( 832136...c40ef1 )
by Sebastian
01:43
created

CsrfGuard::getToken()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 15
ccs 6
cts 6
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 6
nc 1
nop 0
crap 1
1
<?php
2
3
/**
4
 * Linna Cross-site Request Forgery Guard
5
 *
6
 * @author Sebastian Rapetti <[email protected]>
7
 * @copyright (c) 2017, Sebastian Rapetti
8
 * @license http://opensource.org/licenses/MIT MIT License
9
 */
10
declare(strict_types=1);
11
12
namespace Linna;
13
14
/**
15
 * Cross-site Request Forgery Guard
16
 */
17
class CsrfGuard
18
{
19
    /**
20
     * @var Session The session class.
21
     */
22
    private $session;
23
24
    /**
25
     * @var int Max number of tokens stored in session.
26
     */
27
    private $maxStorage;
28
29
    /**
30
     * @var int Rapresent the lenght of the token in bytes.
31
     */
32
    private $tokenStrength;
33
    
34
    /**
35
     * Constructor.
36
     *
37
     * @param int $maxStorage    Max number of tokens stored in session, work as
38
     *                           FIFO data structure, when maximun capacity is
39
     *                           reached, oldest token be dequeued from storage.
40
     * @param int $tokenStrength Rapresent the lenght of the token in bytes.
41
     */
42 18
    public function __construct(int $maxStorage, int $tokenStrength)
43
    {
44
        //if csrf array doesn't exist inside session, initialize it.
45
        //for code shortness: Null coalescing operator
46
        //http://php.net/manual/en/migration70.new-features.php
47 18
        $_SESSION['CSRF'] = $_SESSION['CSRF'] ?? [];
48
49 18
        $this->session = &$_SESSION;
50 18
        $this->maxStorage = $maxStorage;
51 18
        $this->tokenStrength = $tokenStrength;
52 18
    }
53
54
    /**
55
     * Limit number of token stored in session.
56
     */
57 17
    private function dequeue(array &$array)
58
    {
59 17
        $size = count($array);
60
        
61 17
        while ($size > $this->maxStorage){
62 14
            array_shift($array);          
63 14
            $size--;
64
        }
65 17
    }
66
67
    /**
68
     * Return csrf token as array.
69
     * 
70
     * @return array
71
     */
72 17
    public function getToken() : array
73
    {
74 17
        $tokenName = 'csrf_'.bin2hex(random_bytes(8));
75 17
        $token = bin2hex(random_bytes($this->tokenStrength));
76
77 17
        $this->session['CSRF'][$tokenName] = $token;
78
79
        //storage cleaning!
80
        //warning!! if you get in a page more token of maximun storage,
81
        //will there a leak of token, the firsts generated
82
        //in future I think throw and exception.
83 17
        $this->dequeue($this->session['CSRF']);
84
        
85 17
        return ['name' => $tokenName, 'token' => $token];
86
    }
87
88
    /**
89
     * Return csrf token as hidden input form.
90
     *
91
     * @return string
92
     */
93 1
    public function getHiddenInput() : string
94
    {
95 1
        $token = $this->getToken();
96
97 1
        return '<input type="hidden" name="'.$token['name'].'" value="'.$token['token'].'" />';
98
    }
99
100
    /**
101
     * Validate a csrf token.
102
     *
103
     * @param array $requestData From request or from superglobal variables $_POST,
104
     *                           $_GET, $_REQUEST and $_COOKIE.
105
     *
106
     * @return bool
107
     */
108 1
    public function validate(array $requestData) : bool
109
    {
110 1
        $arrayToken = $this->session['CSRF'];
111
112 1
        foreach ($requestData as $key => $value){
113 1
            if (isset($arrayToken[$key]) && hash_equals($arrayToken[$key], $value)){
114 1
                return true;
115
            }
116
        }
117
118 1
        return false;
119
    }
120
}
121