PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 6.2.0
Defender Security – Malware Scanner, Login Security & Firewall v6.2.0
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 / component / class-unlock-me.php

class-unlock-me.php in Defender Security – Malware Scanner, Login Security & Firewall 6.2.0, at src/component/class-unlock-me.php

247 lines 7.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Handles the functionality for unlocking IP addresses that have been locked out due to security policies.
4 *
5 * @package WP_Defender\Component
6 */
7
8 namespace WP_Defender\Component;
9
10 use Calotes\Helper\HTTP;
11 use WP_Defender\Component;
12 use WP_Defender\Traits\Country;
13 use WP_Defender\Model\Unlockout;
14 use WP_Defender\Model\Lockout_Ip;
15 use WP_Defender\Controller\Firewall;
16
17 /**
18 * Handles the functionality for unlocking IP addresses that have been locked out due to security policies.
19 *
20 * @since 4.6.0
21 */
22 class Unlock_Me extends Component {
23 use Country;
24
25 /**
26 * Time after which the unlock attempt is considered expired.
27 *
28 * @var int
29 */
30 public const EXPIRED_COUNTER_TIME = 5 * MINUTE_IN_SECONDS;
31
32 /**
33 * Slug used for the unlock action.
34 *
35 * @var string
36 */
37 public const SLUG_UNLOCK = 'defender_unlock_me';
38
39 /**
40 * Retrieves the time string for when an unlock attempt expires.
41 *
42 * @return string Time string relative to current time.
43 */
44 public static function get_expired_time(): string {
45 $expired_time = apply_filters( 'wpdef_firewall_unlockout_expired_time', '-30 minutes' );
46 return is_string( $expired_time ) ? $expired_time : (string) $expired_time;
47 }
48
49 /**
50 * This is a receiver to unlock IP(-s) using email.
51 * It makes no difference whether the user is logged in or not.
52 *
53 * @return bool|void
54 */
55 public function maybe_unlock() {
56 // Hash values contains an user email, IP.
57 $hash = HTTP::get( 'hash', '' );
58 $login = HTTP::get( 'login', '' );
59 // Get Unlock ID(-s).
60 $string_uid = HTTP::get( 'uid', '' );
61 if ( '' === $hash || '' === $login || '' === $string_uid ) {
62 return false;
63 }
64
65 $user = get_user_by( 'login', $login );
66 if ( ! $user ) {
67 $this->log(
68 'Unlock Me. Incorrect result. Not found user for UID(-s) ' . $string_uid,
69 Firewall::FIREWALL_LOG
70 );
71
72 return false;
73 }
74
75 $user_email = $user->user_email;
76 if ( ! hash_equals( $hash, hash( 'sha256', $user_email . AUTH_SALT ) ) ) {
77 $this->log( 'Unlock Me. Incorrect result. Invalid hash.', Firewall::FIREWALL_LOG );
78
79 return false;
80 }
81
82 $ips = array();
83 // Get the line of ID or several IDs for multiple lockouts, and change status(-es) in Unlockout table.
84 $limit_time = strtotime( self::get_expired_time() );
85
86 // There are some ID's.
87 $arr_uids = explode( '-', $string_uid );
88 if ( ! is_array( $arr_uids ) ) {
89 $this->log( 'Unlock Me. Incorrect result. Wrong UID(-s).', Firewall::FIREWALL_LOG );
90
91 return false;
92 }
93 foreach ( $arr_uids as $arr_uid ) {
94 $resolved_ip = Unlockout::get_resolved_ip_by( (int) $arr_uid, $user_email, $limit_time );
95 if ( 'expired' === $resolved_ip ) {
96 return false;
97 } elseif ( '' !== $resolved_ip ) {
98 // This is not expired result and no empty one.
99 $ips[] = $resolved_ip;
100 }
101 }
102
103 // All is good. IP's were unblocked.
104 if ( array() === $ips ) {
105 return true;
106 }
107 // Work with IP's.
108 $ips = array_unique( $ips );
109 $first_ip = $ips[0];
110
111 // Remove the user IP's from Local Blocklist.
112 $bl = wd_di()->get( \WP_Defender\Model\Setting\Blacklist_Lockout::class );
113 foreach ( $ips as $ip ) {
114 $bl->remove_from_list( $ip, 'blocklist' );
115 $this->log(
116 'Unlock Me. Success. IP ' . $ip . ' have been unblocked from the blocklist.',
117 Firewall::FIREWALL_LOG
118 );
119 }
120
121 // Remove IP(s) from Active lockouts.
122 if ( count( $ips ) > 1 ) {
123 $models = Lockout_Ip::get_bulk( Lockout_Ip::STATUS_BLOCKED, $ips );
124 foreach ( $models as $model ) {
125 $model->status = Lockout_Ip::STATUS_NORMAL;
126 $model->save();
127 $this->log(
128 'Unlock Me. Success. IP ' . $model->ip . ' have been unblocked from Active lockouts.',
129 Firewall::FIREWALL_LOG
130 );
131 }
132 } else {
133 $ip = Lockout_Ip::get_unlocked_ip_by( $first_ip );
134 if ( '' !== $ip ) {
135 $this->log(
136 'Unlock Me. Success. IP ' . $ip . ' have been unblocked from Active lockouts.',
137 Firewall::FIREWALL_LOG
138 );
139 }
140 }
141
142 // Remove IP(s) from Custom IP Blocklist.
143 $ret = wd_di()->get( IP\Global_IP::class )->remove_from_blocklist( $ips );
144 if ( is_wp_error( $ret ) ) {
145 $this->log(
146 'Unlock Me. Error. IP(s) ' . implode( ',', $ips ) . ' have not been unblocked from Custom IP Blocklist.',
147 Firewall::FIREWALL_LOG
148 );
149 } else {
150 $this->log(
151 'Unlock Me. Success. IP(s) ' . implode( ',', $ips ) . ' have been unblocked from Custom IP Blocklist.',
152 Firewall::FIREWALL_LOG
153 );
154 }
155
156 // Remove the old counter.
157 delete_transient( $this->check_ip_by_remote_addr( $first_ip ) );
158 // Log the successful unlock.
159 $user_agent = defender_get_data_from_request( 'HTTP_USER_AGENT', 's' );
160 $model = wd_di()->get( \WP_Defender\Model\Lockout_Log::class );
161 $model->ip = $first_ip;
162 $model->user_agent = isset( $user_agent ) ? \WP_Defender\Component\User_Agent::fast_cleaning( $user_agent ) : null;
163 $model->date = time();
164 $model->tried = $user ? $user->user_login : '';
165 $model->blog_id = get_current_blog_id();
166 $ip_to_country = $this->ip_to_country( $first_ip );
167 if ( isset( $ip_to_country['iso'] ) ) {
168 $model->country_iso_code = $ip_to_country['iso'];
169 }
170 $model->type = \WP_Defender\Model\Lockout_Log::IP_UNLOCK;
171 /* translators: %s: IP address */
172 $model->log = sprintf( __( '%s was unlocked via "Unlock Me (Email)"', 'defender-security' ), $first_ip );
173 $model->save();
174 // Redirect.
175 wp_safe_redirect( Mask_Login::maybe_masked_login_url() );
176 exit;
177 }
178
179 /**
180 * Display the section if:
181 * 1) no empty IP(-s),
182 * 2) depending on the lockout reason.
183 *
184 * @param string $reason The reason for the lockout.
185 * @param array $ips The IPs involved in the lockout.
186 *
187 * @return bool
188 */
189 public static function is_displayed( string $reason, array $ips ): bool {
190 $excluded_reasons = apply_filters(
191 'wpdef_firewall_unlockout_excluded_reasons',
192 array(
193 'country',
194 'demo',
195 )
196 );
197 $excluded_reasons = is_array( $excluded_reasons ) ? $excluded_reasons : (array) $excluded_reasons;
198 $is_displayed = ! in_array( $reason, $excluded_reasons, true ) && array() !== $ips;
199 $is_displayed = apply_filters( 'wpdef_firewall_unlockout_is_displayed', $is_displayed );
200
201 return is_bool( $is_displayed ) ? $is_displayed : (bool) $is_displayed;
202 }
203
204 /**
205 * Get the limit of failed attempts.
206 *
207 * @return int
208 */
209 public static function get_attempt_limit(): int {
210 $limit = apply_filters( 'wpdef_firewall_unlockout_attempt_limit', 5 );
211 return is_int( $limit ) ? $limit : (int) $limit;
212 }
213
214 /**
215 * Creates a URL for unlocking based on email, user login, and UID array.
216 *
217 * @param string $email The user's email.
218 * @param string $user_login The user's login name.
219 * @param array $arr_uids Array of UIDs.
220 *
221 * @return string The generated URL for unlocking.
222 */
223 public static function create_url( $email, $user_login, $arr_uids ): string {
224 $string_uids = implode( '-', $arr_uids );
225
226 return add_query_arg(
227 array(
228 'action' => self::SLUG_UNLOCK,
229 // No need IP.
230 'hash' => hash( 'sha256', $email . AUTH_SALT ),
231 'login' => $user_login,
232 'uid' => $string_uids,
233 ),
234 network_site_url()
235 );
236 }
237
238 /**
239 * Retrieves the title of the Unlock Me feature.
240 *
241 * @return string The feature title.
242 */
243 public static function get_feature_title(): string {
244 return esc_html__( 'Unlock Me', 'defender-security' );
245 }
246 }
247