RuleTrait::isRuleExist()   B
last analyzed

Complexity

Conditions 9
Paths 66

Size

Total Lines 76
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 41
CRAP Score 9

Importance

Changes 0
Metric Value
eloc 40
c 0
b 0
f 0
dl 0
loc 76
ccs 41
cts 41
cp 1
rs 7.7244
cc 9
nc 66
nop 0
crap 9

How to fix   Long Method   

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
 * 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\Kernel;
24
25
use Shieldon\Firewall\Kernel\Enum;
26
use function Shieldon\Firewall\__;
27
use function Shieldon\Firewall\get_cpu_usage;
28
use function Shieldon\Firewall\get_memory_usage;
29
use function file_exists;
30
use function file_put_contents;
31
use function filter_var;
32
use function is_writable;
33
use function time;
34
35
/*
36
 * @since 1.0.0
37
 */
38
trait RuleTrait
39
{
40
    /**
41
     * The events.
42
     *
43
     * @var array
44
     */
45
    protected $event = [
46
47
        // Update rule table when this value true.
48
        'update_rule_table' => false,
49
50
        // Send notifications when this value true.
51
        'trigger_messengers' => false,
52
    ];
53
54
    /**
55
     * Set the message body.
56
     *
57
     * @param string $message The message text.
58
     *
59
     * @return void
60
     */
61
    abstract protected function setMessageBody(string $message = ''): void;
62
63
    /**
64
     * Save and return the result identifier.
65
     * This method is for passing value from traits.
66
     *
67
     * @param int $resultCode The result identifier.
68
     *
69
     * @return int
70
     */
71
    abstract protected function setResultCode(int $resultCode): int;
72
73
    /**
74
     * Look up the rule table.
75
     *
76
     * If a specific IP address doesn't exist, return false.
77
     * Otherwise, return true.
78
     *
79
     * @return bool
80
     */
81 69
    protected function isRuleExist()
82
    {
83 69
        $ipRule = $this->driver->get($this->ip, 'rule');
84
85 69
        if (empty($ipRule)) {
86 69
            return false;
87
        }
88
89 29
        $this->reason = $ipRule['reason'];
90
91 29
        $ruleType = (int) $ipRule['type'];
92
93
        // Apply the status code.
94 29
        $this->setResultCode($ruleType);
95
96 29
        if ($ruleType === Enum::ACTION_ALLOW) {
97 4
            return true;
98
        }
99
100
        // Current visitor has been blocked. If he still attempts accessing the site,
101
        // then we can drop him into the permanent block list.
102 25
        $attempts = $ipRule['attempts'] ?? 0;
103 25
        $attempts = (int) $attempts;
104 25
        $now = time();
105 25
        $logData = [];
106 25
        $handleType = 0;
107
108 25
        $logData['log_ip']     = $ipRule['log_ip'];
109 25
        $logData['ip_resolve'] = $ipRule['ip_resolve'];
110 25
        $logData['time']       = $now;
111 25
        $logData['type']       = $ipRule['type'];
112 25
        $logData['reason']     = $ipRule['reason'];
113 25
        $logData['attempts']   = $attempts;
114
115
        // @since 0.2.0
116 25
        $attemptPeriod = $this->properties['record_attempt_detection_period'];
117 25
        $attemptReset  = $this->properties['reset_attempt_counter'];
118
119 25
        $lastTimeDiff = $now - $ipRule['time'];
120
121 25
        if ($lastTimeDiff <= $attemptPeriod) {
122 21
            $logData['attempts'] = ++$attempts;
123
        }
124
125 25
        if ($lastTimeDiff > $attemptReset) {
126 1
            $logData['attempts'] = 0;
127
        }
128
129 25
        if ($ruleType === Enum::ACTION_TEMPORARILY_DENY) {
130 17
            $ratd = $this->determineAttemptsTemporaryDeny($logData, $handleType, $attempts);
131 17
            $logData = $ratd['log_data'];
132 17
            $handleType = $ratd['handle_type'];
133
        }
134
135 25
        if ($ruleType === Enum::ACTION_DENY) {
136 10
            $rapd = $this->determineAttemptsPermanentDeny($logData, $handleType, $attempts);
137 10
            $logData = $rapd['log_data'];
138 10
            $handleType = $rapd['handle_type'];
139
        }
140
141
        // We only update data when `deny_attempt_enable` is enable.
142
        // Because we want to get the last visited time and attempt counter.
143
        // Otherwise, we don't update it everytime to avoid wasting CPU resource.
144 25
        if ($this->event['update_rule_table']) {
145 3
            $this->driver->save($this->ip, $logData, 'rule');
146
        }
147
148
        // Notify this event to messenger.
149 25
        if ($this->event['trigger_messengers']) {
150 1
            $message = $this->prepareMessengerBody($logData, $handleType);
151
152
            // Method from MessageTrait.
153 1
            $this->setMessageBody($message);
154
        }
155
156 25
        return true;
157
    }
158
159
    /**
160
     * Record the attempts when the user is temporarily denied by rule table.
161
     *
162
     * @param array $logData    The log data.
163
     * @param int   $handleType The type for i18n string of the message.
164
     * @param int   $attempts   The attempt times.
165
     *
166
     * @return array
167
     */
168 17
    protected function determineAttemptsTemporaryDeny(array $logData, int $handleType, int $attempts): array
169
    {
170 17
        if ($this->properties['deny_attempt_enable']['data_circle']) {
171 3
            $this->event['update_rule_table'] = true;
172
173 3
            $buffer = $this->properties['deny_attempt_buffer']['data_circle'];
174
175 3
            if ($attempts >= $buffer) {
176
                if ($this->properties['deny_attempt_notify']['data_circle']) {
177 1
                    $this->event['trigger_messengers'] = true;
178 1
                }
179
180
                $logData['type'] = Enum::ACTION_DENY;
181 1
182
                // Reset this value for next checking process - iptables.
183
                $logData['attempts'] = 0;
184 1
                $handleType = 1;
185 1
            }
186
        }
187
188
        return [
189 17
            'log_data' => $logData,
190 17
            'handle_type' => $handleType,
191 17
        ];
192 17
    }
193
194
    /**
195
     * Record the attempts when the user is permanently denied by rule table.
196
     *
197
     * @param array $logData    The log data.
198
     * @param int   $handleType The type for i18n string of the message.
199
     * @param int   $attempts   The attempt times.
200
     *
201
     * @return array
202
     */
203
    protected function determineAttemptsPermanentDeny(array $logData, int $handleType, int $attempts): array
204 10
    {
205
        if ($this->properties['deny_attempt_enable']['system_firewall']) {
206 10
            $this->event['update_rule_table'] = true;
207 1
            // For the requests that are already banned, but they are still attempting access, that means
208
            // that they are programmably accessing your website. Consider put them in the system-layer fireall
209
            // such as IPTABLE.
210
            $bufferIptable = $this->properties['deny_attempt_buffer']['system_firewall'];
211
212 1
            if ($attempts >= $bufferIptable) {
213
                if ($this->properties['deny_attempt_notify']['system_firewall']) {
214 1
                    $this->event['trigger_messengers'] = true;
215
                }
216 1
217 1
                $folder = rtrim($this->properties['iptables_watching_folder'], '/');
218
219
                if (file_exists($folder) && is_writable($folder)) {
220 1
                    $filePath = $folder . '/iptables_queue.log';
221
222 1
                    // command, ipv4/6, ip, subnet, port, protocol, action
223 1
                    // add,4,127.0.0.1,null,all,all,drop  (example)
224
                    // add,4,127.0.0.1,null,80,tcp,drop   (example)
225
                    $command = 'add,4,' . $this->ip . ',null,all,all,deny';
226
227
                    if (filter_var($this->ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
228 1
                        $command = 'add,6,' . $this->ip . ',null,all,allow';
229
                    }
230 1
231 1
                    // Add this IP address to itables_queue.log
232
                    // Use `bin/iptables.sh` for adding it into IPTABLES. See document for more information.
233
                    file_put_contents($filePath, $command . "\n", FILE_APPEND | LOCK_EX);
234
235
                    $logData['attempts'] = 0;
236 1
                    $handleType = 2;
237
                }
238 1
            }
239 1
        }
240
241
        return [
242
            'log_data' => $logData,
243
            'handle_type' => $handleType,
244 10
        ];
245 10
    }
246 10
247 10
    /**
248
     * Prepare the message body for messenger modules to sent.
249
     *
250
     * @param array $logData    The log data.
251
     * @param int   $handleType The type for i18n string of the message.
252
     *
253
     * @return string
254
     */
255
    protected function prepareMessengerBody(array $logData, int $handleType): string
256
    {
257
        // The data strings that will be appended to message body.
258 1
        $prepareMessageData = [
259
            __('core', 'messenger_text_ip')       => $logData['log_ip'],
260
            __('core', 'messenger_text_rdns')     => $logData['ip_resolve'],
261 1
            __('core', 'messenger_text_reason')   => __('core', 'messenger_text_reason_code_' . $logData['reason']),
262 1
            __('core', 'messenger_text_handle')   => __('core', 'messenger_text_handle_type_' . $handleType),
263 1
            __('core', 'messenger_text_system')   => '',
264 1
            __('core', 'messenger_text_cpu')      => get_cpu_usage(),
265 1
            __('core', 'messenger_text_memory')   => get_memory_usage(),
266 1
            __('core', 'messenger_text_time')     => date('Y-m-d H:i:s', $logData['time']),
267 1
            __('core', 'messenger_text_timezone') => date_default_timezone_get(),
268 1
        ];
269 1
270 1
        $message = __('core', 'messenger_notification_subject', 'Notification for {0}', [$this->ip]) . "\n\n";
271 1
272
        foreach ($prepareMessageData as $key => $value) {
273 1
            $message .= $key . ': ' . $value . "\n";
274
        }
275 1
276 1
        return $message;
277
    }
278
}
279