PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 5.6.0
Defender Security – Malware Scanner, Login Security & Firewall v5.6.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 5.6.0, at src/component/class-unlock-me.php

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