Passed
Push — 2.x ( 0267a9...364dd8 )
by Terry
02:05
created

RuleTrait::determineAttemptsPermanentDeny()   B

Complexity

Conditions 7
Paths 8

Size

Total Lines 43
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 18
c 1
b 0
f 0
nc 8
nop 3
dl 0
loc 43
rs 8.8333
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 file_exists;
16
use function file_put_contents;
17
use function filter_var;
18
use function is_writable;
19
use function time;
20
21
/*
22
 * @since 1.0.0
23
 */
24
trait RuleTrait
25
{
26
    /**
27
     * Look up the rule table.
28
     *
29
     * If a specific IP address doesn't exist, return false. 
30
     * Otherwise, return true.
31
     *
32
     * @return bool
33
     */
34
    private function DoesRuleExist()
35
    {
36
        $ipRule = $this->driver->get($this->ip, 'rule');
37
38
        if (empty($ipRule)) {
39
            return false;
40
        }
41
42
        $ruleType = (int) $ipRule['type'];
43
44
        // Apply the status code.
45
        $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...
46
47
        if ($ruleType === self::ACTION_ALLOW) {
0 ignored issues
show
Bug introduced by
The constant Shieldon\Firewall\Kernel\RuleTrait::ACTION_ALLOW was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
48
            return true;
49
        }
50
51
        // Current visitor has been blocked. If he still attempts accessing the site, 
52
        // then we can drop him into the permanent block list.
53
        $attempts = $ipRule['attempts'] ?? 0;
54
        $attempts = (int) $attempts;
55
        $now = time();
56
        $logData = [];
57
        $handleType = 0;
58
59
        $logData['log_ip']     = $ipRule['log_ip'];
60
        $logData['ip_resolve'] = $ipRule['ip_resolve'];
61
        $logData['time']       = $now;
62
        $logData['type']       = $ipRule['type'];
63
        $logData['reason']     = $ipRule['reason'];
64
        $logData['attempts']   = $attempts;
65
66
        // @since 0.2.0
67
        $attemptPeriod = $this->properties['record_attempt_detection_period'];
68
        $attemptReset  = $this->properties['reset_attempt_counter'];
69
70
        $lastTimeDiff = $now - $ipRule['time'];
71
72
        if ($lastTimeDiff <= $attemptPeriod) {
73
            $logData['attempts'] = ++$attempts;
74
        }
75
76
        if ($lastTimeDiff > $attemptReset) {
77
            $logData['attempts'] = 0;
78
        }
79
80
        if ($ruleType === 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...
81
            $ratd = $this->determineAttemptsTemporaryDeny($logData, $handleType, $attempts);
82
            $logData = $ratd['log_data'];
83
            $handleType = $ratd['handle_type'];
84
        }
85
86
        if ($ruleType === self::ACTION_DENY) {
0 ignored issues
show
Bug introduced by
The constant Shieldon\Firewall\Kernel\RuleTrait::ACTION_DENY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
87
            $rapd = $this->determineAttemptsPermanentDeny($logData, $handleType, $attempts);
88
            $logData = $rapd['log_data'];
89
            $handleType = $rapd['handle_type'];
90
        }
91
92
        // We only update data when `deny_attempt_enable` is enable.
93
        // Because we want to get the last visited time and attempt counter.
94
        // Otherwise, we don't update it everytime to avoid wasting CPU resource.
95
        if ($this->event['update_rule_table']) {
96
            $this->driver->save($this->ip, $logData, 'rule');
97
        }
98
99
        // Notify this event to messenger.
100
        if ($this->event['trigger_messengers']) {
101
            $this->prepareMessengerBody($logData, $handleType);
0 ignored issues
show
Bug introduced by
It seems like prepareMessengerBody() 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
                   prepareMessengerBody($logData, $handleType);
Loading history...
102
        }
103
104
        return true;
105
    }
106
107
    /**
108
     * Record the attempts when the user is temporarily denied by rule table.
109
     *
110
     * @param array $logData
111
     * @param int   $handleType
112
     * @param int   $attempts
113
     * 
114
     * @return array
115
     */
116
    private function determineAttemptsTemporaryDeny(array $logData, int $handleType, int $attempts): array
117
    {
118
        if ($this->properties['deny_attempt_enable']['data_circle']) {
119
            $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...
120
121
            $buffer = $this->properties['deny_attempt_buffer']['data_circle'];
122
123
            if ($attempts >= $buffer) {
124
125
                if ($this->properties['deny_attempt_notify']['data_circle']) {
126
                    $this->event['trigger_messengers'] = true;
127
                }
128
129
                $logData['type'] = self::ACTION_DENY;
0 ignored issues
show
Bug introduced by
The constant Shieldon\Firewall\Kernel\RuleTrait::ACTION_DENY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
130
131
                // Reset this value for next checking process - iptables.
132
                $logData['attempts'] = 0;
133
                $handleType = 1;
134
            }
135
        }
136
137
        return [
138
            'log_data' => $logData,
139
            'handle_type' => $handleType,
140
        ];
141
    }
142
143
    /**
144
     * Record the attempts when the user is permanently denied by rule table.
145
     *
146
     * @param array $logData
147
     * @param int   $handleType
148
     * @param int   $attempts
149
     * 
150
     * @return array
151
     */
152
    private function determineAttemptsPermanentDeny(array $logData, int $handleType, int $attempts): array
153
    {
154
        if ($this->properties['deny_attempt_enable']['system_firewall']) {
155
            $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...
156
157
            // For the requests that are already banned, but they are still attempting access, that means 
158
            // that they are programmably accessing your website. Consider put them in the system-layer fireall
159
            // such as IPTABLE.
160
            $bufferIptable = $this->properties['deny_attempt_buffer']['system_firewall'];
161
162
            if ($attempts >= $bufferIptable) {
163
164
                if ($this->properties['deny_attempt_notify']['system_firewall']) {
165
                    $this->event['trigger_messengers'] = true;
166
                }
167
168
                $folder = rtrim($this->properties['iptables_watching_folder'], '/');
169
170
                if (file_exists($folder) && is_writable($folder)) {
171
                    $filePath = $folder . '/iptables_queue.log';
172
173
                    // command, ipv4/6, ip, subnet, port, protocol, action
174
                    // add,4,127.0.0.1,null,all,all,drop  (example)
175
                    // add,4,127.0.0.1,null,80,tcp,drop   (example)
176
                    $command = 'add,4,' . $this->ip . ',null,all,all,deny';
177
178
                    if (filter_var($this->ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
179
                        $command = 'add,6,' . $this->ip . ',null,all,allow';
180
                    }
181
182
                    // Add this IP address to itables_queue.log
183
                    // Use `bin/iptables.sh` for adding it into IPTABLES. See document for more information. 
184
                    file_put_contents($filePath, $command . "\n", FILE_APPEND | LOCK_EX);
185
186
                    $logData['attempts'] = 0;
187
                    $handleType = 2;
188
                }
189
            }
190
        }
191
192
        return [
193
            'log_data' => $logData,
194
            'handle_type' => $handleType,
195
        ];
196
    }
197
}
198