Passed
Push — 2.x ( 44da33...f0bfd9 )
by Terry
01:53
created

RuleTrait   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 221
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 86
c 3
b 0
f 0
dl 0
loc 221
rs 10
wmc 22

4 Methods

Rating   Name   Duplication   Size   Complexity  
A prepareMessengerBody() 0 22 2
B determineAttemptsPermanentDeny() 0 43 7
A determineAttemptsTemporaryDeny() 0 24 4
B isRuleExist() 0 74 9
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 Shieldon\Firewall\Kernel;
16
use function Shieldon\Firewall\__;
17
use function Shieldon\Firewall\get_cpu_usage;
18
use function Shieldon\Firewall\get_memory_usage;
19
use function file_exists;
20
use function file_put_contents;
21
use function filter_var;
22
use function is_writable;
23
use function time;
24
25
/*
26
 * @since 1.0.0
27
 */
28
trait RuleTrait
29
{
30
    /**
31
     * The events.
32
     *
33
     * @var array
34
     */
35
    protected $event = [
36
37
        // Update rule table when this value true.
38
        'update_rule_table' => false,
39
40
        // Send notifications when this value true.
41
        'trigger_messengers' => false,
42
    ];
43
44
    /**
45
     * Look up the rule table.
46
     *
47
     * If a specific IP address doesn't exist, return false. 
48
     * Otherwise, return true.
49
     *
50
     * @return bool
51
     */
52
    protected function isRuleExist()
53
    {
54
        $ipRule = $this->driver->get($this->ip, 'rule');
55
56
        if (empty($ipRule)) {
57
            return false;
58
        }
59
60
        $ruleType = (int) $ipRule['type'];
61
62
        // Apply the status code.
63
        $this->result = $ruleType;
0 ignored issues
show
Bug Best Practice introduced by
The property result does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
64
65
        if ($ruleType === kernel::ACTION_ALLOW) {
66
            return true;
67
        }
68
69
        // Current visitor has been blocked. If he still attempts accessing the site, 
70
        // then we can drop him into the permanent block list.
71
        $attempts = $ipRule['attempts'] ?? 0;
72
        $attempts = (int) $attempts;
73
        $now = time();
74
        $logData = [];
75
        $handleType = 0;
76
77
        $logData['log_ip']     = $ipRule['log_ip'];
78
        $logData['ip_resolve'] = $ipRule['ip_resolve'];
79
        $logData['time']       = $now;
80
        $logData['type']       = $ipRule['type'];
81
        $logData['reason']     = $ipRule['reason'];
82
        $logData['attempts']   = $attempts;
83
84
        // @since 0.2.0
85
        $attemptPeriod = $this->properties['record_attempt_detection_period'];
86
        $attemptReset  = $this->properties['reset_attempt_counter'];
87
88
        $lastTimeDiff = $now - $ipRule['time'];
89
90
        if ($lastTimeDiff <= $attemptPeriod) {
91
            $logData['attempts'] = ++$attempts;
92
        }
93
94
        if ($lastTimeDiff > $attemptReset) {
95
            $logData['attempts'] = 0;
96
        }
97
98
        if ($ruleType === kernel::ACTION_TEMPORARILY_DENY) {
99
            $ratd = $this->determineAttemptsTemporaryDeny($logData, $handleType, $attempts);
100
            $logData = $ratd['log_data'];
101
            $handleType = $ratd['handle_type'];
102
        }
103
104
        if ($ruleType === kernel::ACTION_DENY) {
105
            $rapd = $this->determineAttemptsPermanentDeny($logData, $handleType, $attempts);
106
            $logData = $rapd['log_data'];
107
            $handleType = $rapd['handle_type'];
108
        }
109
110
        // We only update data when `deny_attempt_enable` is enable.
111
        // Because we want to get the last visited time and attempt counter.
112
        // Otherwise, we don't update it everytime to avoid wasting CPU resource.
113
        if ($this->event['update_rule_table']) {
114
            $this->driver->save($this->ip, $logData, 'rule');
115
        }
116
117
        // Notify this event to messenger.
118
        if ($this->event['trigger_messengers']) {
119
            $message = $this->prepareMessengerBody($logData, $handleType);
120
121
            // Method from MessageTrait.
122
            $this->setMessageBody($message);
0 ignored issues
show
Bug introduced by
It seems like setMessageBody() 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

122
            $this->/** @scrutinizer ignore-call */ 
123
                   setMessageBody($message);
Loading history...
123
        }
124
125
        return true;
126
    }
127
128
    /**
129
     * Record the attempts when the user is temporarily denied by rule table.
130
     *
131
     * @param array $logData
132
     * @param int   $handleType
133
     * @param int   $attempts
134
     * 
135
     * @return array
136
     */
137
    protected function determineAttemptsTemporaryDeny(array $logData, int $handleType, int $attempts): array
138
    {
139
        if ($this->properties['deny_attempt_enable']['data_circle']) {
140
            $this->event['update_rule_table'] = true;
141
142
            $buffer = $this->properties['deny_attempt_buffer']['data_circle'];
143
144
            if ($attempts >= $buffer) {
145
146
                if ($this->properties['deny_attempt_notify']['data_circle']) {
147
                    $this->event['trigger_messengers'] = true;
148
                }
149
150
                $logData['type'] = kernel::ACTION_DENY;
151
152
                // Reset this value for next checking process - iptables.
153
                $logData['attempts'] = 0;
154
                $handleType = 1;
155
            }
156
        }
157
158
        return [
159
            'log_data' => $logData,
160
            'handle_type' => $handleType,
161
        ];
162
    }
163
164
    /**
165
     * Record the attempts when the user is permanently denied by rule table.
166
     *
167
     * @param array $logData
168
     * @param int   $handleType
169
     * @param int   $attempts
170
     * 
171
     * @return array
172
     */
173
    protected function determineAttemptsPermanentDeny(array $logData, int $handleType, int $attempts): array
174
    {
175
        if ($this->properties['deny_attempt_enable']['system_firewall']) {
176
            $this->event['update_rule_table'] = true;
177
178
            // For the requests that are already banned, but they are still attempting access, that means 
179
            // that they are programmably accessing your website. Consider put them in the system-layer fireall
180
            // such as IPTABLE.
181
            $bufferIptable = $this->properties['deny_attempt_buffer']['system_firewall'];
182
183
            if ($attempts >= $bufferIptable) {
184
185
                if ($this->properties['deny_attempt_notify']['system_firewall']) {
186
                    $this->event['trigger_messengers'] = true;
187
                }
188
189
                $folder = rtrim($this->properties['iptables_watching_folder'], '/');
190
191
                if (file_exists($folder) && is_writable($folder)) {
192
                    $filePath = $folder . '/iptables_queue.log';
193
194
                    // command, ipv4/6, ip, subnet, port, protocol, action
195
                    // add,4,127.0.0.1,null,all,all,drop  (example)
196
                    // add,4,127.0.0.1,null,80,tcp,drop   (example)
197
                    $command = 'add,4,' . $this->ip . ',null,all,all,deny';
198
199
                    if (filter_var($this->ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
200
                        $command = 'add,6,' . $this->ip . ',null,all,allow';
201
                    }
202
203
                    // Add this IP address to itables_queue.log
204
                    // Use `bin/iptables.sh` for adding it into IPTABLES. See document for more information. 
205
                    file_put_contents($filePath, $command . "\n", FILE_APPEND | LOCK_EX);
206
207
                    $logData['attempts'] = 0;
208
                    $handleType = 2;
209
                }
210
            }
211
        }
212
213
        return [
214
            'log_data' => $logData,
215
            'handle_type' => $handleType,
216
        ];
217
    }
218
219
    /**
220
     * Prepare the message body for messenger modules to sent.
221
     *
222
     * @param array $logData
223
     * @param int   $handleType
224
     * 
225
     * @return string
226
     */
227
    protected function prepareMessengerBody(array $logData, int $handleType): string
228
    {
229
        // The data strings that will be appended to message body.
230
        $prepareMessageData = [
231
            __('core', 'messenger_text_ip')       => $logData['log_ip'],
232
            __('core', 'messenger_text_rdns')     => $logData['ip_resolve'],
233
            __('core', 'messenger_text_reason')   => __('core', 'messenger_text_reason_code_' . $logData['reason']),
234
            __('core', 'messenger_text_handle')   => __('core', 'messenger_text_handle_type_' . $handleType),
235
            __('core', 'messenger_text_system')   => '',
236
            __('core', 'messenger_text_cpu')      => get_cpu_usage(),
237
            __('core', 'messenger_text_memory')   => get_memory_usage(),
238
            __('core', 'messenger_text_time')     => date('Y-m-d H:i:s', $logData['time']),
239
            __('core', 'messenger_text_timezone') => date_default_timezone_get(),
240
        ];
241
242
        $message = __('core', 'messenger_notification_subject', 'Notification for {0}', [$this->ip]) . "\n\n";
243
244
        foreach ($prepareMessageData as $key => $value) {
245
            $message .= $key . ': ' . $value . "\n";
246
        }
247
248
        return $message;
249
    }
250
}
251