PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 3.1.0
Defender Security – Malware Scanner, Login Security & Firewall v3.1.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 / password-protection.php

password-protection.php in Defender Security – Malware Scanner, Login Security & Firewall 3.1.0, at src/component/password-protection.php

423 lines 10.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace WP_Defender\Component;
4
5 use Calotes\Helper\HTTP;
6 use WP_Defender\Component;
7 use WP_User;
8
9 /**
10 * Doing the logic for Pwned Passwords module.
11 *
12 * Class Password_Protection
13 *
14 * @package WP_Defender\Component
15 */
16 class Password_Protection extends Component {
17
18 /**
19 * The Pwned API URL.
20 * API source website: http://haveibeenpwned.com/. API version: v3.
21 *
22 * @var string
23 */
24 protected $pwned_api;
25
26 /**
27 * @var \WP_Defender\Model\Setting\Password_Reset
28 */
29 protected $model;
30
31 /**
32 * Use for cache
33 *
34 * @var \WP_Defender\Model\Setting\Password_Protection
35 */
36 protected $password_protection_model;
37
38 public function __construct() {
39 $this->pwned_api = 'https://api.pwnedpasswords.com/range/';
40 $this->model = wd_di()->get( \WP_Defender\Model\Setting\Password_Reset::class );
41
42 $this->password_protection_model = wd_di()->get(
43 \WP_Defender\Model\Setting\Password_Protection::class
44 );
45 }
46
47 /**
48 * Get the password that was submitted by user.
49 *
50 * @return string
51 */
52 public function get_submitted_password() {
53 $password = '';
54 foreach ( array( 'pwd', 'pass1', 'password', 'edd_user_pass' ) as $key ) {
55 $submitted_pass = HTTP::post( $key );
56 if ( ! empty( $submitted_pass ) ) {
57 $password = $submitted_pass;
58 break;
59 }
60 }
61
62 return $password;
63 }
64
65 /**
66 * Makes an API request to the remote server and checks if the password is pwned.
67 *
68 * @param string $password
69 *
70 * @return \WP_Error|bool
71 */
72 public function check_pwned_password( $password ) {
73 $hash = strtoupper( hash( 'sha1', $password ) );
74 $subhash = substr( $hash, 0, 5 );
75
76 $url = $this->pwned_api . $subhash;
77 $args = array(
78 'method' => 'GET',
79 'headers' => array(
80 'user-agent' => sprintf(
81 'Mozilla/5.0 (compatible; WPMU DEV Defender/%1$s; +https://wpmudev.com)',
82 DEFENDER_VERSION
83 ),
84 ),
85 );
86
87 $request = wp_remote_request( $url, $args );
88
89 if ( is_wp_error( $request ) ) {
90 return $request;
91 }
92
93 $pwned_count = 0;
94 $body = wp_remote_retrieve_body( $request );
95 foreach ( array_map( 'trim', explode( "\n", trim( $body ) ) ) as $row ) {
96 if ( $subhash . substr( strtoupper( $row ), 0, 35 ) === $hash ) {
97 $pwned_count = substr( $row, 36 );
98 break;
99 }
100 }
101 // Found Pwned password or not.
102 return $pwned_count > 0;
103 }
104
105 /**
106 * Check if the specified user role is enabled.
107
108 * @param WP_User $user
109 * @param array $selected_user_roles
110 *
111 * @return bool
112 */
113 public function is_enabled_by_user_role( $user, $selected_user_roles ) {
114 // No for super admin.
115 if ( is_multisite() && is_super_admin( $user->ID ) ) {
116 return false;
117 }
118
119 if ( empty( $user->roles ) ) {
120 // WP_User does have data about roles sometimes, e.g. on the Profile page, so we get it from userdata.
121 $user_id = $user->ID;
122 if ( ! is_multisite() ) {
123 $user_meta = get_userdata( $user_id );
124 if ( empty( $user_meta->roles ) ) {
125 // User should have roles.
126 $this->log( sprintf( "User ID: %d doesn't have roles.", $user_id ), 'password.log' );
127
128 return false;
129 }
130 $user_roles = $user_meta->roles;
131 } else {
132 $arr_user_blogs = get_blogs_of_user( $user_id );
133 if ( empty( $arr_user_blogs ) ) {
134 // User should be associated with some site.
135 $this->log( sprintf( 'User ID: %d is not associated with any site.', $user_id ), 'password.log' );
136
137 return false;
138 }
139 $user_blog_id = array_key_first( $arr_user_blogs );
140 $user = new WP_User( $user_id, '', $user_blog_id );
141 if ( empty( $user->roles ) ) {
142 $this->log( sprintf( "User ID: %d doesn't have roles on MU.", $user->ID ), 'password.log' );
143
144 return false;
145 }
146 $user_roles = $user->roles;
147 }
148 } else {
149 $user_roles = $user->roles;
150 }
151
152 return ! empty( array_intersect( $selected_user_roles, $user_roles ) );
153 }
154
155 /**
156 * Get reset password redirect URL.
157 *
158 * @param WP_User $user
159 *
160 * @return string|null
161 */
162 public function get_reset_password_redirect_url( $user ) {
163 $url = null;
164
165 $key = get_password_reset_key( $user );
166 if ( ! is_wp_error( $key ) ) {
167 $url = add_query_arg(
168 array(
169 'action' => 'rp',
170 'key' => $key,
171 'login' => $user->user_login,
172 ),
173 wp_login_url()
174 );
175 // Extra hosting checks.
176 $this->hosting_compatibility( $key, $user->user_login );
177 }
178
179 return $url;
180 }
181
182 /**
183 * Reset password redirect.
184 *
185 * @param string|null $url
186 * @param bool $safe
187 *
188 * @return void
189 */
190 public function reset_password_redirect( $url, $safe = false ) {
191 if ( empty( $url ) ) {
192 return;
193 }
194 $url = esc_url_raw( $url );
195 header( 'Cache-Control: no-store, no-cache' );
196 $safe ? wp_safe_redirect( $url ) : wp_redirect( $url );
197 exit();
198 }
199
200 /**
201 * Set cookie notice.
202 *
203 * @param string $name
204 * @param string $value
205 * @param int $time
206 */
207 public function set_cookie_notice( $name, $value, $time ) {
208 if ( ! isset( $_COOKIE[ $name ] ) ) {
209 setcookie( $name, $value, $time, '/' );
210 }
211 }
212
213 /**
214 * Remove cookie notice.
215 *
216 * @param string $name
217 * @param int $time
218 */
219 public function remove_cookie_notice( $name, $time ) {
220 if ( isset( $_COOKIE[ $name ] ) ) {
221 setcookie( $name, null, $time, '/' );
222 }
223 $this->remove_extra_cookies();
224 }
225
226 /**
227 * Get the time when the user's password has last been changed.
228 *
229 * @param WP_User|int $user
230 *
231 * @return int
232 */
233 protected function password_last_changed( $user ) {
234 if ( ! $user ) {
235 return 0;
236 }
237
238 $changed = (int) get_user_meta( $user->ID, 'wd_last_password_change', true );
239
240 if ( ! $changed ) {
241 return strtotime( $user->user_registered );
242 }
243
244 return $changed;
245 }
246
247 /**
248 * Is user password expired?
249 *
250 * @param WP_User $user
251 *
252 * @return bool
253 */
254 public function check_expired_password( $user ) {
255
256 return isset( $this->model->force_time ) && $this->model->force_time >= $this->password_last_changed( $user );
257 }
258
259 /**
260 * Set the last updated time when a password is updated.
261 *
262 * @param WP_User $user
263 */
264 public function handle_password_updated( $user ) {
265 update_user_meta( $user->ID, 'wd_last_password_change', time() );
266 }
267
268 /**
269 * @param string $key
270 * @param string $user_login
271 *
272 * @since 2.5.5
273 */
274 protected function hosting_compatibility( $key, $user_login ) {
275 $mask_login = new \WP_Defender\Model\Setting\Mask_Login();
276 if (
277 ! isset( $_COOKIE[ 'wp-resetpass-' . COOKIEHASH ] )
278 && $mask_login->is_active()
279 && 'flywheel' === \WP_Defender\Component\Security_Tweaks\Servers\Server::get_current_server()
280 ) {
281 $value = sprintf( '%s:%s', $user_login, $key );
282 setcookie( 'wp-resetpass-' . COOKIEHASH, $value, 0, $mask_login->get_new_login_url(), COOKIE_DOMAIN, is_ssl(), true );
283 }
284 }
285
286 /**
287 * @since 2.5.5
288 */
289 protected function remove_extra_cookies() {
290 if (
291 isset( $_COOKIE[ 'wp-resetpass-' . COOKIEHASH ] )
292 && 'flywheel' === \WP_Defender\Component\Security_Tweaks\Servers\Server::get_current_server()
293 ) {
294 setcookie( 'wp-resetpass-' . COOKIEHASH, '', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true );
295 }
296 }
297
298 /**
299 * Force reset password.
300 *
301 * @param \WP_User|\WP_Error $user WP_User object or WP_Error.
302 * @param string $password Password plain string.
303 *
304 * @return \WP_User|\WP_Error Return user object or error object.
305 */
306 public function do_force_reset( $user, $password ) {
307 if (
308 ! is_wp_error( $user ) &&
309 wp_check_password( $password, $user->user_pass, $user->ID ) &&
310 $this->is_force_reset( $user )
311 ) {
312 $action = 'password_reset';
313 $cookie_name = 'display_reset_password_warning';
314
315 $this->trigger_redirect( $user, $action, $cookie_name );
316 }
317
318 return $user;
319 }
320
321 /**
322 * Reset weak password.
323 *
324 * @param \WP_User|\WP_Error $user WP_User object or WP_Error.
325 * @param string $password Password plain string.
326 *
327 * @return \WP_User|\WP_Error Return user object or error object.
328 */
329 public function do_weak_reset( $user, $password ) {
330 if (
331 ! is_wp_error( $user ) &&
332 wp_check_password( $password, $user->user_pass, $user->ID ) &&
333 $this->is_weak_password( $user, $password )
334 ) {
335 $action = 'password_protection';
336 $cookie_name = 'display_pwned_password_warning';
337
338 $this->trigger_redirect( $user, $action, $cookie_name );
339 }
340
341 return $user;
342 }
343
344 /**
345 * Redirect to reset password with error message
346 *
347 * @param \WP_User|\WP_Error $user WP_User object or WP_Error.
348 * @param string $action Action query string name.
349 * @param string $cookie_name Cookie name.
350 */
351 private function trigger_redirect( $user, $action, $cookie_name ) {
352 // Set cookie to check and display the warning notice on reset password page.
353 $this->set_cookie_notice(
354 $cookie_name,
355 true,
356 time() + MINUTE_IN_SECONDS * 2
357 );
358 // Get the reset password URL.
359 $url = $this->get_reset_password_redirect_url( $user );
360 /**
361 * Fires before redirecting to the password reset page.
362 *
363 * @since 2.5.6
364 *
365 * @param string $url
366 * @param string $action
367 */
368 do_action( 'wd_forced_reset_password_url', $url, $action );
369 // Redirect to the reset password page.
370 $this->reset_password_redirect( $url );
371 }
372
373 /**
374 * Verify if password is weak.
375 *
376 * Is user role enabled for weak password verification and is the password weak?
377 * If so then return true else password is strong therefore return false.
378 *
379 * @since 2.6.1
380 *
381 * @param WP_User $user WP_User object.
382 * @param string $password Plain password string.
383 *
384 * @return bool If password weak then true else false.
385 */
386 public function is_weak_password( $user, $password ) {
387 $user_roles = $this->password_protection_model->user_roles;
388 $is_enabled_by_user = $this->is_enabled_by_user_role( $user, $user_roles );
389 $is_pwned = $this->check_pwned_password( $password );
390
391 $is_weak_password = $this->password_protection_model->is_active() &&
392 $is_enabled_by_user &&
393 ! is_wp_error( $is_pwned ) &&
394 $is_pwned;
395
396 return $is_weak_password;
397 }
398
399 /**
400 * Verify if password is need to be force reset.
401 *
402 * Is user role enabled for force password reset and is the password expired?
403 * If so then return true else password is non expired therefore return false.
404 *
405 * @since 2.6.1
406 *
407 * @param WP_User $user WP_User object.
408 *
409 * @return bool If password expired then true else false.
410 */
411 public function is_force_reset( $user ) {
412 $user_roles = $this->model->user_roles;
413 $is_enabled_by_user = $this->is_enabled_by_user_role( $user, $user_roles );
414 $is_expired = $this->check_expired_password( $user );
415
416 $is_expired_password = $this->model->is_active() &&
417 $is_enabled_by_user &&
418 $is_expired;
419
420 return $is_expired_password;
421 }
422 }
423