1
<?php
2
/**
3
 * This file is part of the Shieldon package.
4
 *
5
 * (c) Terry L. <contact@terryl.in>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 * 
10
 * php version 7.1.0
11
 * 
12
 * @category  Web-security
13
 * @package   Shieldon
14
 * @author    Terry Lin <contact@terryl.in>
15
 * @copyright 2019 terrylinooo
16
 * @license   https://github.com/terrylinooo/shieldon/blob/2.x/LICENSE MIT
17
 * @link      https://github.com/terrylinooo/shieldon
18
 * @see       https://shieldon.io
19
 */
20

21
declare(strict_types=1);
22

23
namespace Shieldon\Firewall\Driver;
24

25
use Shieldon\Firewall\Driver\DriverProvider;
26
use Redis;
27
use function is_bool;
28
use function json_decode;
29
use function json_encode;
30
use function ksort;
31

32
/**
33
 * Redis Driver.
34
 */
35
class RedisDriver extends DriverProvider
36
{
37
    /**
38
     * Redis instance.
39
     *
40
     * @var object|null
41
     */
42
    protected $redis;
43

44
    /**
45
     * Constructor.
46
     *
47
     * @param Redis $redis The Redis instance.
48
     * 
49
     * @return void
50
     */
51 3
    public function __construct(Redis $redis)
52
    {
53 3
        $this->redis = $redis;
54
    }
55

56
    /**
57
     * Set data channel.
58
     *
59
     * @param string $channel The prefix of data tables.
60
     *
61
     * @return void
62
     */
63 3
    public function setChannel(string $channel): void
64
    {
65 3
        $this->channel = $channel;
66

67 3
        if (!empty($this->channel)) {
68 3
            $this->tableFilterLogs = $this->channel . ':shieldon_filter_logs';
69 3
            $this->tableRuleList   = $this->channel . ':shieldon_rule_list';
70 3
            $this->tableSessions   = $this->channel . ':shieldon_sessions';
71
        }
72
    }
73

74
    /**
75
     * Initialize data tables.
76
     *
77
     * @param bool $dbCheck This is for creating data tables automatically
78
     *                      Turn it off, if you don't want to check data tables every pageview.
79
     *
80
     * @return void
81
     */
82 3
    protected function doInitialize(bool $dbCheck = true): void
83
    {
84 3
        $this->isInitialized = true;
85
    }
86

87
    /**
88
     * {@inheritDoc}
89
     * 
90
     * @param string $type The type of the data table.
91
     * 
92
     * @return bool
93
     */
94 3
    protected function doFetchAll(string $type = 'filter'): array
95
    {
96 3
        $results = [];
97

98 3
        $this->assertInvalidDataTable($type);
99

100 3
        $keys = $this->redis->keys($this->getNamespace($type) . ':*');
101

102 3
        foreach ($keys as $key) {
103 3
            $content = $this->redis->get($key);
104

105 3
            if (!empty($content)) {
106 3
                $content = json_decode($content, true);
107

108 3
                if ($type === 'session') {
109 3
                    $sort = $content['microtimestamp'] . '.' . $content['id']; 
110
                } else {
111 3
                    $sort = $content['log_ip'];
112
                }
113
    
114 3
                $results[$sort] = $content; 
115
            }
116
        }
117

118
        // Sort by ascending timestamp (microtimestamp).
119 3
        ksort($results);
120

121 3
        return $results;
122
    }
123

124
    /**
125
     * {@inheritDoc}
126
     * 
127
     * @param string $ip   The data id of the entry to fetch.
128
     * @param string $type The type of the data table.
129
     * 
130
     * @return array
131
     */
132 3
    protected function doFetch(string $ip, string $type = 'filter'): array
133
    {
134 3
        $results = [];
135

136 3
        if (!$this->checkExist($ip, $type)) {
137 3
            return $results;
138
        }
139

140 3
        if ($type === 'filter') {
141 3
            $content = $this->redis->get($this->getKeyName($ip, $type));
142 3
            $resultData = json_decode($content, true);
143

144 3
            if (!empty($resultData['log_data'])) {
145 3
                $results = $resultData['log_data']; 
146
            }
147

148
        } else {
149
            // type: rule | session
150 3
            $content = $this->redis->get($this->getKeyName($ip, $type));
151 3
            $results = json_decode($content, true);
152
        }
153

154 3
        return $results;
155
    }
156

157
    /**
158
     * {@inheritDoc}
159
     *
160
     * @param string $ip   The data id of the entry to check for.
161
     * @param string $type The type of the data table.
162
     *
163
     * @return bool
164
     */
165 3
    protected function checkExist(string $ip, string $type = 'filter'): bool
166
    {
167 3
        $isExist = $this->redis->exists($this->getKeyName($ip, $type));
168

169
        // This function took a single argument and returned TRUE or FALSE in phpredis versions < 4.0.0.
170

171
        // @codeCoverageIgnoreStart
172
        if (is_bool($isExist)) {
173
            return $isExist;
174
        }
175

176
        return $isExist > 0;
177
        // @codeCoverageIgnoreEnd
178
    }
179

180
    /**
181
     * {@inheritDoc}
182
     * 
183
     * @param string $ip     The data id.
184
     * @param array  $data   The data.
185
     * @param string $type   The type of the data table.
186
     * @param int    $expire The data will be deleted after expiring.
187
     *
188
     * @return bool
189
     */
190 3
    protected function doSave(string $ip, array $data, string $type = 'filter', $expire = 0): bool
191
    {
192 2
        switch ($type) {
193

194 3
            case 'rule':
195 3
                $logData = $data;
196 3
                $logData['log_ip'] = $ip;
197 3
                break;
198

199 3
            case 'filter':
200 3
                $logData = [];
201 3
                $logData['log_ip'] = $ip;
202 3
                $logData['log_data'] = $data;
203 3
                break;
204

205 3
            case 'session':
206 3
                $logData = $data;
207 3
                break;
208

209
            // @codeCoverageIgnoreStart
210
            default:
211
                return false;
212
            // @codeCoverageIgnoreEnd
213
        }
214

215 3
        if ($expire > 0) {
216 3
            return $this->redis->setex(
217 3
                $this->getKeyName($ip, $type),
218 2
                $expire,
219 3
                json_encode($logData)
220
            );
221
        }
222

223 3
        return $this->redis->set(
224 3
            $this->getKeyName($ip, $type),
225 3
            json_encode($logData)
226
        );
227
    }
228

229
    /**
230
     * {@inheritDoc}
231
     * 
232
     * @param string $ip   The key name of a redis entry.
233
     * @param string $type The type of the data table.
234
     * 
235
     * @return bool
236
     */
237 3
    protected function doDelete(string $ip, string $type = 'filter'): bool
238
    {
239 3
        if (in_array($type, ['rule', 'filter', 'session'])) {
240 3
            return $this->redis->del($this->getKeyName($ip, $type)) >= 0;
241
        }
242 3
        return false;
243
    }
244

245
    /**
246
     * {@inheritDoc}
247
     * 
248
     * Rebuild data tables.
249
     *
250
     * @return bool
251
     */
252 3
    protected function doRebuild(): bool
253
    {
254 3
        foreach (['rule', 'filter', 'session'] as $type) {
255 3
            $keys = $this->redis->keys($this->getNamespace($type) . ':*');
256

257 3
            if (!empty($keys)) {
258 3
                foreach ($keys as $key) {
259 3
                    $this->redis->del($key);
260
                }
261
            }
262
        }
263 3
        return false;
264
    }
265

266
    /**
267
     * Get key name.
268
     *
269
     * @param string $ip   The key name of a redis entry.
270
     * @param string $type The type of the data table.
271
     *
272
     * @return string
273
     */
274 3
    private function getKeyName(string $ip, string $type = 'filter'): string
275
    {
276
        $table = [
277 3
            'filter'  => $this->tableFilterLogs . ':' . $ip,
278 3
            'session' => $this->tableSessions   . ':' . $ip,
279 3
            'rule'    => $this->tableRuleList   . ':' . $ip,
280
        ];
281
        
282 3
        return $table[$type] ?? '';
283
    }
284

285
    /**
286
     * Get namespace.
287
     *
288
     * @param string $type The type of the data table.
289
     *
290
     * @return string
291
     */
292 3
    private function getNamespace(string $type = 'filter'): string
293
    {
294
        $table = [
295 3
            'filter'  => $this->tableFilterLogs,
296 3
            'session' => $this->tableSessions,
297 3
            'rule'    => $this->tableRuleList,
298
        ];
299

300 3
        return $table[$type] ?? '';
301
    }
302
}

Read our documentation on viewing source code .

Loading