PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 5.5.1
Defender Security – Malware Scanner, Login Security & Firewall v5.5.1
6.3.0 6.2.3 6.2.4 6.2.0 6.2.1 6.2.2 6.1.0 5.3.1 5.4.0 5.4.1 5.5.0 5.5.1 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.7.2 5.8.0 5.8.1 5.9.0 6.0.0 6.0.1 3.0.1 3.1.0 All 141 releases
defender-security / src / model / class-lockout-ip.php

class-lockout-ip.php in Defender Security – Malware Scanner, Login Security & Firewall 5.5.1, at src/model/class-lockout-ip.php

337 lines 8.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Handles interactions with the database table for lockout IPs.
4 *
5 * @package WP_Defender\Model
6 */
7
8 namespace WP_Defender\Model;
9
10 use DateTime;
11 use WP_Defender\DB;
12 use Calotes\Base\Model;
13 use Calotes\Helper\Array_Cache;
14 use WP_Defender\Model\Setting\Blacklist_Lockout;
15
16 /**
17 * Model for the lockout IP table.
18 */
19 class Lockout_Ip extends DB {
20
21 public const STATUS_BLOCKED = 'blocked', STATUS_NORMAL = 'normal';
22
23 /**
24 * Table name.
25 *
26 * @var string
27 */
28 protected $table = 'defender_lockout';
29
30 /**
31 * Primary key column.
32 *
33 * @var int
34 * @defender_property
35 */
36 public $id;
37 /**
38 * Table column for IP address.
39 *
40 * @var string
41 * @defender_property
42 */
43 public $ip;
44 /**
45 * Table column for status.
46 *
47 * @var string
48 * @defender_property
49 */
50 public $status;
51 /**
52 * Table column for lockout message.
53 *
54 * @var string
55 * @defender_property
56 */
57 public $lockout_message;
58 /**
59 * Table column for release time.
60 *
61 * @var int
62 * @defender_property
63 */
64 public $release_time;
65 /**
66 * Table column for lock time.
67 *
68 * @since 3.7.0 Used as timestamp for Login/404 lockouts.
69 * @since 5.4.0 Used as timestamp for Bot Trap lockouts too.
70 *
71 * @var int
72 * @defender_property
73 */
74 public $lock_time;
75 /**
76 * Todo: need to use this column less. The lock_time column is used for Login/404/Bot Trap lockouts.
77 *
78 * @var int
79 * @defender_property
80 */
81 public $lock_time_404;
82 /**
83 * Table column for attempt.
84 *
85 * @var int
86 * @defender_property
87 */
88 public $attempt;
89 /**
90 * Table column for 404 attempt.
91 *
92 * @var int
93 * @defender_property
94 */
95 public $attempt_404;
96 /**
97 * Table column for meta data.
98 * TODO: maybe add a new column for type of login-/404-lockouts and remove lock_time_404 & attempt_404 columns?
99 *
100 * @var array
101 * @defender_property
102 */
103 public $meta = array();
104
105 /**
106 * Get the record by IP, if it not appears, then create one.
107 *
108 * @param string $ip The IP address to search for.
109 * @param null|string $status The status of the record. If 'unban', it will be converted to STATUS_BLOCKED.
110 * If null, it will not filter by status.
111 * @param boolean $all Whether to return all records that match the IP address.
112 * If true, it will return an array of records.
113 * If false, it will return the first record that matches the IP address.
114 *
115 * @return object|null|array The record that matches the IP address.
116 * If $all is true, it will return an array of records.
117 * If no record is found, it will return null.
118 */
119 public static function get( $ip, $status = null, $all = false ) {
120 $model = Array_Cache::get( $ip, 'ip_lockout' );
121 if ( is_object( $model ) ) {
122 return $model;
123 }
124 $orm = self::get_orm();
125 $builder = $orm->get_repository( self::class )
126 ->where( 'ip', $ip );
127 if ( null !== $status ) {
128 $status = 'unban' === $status ? self::STATUS_BLOCKED : self::STATUS_NORMAL;
129 $builder->where( 'status', $status );
130 }
131
132 if ( true === $all ) {
133 return $builder->get();
134 }
135
136 $model = $builder->first();
137
138 if ( ! is_object( $model ) ) {
139 $model = new Lockout_Ip();
140 $model->ip = $ip;
141 $model->attempt = 0;
142 $model->status = self::STATUS_NORMAL;
143 $model->lockout_message = '';
144 $model->release_time = 0;
145 // @since 3.7.0 The lock_time column is used for both lockouts.
146 $model->lock_time = time();
147 $model->lock_time_404 = 0;
148 $model->attempt_404 = 0;
149 $orm->save( $model );
150 }
151
152 Array_Cache::set( $ip, $model, 'ip_lockout' );
153
154 return $model;
155 }
156
157 /**
158 * Get the first IP.
159 *
160 * @param string $ip The IP address to search for.
161 *
162 * @return null|Model
163 */
164 public static function is_blocklisted_ip( $ip ): ?Model {
165 $orm = self::get_orm();
166
167 return $orm->get_repository( self::class )
168 ->select( 'ip,status' )
169 ->where( 'ip', $ip )
170 ->where( 'status', self::STATUS_BLOCKED )
171 ->first();
172 }
173
174 /**
175 * Maybe unblock IP?
176 *
177 * @param string $ip The IP address to search for.
178 *
179 * @return string
180 */
181 public static function get_unlocked_ip_by( $ip ) {
182 $orm = self::get_orm();
183 $model = $orm->get_repository( self::class )
184 ->where( 'ip', $ip )
185 ->first();
186
187 if ( is_object( $model ) ) {
188 $model->status = self::STATUS_NORMAL;
189 $orm->save( $model );
190
191 return $model->ip;
192 }
193
194 return '';
195 }
196
197 /**
198 * Retrieves bulk IPs based on the provided status, IPs, and limit.
199 *
200 * @param string $status The status of the IPs to retrieve.
201 * @param array|null $ips An array of IPs to retrieve. If null, retrieves all IPs with the given status.
202 * @param int|null $limit The maximum number of IPs to retrieve. If null, retrieves all IPs.
203 *
204 * @return array An array of IP models.
205 */
206 public static function get_bulk( string $status, $ips = null, $limit = null ) {
207 $orm = self::get_orm();
208 $builder = $orm->get_repository( self::class );
209 if ( null === $ips ) {
210 $builder->where( 'status', $status );
211 }
212 if ( null !== $ips ) {
213 $builder->where( 'ip', 'in', $ips );
214 }
215 if ( null !== $limit ) {
216 $builder->limit( $limit );
217 }
218
219 return $builder->get();
220 }
221
222 /**
223 * Get the access status of this IP.
224 *
225 * @return array
226 */
227 public function get_access_status(): array {
228 $settings = wd_di()->get( Blacklist_Lockout::class );
229 if (
230 ! in_array( $this->ip, $settings->get_list( 'blocklist' ), true )
231 && ! in_array( $this->ip, $settings->get_list( 'allowlist' ), true )
232 ) {
233 return array( 'na' );
234 }
235
236 $result = array();
237 if ( in_array( $this->ip, $settings->get_list( 'blocklist' ), true ) ) {
238 $result[] = 'banned';
239 }
240 if ( in_array( $this->ip, $settings->get_list( 'allowlist' ), true ) ) {
241 $result[] = 'allowlist';
242 }
243
244 return $result;
245 }
246
247 /**
248 * Returns the text representation of the access status based on the given status code.
249 *
250 * @param string $status The status code to determine the access status text for.
251 * Possible values are: 'banned', 'allowlist', 'na'.
252 *
253 * @return string The text representation of the access status.
254 * Returns an empty string if the status code is not recognized.
255 */
256 public function get_access_status_text( string $status ): string {
257 switch ( $status ) {
258 case 'banned':
259 return esc_html__( 'Banned', 'defender-security' );
260 case 'allowlist':
261 return esc_html__( 'In Allowlist', 'defender-security' );
262 case 'na':
263 return esc_html__( 'Not banned or in allowlist', 'defender-security' );
264 default:
265 return '';
266 }
267 }
268
269 /**
270 * Get locked IPs.
271 *
272 * @return array
273 */
274 public static function query_locked_ip(): array {
275 $orm = self::get_orm();
276 $time = new DateTime( 'now', wp_timezone() );
277
278 return $orm->get_repository( self::class )
279 ->select( 'id,ip,status' )
280 ->where( 'status', self::STATUS_BLOCKED )
281 ->where( 'release_time', '>', $time->getTimestamp() )
282 ->group_by( 'ip' )
283 ->order_by( 'lock_time', 'desc' )
284 ->get_results();
285 }
286
287 /**
288 * Checks if the current object is locked.
289 *
290 * @return bool Returns false if the object is not locked, true otherwise.
291 */
292 public function is_locked(): bool {
293 if ( self::STATUS_BLOCKED === $this->status ) {
294 $time = new DateTime( 'now', wp_timezone() );
295 if ( $this->release_time < $time->getTimestamp() ) {
296 // Unlock it and clear the metadata.
297 $this->attempt = 0;
298 $this->meta = array(
299 'nf' => array(),
300 'login' => array(),
301 );
302 $this->status = self::STATUS_NORMAL;
303 $this->save();
304
305 return false;
306 } else {
307 return true;
308 }
309 }
310
311 return false;
312 }
313
314 /**
315 * Return remaining release time.
316 *
317 * @return int Remaining release time.
318 */
319 public function remaining_release_time(): int {
320 $time = new DateTime( 'now', wp_timezone() );
321
322 return $this->release_time - $time->getTimestamp();
323 }
324
325 /**
326 * Remove all records.
327 *
328 * @return bool|int
329 * @since 3.3.0
330 */
331 public static function truncate() {
332 $orm = self::get_orm();
333
334 return $orm->get_repository( self::class )->truncate();
335 }
336 }
337