Passed
Push — master ( 5b56d7...ffd336 )
by Gabor
12:22
created

ServiceAdapter::generate()   A

Complexity

Conditions 2
Paths 3

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 10
nc 3
nop 1
dl 0
loc 17
ccs 0
cts 13
cp 0
crap 6
rs 9.9332
c 0
b 0
f 0
1
<?php
2
/**
3
 * WebHemi.
4
 *
5
 * PHP version 7.1
6
 *
7
 * @copyright 2012 - 2018 Gixx-web (http://www.gixx-web.com)
8
 * @license   https://opensource.org/licenses/MIT The MIT License (MIT)
9
 *
10
 * @link http://www.gixx-web.com
11
 */
12
declare(strict_types = 1);
13
14
namespace WebHemi\CSRF\ServiceAdapter\Base;
15
16
use WebHemi\CSRF\ServiceInterface;
17
use WebHemi\Session\ServiceInterface as SessionInterface;
18
19
/**
20
 * Class ServiceAdapter.
21
 */
22
class ServiceAdapter implements ServiceInterface
23
{
24
    protected $sessionManager;
25
26
    /**
27
     * ServiceInterface constructor.
28
     *
29
     * @param SessionInterface $sessionManager
30
     */
31
    public function __construct(SessionInterface $sessionManager)
32
    {
33
        $this->sessionManager = $sessionManager;
34
    }
35
36
    /**
37
     * Generate a CSRF token.
38
     *
39
     * @param string $key
40
     * @return string
41
     */
42
    public function generate(string $key) : string
43
    {
44
        $key = preg_replace('/[^a-zA-Z0-9]/', '', $key);
45
        $extra = $this->getClientHash();
46
47
        try {
48
            $randomString = bin2hex(random_bytes(16));
49
            $randomString = str_pad(substr($randomString, 0, 32), 32, 'a', STR_PAD_LEFT);
50
        } catch (\Throwable $error) {
51
            $randomString = $this->getRandomString(32);
52
        }
53
54
        $token = base64_encode(time() . $extra . $randomString);
55
56
        $this->sessionManager->set(self::SESSION_PREFIX.'_'.$key, $token);
57
58
        return $token;
59
    }
60
61
    /**
62
     * Check the CSRF token is valid.
63
     *
64
     * @param string $key
65
     * @param string $token
66
     * @param null|int $ttl
67
     * @param bool $multiple
68
     * @return bool
69
     */
70
    public function verify(string $key, string $token, ? int $ttl = null, bool $multiple = false) : bool
71
    {
72
        $key = preg_replace('/[^a-zA-Z0-9]/', '', $key);
73
74
        $sessionToken = $this->sessionManager->get(self::SESSION_PREFIX.'_'.$key) ?? '';
75
76
        if (!$multiple) {
77
            $this->sessionManager->delete(self::SESSION_PREFIX.'_'.$key);
78
        }
79
80
        $sessionToken = $this->decodeToken($sessionToken);
81
        $token = $this->decodeToken($token);
82
83
        return !((!empty($ttl) && $sessionToken['time'] + $ttl > time())
84
            || ($token['extra'] != $this->getClientHash())
85
            || ($sessionToken['randomString'] != $token['randomString'])
86
        );
87
    }
88
89
    /**
90
     * Decodes the given token.
91
     *
92
     * @param string $token
93
     * @return array
94
     */
95
    protected function decodeToken(string $token) : array
96
    {
97
        $token = base64_decode($token) ?: str_repeat('0', 82);
98
99
        return [
100
            'time' => substr($token, 0, 10),
101
            'extra' => substr($token, 10, 40),
102
            'randomString' => substr($token, 50),
103
        ];
104
    }
105
106
    /**
107
     * @return string
108
     */
109
    protected function getClientHash() : string
110
    {
111
        return sha1($_SERVER['REMOTE_ADDR'] . $_SERVER['HTTP_USER_AGENT']);
112
    }
113
114
    /**
115
     * Generate a random string
116
     *
117
     * @param int $length
118
     * @return string
119
     */
120
    protected function getRandomString(int $length) : string
121
    {
122
        $seed = 'abcdef0123456789';
123
        $max = strlen($seed) - 1;
124
        $string = '';
125
126
        for ($i = 0; $i < $length; ++$i) {
127
            $string .= $seed[intval(mt_rand(0.0, $max))];
128
        }
129
130
        return $string;
131
    }
132
}
133