Passed
Push — 2.x ( e12ed9...43bb91 )
by Terry
03:35 queued 32s
created

RuleTrait::prepareMessengerBody()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 14
c 1
b 0
f 0
nc 2
nop 2
dl 0
loc 22
rs 9.7998
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
     * Look up the rule table.
32
     *
33
     * If a specific IP address doesn't exist, return false. 
34
     * Otherwise, return true.
35
     *
36
     * @return bool
37
     */
38
    private function DoesRuleExist()
39
    {
40
        $ipRule = $this->driver->get($this->ip, 'rule');
41
42
        if (empty($ipRule)) {
43
            return false;
44
        }
45
46
        $ruleType = (int) $ipRule['type'];
47
48
        // Apply the status code.
49
        $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...
50
51
        if ($ruleType === kernel::ACTION_ALLOW) {
52
            return true;
53
        }
54
55
        // Current visitor has been blocked. If he still attempts accessing the site, 
56
        // then we can drop him into the permanent block list.
57
        $attempts = $ipRule['attempts'] ?? 0;
58
        $attempts = (int) $attempts;
59
        $now = time();
60
        $logData = [];
61
        $handleType = 0;
62
63
        $logData['log_ip']     = $ipRule['log_ip'];
64
        $logData['ip_resolve'] = $ipRule['ip_resolve'];
65
        $logData['time']       = $now;
66
        $logData['type']       = $ipRule['type'];
67
        $logData['reason']     = $ipRule['reason'];
68
        $logData['attempts']   = $attempts;
69
70
        // @since 0.2.0
71
        $attemptPeriod = $this->properties['record_attempt_detection_period'];
72
        $attemptReset  = $this->properties['reset_attempt_counter'];
73
74
        $lastTimeDiff = $now - $ipRule['time'];
75
76
        if ($lastTimeDiff <= $attemptPeriod) {
77
            $logData['attempts'] = ++$attempts;
78
        }
79
80
        if ($lastTimeDiff > $attemptReset) {
81
            $logData['attempts'] = 0;
82
        }
83
84
        if ($ruleType === kernel::ACTION_TEMPORARILY_DENY) {
85
            $ratd = $this->determineAttemptsTemporaryDeny($logData, $handleType, $attempts);
86
            $logData = $ratd['log_data'];
87
            $handleType = $ratd['handle_type'];
88
        }
89
90
        if ($ruleType === kernel::ACTION_DENY) {
91
            $rapd = $this->determineAttemptsPermanentDeny($logData, $handleType, $attempts);
92
            $logData = $rapd['log_data'];
93
            $handleType = $rapd['handle_type'];
94
        }
95
96
        // We only update data when `deny_attempt_enable` is enable.
97
        // Because we want to get the last visited time and attempt counter.
98
        // Otherwise, we don't update it everytime to avoid wasting CPU resource.
99
        if ($this->event['update_rule_table']) {
100
            $this->driver->save($this->ip, $logData, 'rule');
101
        }
102
103
        // Notify this event to messenger.
104
        if ($this->event['trigger_messengers']) {
105
            $this->prepareMessengerBody($logData, $handleType);
106
        }
107
108
        return true;
109
    }
110
111
    /**
112
     * Record the attempts when the user is temporarily denied by rule table.
113
     *
114
     * @param array $logData
115
     * @param int   $handleType
116
     * @param int   $attempts
117
     * 
118
     * @return array
119
     */
120
    private function determineAttemptsTemporaryDeny(array $logData, int $handleType, int $attempts): array
121
    {
122
        if ($this->properties['deny_attempt_enable']['data_circle']) {
123
            $this->event['update_rule_table'] = true;
0 ignored issues
show
Bug Best Practice introduced by
The property event does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
124
125
            $buffer = $this->properties['deny_attempt_buffer']['data_circle'];
126
127
            if ($attempts >= $buffer) {
128
129
                if ($this->properties['deny_attempt_notify']['data_circle']) {
130
                    $this->event['trigger_messengers'] = true;
131
                }
132
133
                $logData['type'] = kernel::ACTION_DENY;
134
135
                // Reset this value for next checking process - iptables.
136
                $logData['attempts'] = 0;
137
                $handleType = 1;
138
            }
139
        }
140
141
        return [
142
            'log_data' => $logData,
143
            'handle_type' => $handleType,
144
        ];
145
    }
146
147
    /**
148
     * Record the attempts when the user is permanently denied by rule table.
149
     *
150
     * @param array $logData
151
     * @param int   $handleType
152
     * @param int   $attempts
153
     * 
154
     * @return array
155
     */
156
    private function determineAttemptsPermanentDeny(array $logData, int $handleType, int $attempts): array
157
    {
158
        if ($this->properties['deny_attempt_enable']['system_firewall']) {
159
            $this->event['update_rule_table'] = true;
0 ignored issues
show
Bug Best Practice introduced by
The property event does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
160
161
            // For the requests that are already banned, but they are still attempting access, that means 
162
            // that they are programmably accessing your website. Consider put them in the system-layer fireall
163
            // such as IPTABLE.
164
            $bufferIptable = $this->properties['deny_attempt_buffer']['system_firewall'];
165
166
            if ($attempts >= $bufferIptable) {
167
168
                if ($this->properties['deny_attempt_notify']['system_firewall']) {
169
                    $this->event['trigger_messengers'] = true;
170
                }
171
172
                $folder = rtrim($this->properties['iptables_watching_folder'], '/');
173
174
                if (file_exists($folder) && is_writable($folder)) {
175
                    $filePath = $folder . '/iptables_queue.log';
176
177
                    // command, ipv4/6, ip, subnet, port, protocol, action
178
                    // add,4,127.0.0.1,null,all,all,drop  (example)
179
                    // add,4,127.0.0.1,null,80,tcp,drop   (example)
180
                    $command = 'add,4,' . $this->ip . ',null,all,all,deny';
181
182
                    if (filter_var($this->ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
183
                        $command = 'add,6,' . $this->ip . ',null,all,allow';
184
                    }
185
186
                    // Add this IP address to itables_queue.log
187
                    // Use `bin/iptables.sh` for adding it into IPTABLES. See document for more information. 
188
                    file_put_contents($filePath, $command . "\n", FILE_APPEND | LOCK_EX);
189
190
                    $logData['attempts'] = 0;
191
                    $handleType = 2;
192
                }
193
            }
194
        }
195
196
        return [
197
            'log_data' => $logData,
198
            'handle_type' => $handleType,
199
        ];
200
    }
201
202
    /**
203
     * Prepare the message body for messenger modules to sent.
204
     *
205
     * @param array $logData
206
     * @param int   $handleType
207
     * 
208
     * @return void
209
     */
210
    private function prepareMessengerBody(array $logData, int $handleType): void
211
    {
212
        // The data strings that will be appended to message body.
213
        $prepareMessageData = [
214
            __('core', 'messenger_text_ip')       => $logData['log_ip'],
215
            __('core', 'messenger_text_rdns')     => $logData['ip_resolve'],
216
            __('core', 'messenger_text_reason')   => __('core', 'messenger_text_reason_code_' . $logData['reason']),
217
            __('core', 'messenger_text_handle')   => __('core', 'messenger_text_handle_type_' . $handleType),
218
            __('core', 'messenger_text_system')   => '',
219
            __('core', 'messenger_text_cpu')      => get_cpu_usage(),
220
            __('core', 'messenger_text_memory')   => get_memory_usage(),
221
            __('core', 'messenger_text_time')     => date('Y-m-d H:i:s', $logData['time']),
222
            __('core', 'messenger_text_timezone') => date_default_timezone_get(),
223
        ];
224
225
        $message = __('core', 'messenger_notification_subject', 'Notification for {0}', [$this->ip]) . "\n\n";
226
227
        foreach ($prepareMessageData as $key => $value) {
228
            $message .= $key . ': ' . $value . "\n";
229
        }
230
231
        $this->msgBody = $message;
0 ignored issues
show
Bug Best Practice introduced by
The property msgBody does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
232
    }
233
}
234