Passed
Push — 2.x ( c85191...0267a9 )
by Terry
02:01
created

FilterTrait::filterFrequency()   B

Complexity

Conditions 11
Paths 2

Size

Total Lines 72
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 11
eloc 37
c 1
b 0
f 0
nc 2
nop 3
dl 0
loc 72
rs 7.3166

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
11
declare(strict_types=1);
12
13
namespace Shieldon\Firewall\Kernel;
14
15
use function Shieldon\Firewall\get_request;
16
use function Shieldon\Firewall\get_session;
17
use function Shieldon\Firewall\unset_superglobal;
18
19
/*
20
 * This trait is used on Kernel only.
21
 */
22
trait FilterTrait
23
{
24
    /**
25
     * Enable or disable the filters.
26
     *
27
     * @var array
28
     */
29
    protected $filterStatus = [
30
        /**
31
         * Check how many pageviews an user made in a short period time.
32
         * For example, limit an user can only view 30 pages in 60 minutes.
33
         */
34
        'frequency' => true,
35
36
        /**
37
         * If an user checks any internal link on your website, the user's
38
         * browser will generate HTTP_REFERER information.
39
         * When a user view many pages without HTTP_REFERER information meaning
40
         * that the user MUST be a web crawler.
41
         */
42
        'referer' => false,
43
44
        /**
45
         * Most of web crawlers do not render JavaScript, they only get the 
46
         * content they want, so we can check whether the cookie can be created
47
         * by JavaScript or not.
48
         */
49
        'cookie' => false,
50
51
        /**
52
         * Every unique user should only has a unique session, but if a user
53
         * creates different sessions every connection... meaning that the 
54
         * user's browser doesn't support cookie.
55
         * It is almost impossible that modern browsers not support cookie,
56
         * therefore the user MUST be a web crawler.
57
         */
58
        'session' => false,
59
    ];
60
61
    /**
62
     * The status for Filters to reset.
63
     *
64
     * @var array
65
     */
66
    protected $filterResetStatus = [
67
        's' => false, // second.
68
        'm' => false, // minute.
69
        'h' => false, // hour.
70
        'd' => false, // day.
71
    ];
72
73
    /**
74
     * Filter - Referer.
75
     *
76
     * @param array $logData   IP data from Shieldon log table.
77
     * @param array $ipData    The IP log data.
78
     * @param bool  $isFlagged Is flagged as unusual behavior or not.
79
     *
80
     * @return array
81
     */
82
    protected function filterReferer(array $logData, array $ipDetail, bool $isFlagged): array
83
    {
84
        $isReject = false;
85
86
        if ($this->filterStatus['referer']) {
87
88
            if ($logData['last_time'] - $ipDetail['last_time'] > $this->properties['interval_check_referer']) {
89
90
                // Get values from data table. We will count it and save it back to data table.
91
                // If an user is already in your website, it is impossible no referer when he views other pages.
92
                $logData['flag_empty_referer'] = $ipDetail['flag_empty_referer'] ?? 0;
93
94
                if (empty(get_request()->getHeaderLine('referer'))) {
95
                    $logData['flag_empty_referer']++;
96
                    $isFlagged = true;
97
                }
98
99
                // Ban this IP if they reached the limit.
100
                if ($logData['flag_empty_referer'] > $this->properties['limit_unusual_behavior']['referer']) {
101
                    $this->action(
0 ignored issues
show
Bug introduced by
It seems like action() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

101
                    $this->/** @scrutinizer ignore-call */ 
102
                           action(
Loading history...
102
                        self::ACTION_TEMPORARILY_DENY,
0 ignored issues
show
Bug introduced by
The constant Shieldon\Firewall\Kernel...ACTION_TEMPORARILY_DENY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
103
                        self::REASON_EMPTY_REFERER
0 ignored issues
show
Bug introduced by
The constant Shieldon\Firewall\Kernel...t::REASON_EMPTY_REFERER was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
104
                    );
105
                    $isReject = true;
106
                }
107
            }
108
        }
109
110
        return [
111
            'is_flagged' => $isFlagged,
112
            'is_reject' => $isReject,
113
            'log_data' => $logData,
114
        ];
115
    }
116
117
    /**
118
     * Filter - Session
119
     *
120
     * @param array $logData   IP data from Shieldon log table.
121
     * @param array $ipData    The IP log data.
122
     * @param bool  $isFlagged Is flagged as unusual behavior or not.
123
     *
124
     * @return array
125
     */
126
    protected function filterSession(array $logData, array $ipDetail, bool $isFlagged): array
127
    {
128
        $isReject = false;
129
130
        if ($this->filterStatus['session']) {
131
132
            if ($logData['last_time'] - $ipDetail['last_time'] > $this->properties['interval_check_session']) {
133
134
                // Get values from data table. We will count it and save it back to data table.
135
                $logData['flag_multi_session'] = $ipDetail['flag_multi_session'] ?? 0;
136
                
137
                if (get_session()->get('id') !== $ipDetail['session']) {
138
139
                    // Is is possible because of direct access by the same user many times.
140
                    // Or they don't have session cookie set.
141
                    $logData['flag_multi_session']++;
142
                    $isFlagged = true;
143
                }
144
145
                // Ban this IP if they reached the limit.
146
                if ($logData['flag_multi_session'] > $this->properties['limit_unusual_behavior']['session']) {
147
                    $this->action(
148
                        self::ACTION_TEMPORARILY_DENY,
0 ignored issues
show
Bug introduced by
The constant Shieldon\Firewall\Kernel...ACTION_TEMPORARILY_DENY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
149
                        self::REASON_TOO_MANY_SESSIONS
0 ignored issues
show
Bug introduced by
The constant Shieldon\Firewall\Kernel...EASON_TOO_MANY_SESSIONS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
150
                    );
151
                    $isReject = true;
152
                }
153
            }
154
        }
155
156
157
        return [
158
            'is_flagged' => $isFlagged,
159
            'is_reject' => $isReject,
160
            'log_data' => $logData,
161
        ];
162
    }
163
164
    /**
165
     * Filter - Cookie
166
     *
167
     * @param array $logData   IP data from Shieldon log table.
168
     * @param array $ipData    The IP log data.
169
     * @param bool  $isFlagged Is flagged as unusual behavior or not.
170
     *
171
     * @return array
172
     */
173
    protected function filterCookie(array $logData, array $ipDetail, bool $isFlagged): array
174
    {
175
        $isReject = false;
176
177
        // Let's checking cookie created by javascript..
178
        if ($this->filterStatus['cookie']) {
179
180
            // Get values from data table. We will count it and save it back to data table.
181
            $logData['flag_js_cookie'] = $ipDetail['flag_js_cookie'] ?? 0;
182
            $logData['pageviews_cookie'] = $ipDetail['pageviews_cookie'] ?? 0;
183
184
            $c = $this->properties['cookie_name'];
185
186
            $jsCookie = get_request()->getCookieParams()[$c] ?? 0;
187
188
            // Checking if a cookie is created by JavaScript.
189
            if (!empty($jsCookie)) {
190
191
                if ($jsCookie == '1') {
192
                    $logData['pageviews_cookie']++;
193
194
                } else {
195
                    // Flag it if the value is not 1.
196
                    $logData['flag_js_cookie']++;
197
                    $isFlagged = true;
198
                }
199
            } else {
200
                // If we cannot find the cookie, flag it.
201
                $logData['flag_js_cookie']++;
202
                $isFlagged = true;
203
            }
204
205
            if ($logData['flag_js_cookie'] > $this->properties['limit_unusual_behavior']['cookie']) {
206
207
                // Ban this IP if they reached the limit.
208
                $this->action(
209
                    self::ACTION_TEMPORARILY_DENY,
0 ignored issues
show
Bug introduced by
The constant Shieldon\Firewall\Kernel...ACTION_TEMPORARILY_DENY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
210
                    self::REASON_EMPTY_JS_COOKIE
0 ignored issues
show
Bug introduced by
The constant Shieldon\Firewall\Kernel...:REASON_EMPTY_JS_COOKIE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
211
                );
212
                $isReject = true;
213
            }
214
215
            // Remove JS cookie and reset.
216
            if ($logData['pageviews_cookie'] > $this->properties['limit_unusual_behavior']['cookie']) {
217
                $logData['pageviews_cookie'] = 0; // Reset to 0.
218
                $logData['flag_js_cookie'] = 0;
219
                unset_superglobal($c, 'cookie');
220
            }
221
        }
222
223
        return [
224
            'is_flagged' => $isFlagged,
225
            'is_reject' => $isReject,
226
            'log_data' => $logData,
227
        ];
228
    }
229
230
    /**
231
     * Filter - Frequency
232
     *
233
     * @param array $logData   IP data from Shieldon log table.
234
     * @param array $ipData    The IP log data.
235
     * @param bool  $isFlagged Is flagged as unusual behavior or not.
236
     *
237
     * @return array
238
     */
239
    protected function filterFrequency(array $logData, array $ipDetail, bool $isFlagged): array
240
    {
241
        $isReject = false;
242
243
        if ($this->filterStatus['frequency']) {
244
            $timeSecond = [];
245
            $timeSecond['s'] = 1;
246
            $timeSecond['m'] = 60;
247
            $timeSecond['h'] = 3600;
248
            $timeSecond['d'] = 86400;
249
250
            foreach (array_keys($this->properties['time_unit_quota']) as $unit) {
251
252
                if (($logData['last_time'] - $ipDetail['first_time_' . $unit]) >= ($timeSecond[$unit] + 1)) {
253
254
                    // For example:
255
                    // (1) minutely: now > first_time_m about 61, (2) hourly: now > first_time_h about 3601, 
256
                    // Let's prepare to rest the the pageview count.
257
                    $this->filterResetStatus[$unit] = true;
258
259
                } else {
260
261
                    // If an user's pageview count is more than the time period limit
262
                    // He or she will get banned.
263
                    if ($logData['pageviews_' . $unit] > $this->properties['time_unit_quota'][$unit]) {
264
265
                        if ($unit === 's') {
266
                            $this->action(
267
                                self::ACTION_TEMPORARILY_DENY,
0 ignored issues
show
Bug introduced by
The constant Shieldon\Firewall\Kernel...ACTION_TEMPORARILY_DENY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
268
                                self::REASON_REACHED_LIMIT_SECOND
0 ignored issues
show
Bug introduced by
The constant Shieldon\Firewall\Kernel...ON_REACHED_LIMIT_SECOND was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
269
                            );
270
                        }
271
272
                        if ($unit === 'm') {
273
                            $this->action(
274
                                self::ACTION_TEMPORARILY_DENY,
275
                                self::REASON_REACHED_LIMIT_MINUTE
0 ignored issues
show
Bug introduced by
The constant Shieldon\Firewall\Kernel...ON_REACHED_LIMIT_MINUTE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
276
                            );
277
                        }
278
279
                        if ($unit === 'h') {
280
                            $this->action(
281
                                self::ACTION_TEMPORARILY_DENY,
282
                                self::REASON_REACHED_LIMIT_HOUR
0 ignored issues
show
Bug introduced by
The constant Shieldon\Firewall\Kernel...ASON_REACHED_LIMIT_HOUR was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
283
                            );
284
                        }
285
286
                        if ($unit === 'd') {
287
                            $this->action(
288
                                self::ACTION_TEMPORARILY_DENY,
289
                                self::REASON_REACHED_LIMIT_DAY
0 ignored issues
show
Bug introduced by
The constant Shieldon\Firewall\Kernel...EASON_REACHED_LIMIT_DAY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
290
                            );
291
                        }
292
293
                        $isReject = true;
294
                    }
295
                }
296
            }
297
298
            foreach ($this->filterResetStatus as $unit => $status) {
299
                // Reset the pageview check for specfic time unit.
300
                if ($status) {
301
                    $logData['first_time_' . $unit] = $logData['last_time'];
302
                    $logData['pageviews_' . $unit] = 0;
303
                }
304
            }
305
        }
306
307
        return [
308
            'is_flagged' => $isFlagged,
309
            'is_reject' => $isReject,
310
            'log_data' => $logData,
311
        ];
312
    }
313
}
314