Passed
Push — 2.x ( 96150f...1cf170 )
by Terry
02:22
created

RedisDriver::doFetch()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 23
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 12
nc 4
nop 2
dl 0
loc 23
rs 9.8666
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file is part of the Shieldon package.
4
 *
5
 * (c) Terry L. <[email protected]>
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 <[email protected]>
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_array;
28
use function is_bool;
29
use function json_decode;
30
use function json_encode;
31
use function ksort;
32
33
/**
34
 * Redis Driver.
35
 */
36
class RedisDriver extends DriverProvider
37
{
38
    /**
39
     * Redis instance.
40
     *
41
     * @var object|null
42
     */
43
    protected $redis;
44
45
    /**
46
     * Constructor.
47
     *
48
     * @param Redis $redis The Redis instance.
49
     * 
50
     * @return void
51
     */
52
    public function __construct(Redis $redis)
53
    {
54
        $this->redis = $redis;
55
    }
56
57
    /**
58
     * Set data channel.
59
     *
60
     * @param string $channel The prefix of data tables.
61
     *
62
     * @return void
63
     */
64
    public function setChannel(string $channel): void
65
    {
66
        $this->channel = $channel;
67
68
        if (!empty($this->channel)) {
69
            $this->tableFilterLogs = $this->channel . ':shieldon_filter_logs';
70
            $this->tableRuleList   = $this->channel . ':shieldon_rule_list';
71
            $this->tableSessions   = $this->channel . ':shieldon_sessions';
72
        }
73
    }
74
75
    /**
76
     * Initialize data tables.
77
     *
78
     * @param bool $dbCheck This is for creating data tables automatically
79
     *                      Turn it off, if you don't want to check data tables every pageview.
80
     *
81
     * @return void
82
     */
83
    protected function doInitialize(bool $dbCheck = true): void
84
    {
85
        $this->isInitialized = true;
86
    }
87
88
    /**
89
     * {@inheritDoc}
90
     * 
91
     * @param string $type The type of the data table.
92
     * 
93
     * @return bool
94
     */
95
    protected function doFetchAll(string $type = 'filter'): array
96
    {
97
        $results = [];
98
99
        $this->assertInvalidDataTable($type);
100
101
        $keys = $this->redis->keys($this->getNamespace($type) . ':*');
102
103
        foreach ($keys as $key) {
104
            $content = $this->redis->get($key);
105
            $content = json_decode($content, true);
106
107
            if ($type === 'session') {
108
                $sort = $content['microtimesamp'] . '.' . $content['id']; 
109
            } else {
110
                $sort = $content['log_ip'];
111
            }
112
113
            $results[$sort] = $content;   
114
        }
115
116
        // Sort by ascending timesamp (microtimesamp).
117
        ksort($results);
118
119
        return $results;
120
    }
121
122
    /**
123
     * {@inheritDoc}
124
     * 
125
     * @param string $ip   The data id of the entry to fetch.
126
     * @param string $type The type of the data table.
127
     * 
128
     * @return array
129
     */
130
    protected function doFetch(string $ip, string $type = 'filter'): array
131
    {
132
        $results = [];
133
134
        if (!$this->checkExist($ip, $type)) {
135
            return $results;
136
        }
137
138
        if ($type === 'filter') {
139
            $content = $this->redis->get($this->getKeyName($ip, $type));
140
            $resultData = json_decode($content, true);
141
142
            if (!empty($resultData['log_data'])) {
143
                $results = $resultData['log_data']; 
144
            }
145
146
        } else {
147
            // type: rule | session
148
            $content = $this->redis->get($this->getKeyName($ip, $type));
149
            $results = json_decode($content, true);
150
        }
151
152
        return $results;
153
    }
154
155
    /**
156
     * {@inheritDoc}
157
     *
158
     * @param string $ip   The data id of the entry to check for.
159
     * @param string $type The type of the data table.
160
     *
161
     * @return bool
162
     */
163
    protected function checkExist(string $ip, string $type = 'filter'): bool
164
    {
165
        $isExist = $this->redis->exists($this->getKeyName($ip, $type));
166
167
        // This function took a single argument and returned TRUE or FALSE in phpredis versions < 4.0.0.
168
169
        // @codeCoverageIgnoreStart
170
        if (is_bool($isExist)) {
171
            return $isExist;
172
        }
173
174
        return $isExist > 0;
175
        // @codeCoverageIgnoreEnd
176
    }
177
178
    /**
179
     * {@inheritDoc}
180
     * 
181
     * @param string $ip     The data id.
182
     * @param array  $data   The data.
183
     * @param string $type   The type of the data table.
184
     * @param int    $expire The data will be deleted after expiring.
185
     *
186
     * @return bool
187
     */
188
    protected function doSave(string $ip, array $data, string $type = 'filter', $expire = 0): bool
189
    {
190
        switch ($type) {
191
192
            case 'rule':
193
                $logData = $data;
194
                $logData['log_ip'] = $ip;
195
                break;
196
197
            case 'filter':
198
                $logData = [];
199
                $logData['log_ip'] = $ip;
200
                $logData['log_data'] = $data;
201
                break;
202
203
            case 'session':
204
                $logData = $data;
205
                break;
206
207
            // @codeCoverageIgnoreStart
208
            default:
209
                return false;
210
            // @codeCoverageIgnoreEnd
211
        }
212
213
        if ($expire > 0) {
214
            return $this->redis->setex(
215
                $this->getKeyName($ip, $type),
216
                $expire,
217
                json_encode($logData)
218
            );
219
        }
220
221
        return $this->redis->set(
222
            $this->getKeyName($ip, $type),
223
            json_encode($logData)
224
        );
225
    }
226
227
    /**
228
     * {@inheritDoc}
229
     * 
230
     * @param string $ip   The key name of a redis entry.
231
     * @param string $type The type of the data table.
232
     * 
233
     * @return bool
234
     */
235
    protected function doDelete(string $ip, string $type = 'filter'): bool
236
    {
237
        if (in_array($type, ['rule', 'filter', 'session'])) {
238
            return $this->redis->del($this->getKeyName($ip, $type)) >= 0;
239
        }
240
        return false;
241
    }
242
243
    /**
244
     * {@inheritDoc}
245
     * 
246
     * Rebuild data tables.
247
     *
248
     * @return bool
249
     */
250
    protected function doRebuild(): bool
251
    {
252
        foreach (['rule', 'filter', 'session'] as $type) {
253
            $keys = $this->redis->keys($this->getNamespace($type) . ':*');
254
255
            if (!empty($keys)) {
256
                foreach ($keys as $key) {
257
                    $this->redis->del($key);
258
                }
259
            }
260
        }
261
        return false;
262
    }
263
264
    /**
265
     * Get key name.
266
     *
267
     * @param string $ip   The key name of a redis entry.
268
     * @param string $type The type of the data table.
269
     *
270
     * @return string
271
     */
272
    private function getKeyName(string $ip, string $type = 'filter'): string
273
    {
274
        $table = [
275
            'filter'  => $this->tableFilterLogs . ':' . $ip,
276
            'session' => $this->tableSessions   . ':' . $ip,
277
            'rule'    => $this->tableRuleList   . ':' . $ip,
278
        ];
279
        
280
        return $table[$type] ?? '';
281
    }
282
283
    /**
284
     * Get namespace.
285
     *
286
     * @param string $type The type of the data table.
287
     *
288
     * @return string
289
     */
290
    private function getNamespace(string $type = 'filter'): string
291
    {
292
        $table = [
293
            'filter'  => $this->tableFilterLogs,
294
            'session' => $this->tableSessions,
295
            'rule'    => $this->tableRuleList,
296
        ];
297
298
        return $table[$type] ?? '';
299
    }
300
}
301