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

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

409 lines 10.1 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/
21 * API version: v3
22 * @var string
23 */
24 protected $pwned_api;
25 /**
26 * @var \WP_Defender\Model\Setting\Password_Reset
27 */
28 protected $model;
29
30 /**
31 * Use for cache
32 *
33 * @var \WP_Defender\Model\Setting\Password_Protection
34 */
35 protected $password_protection_model;
36
37 public function __construct() {
38 $this->pwned_api = 'https://api.pwnedpasswords.com/range/';
39 $this->model = wd_di()->get( \WP_Defender\Model\Setting\Password_Reset::class );
40
41 $this->password_protection_model = wd_di()->get(
42 \WP_Defender\Model\Setting\Password_Protection::class
43 );
44 }
45
46 /**
47 * Get the password that was submitted by user.
48 *
49 * @return string
50 */
51 public function get_submitted_password() {
52 $password = '';
53 foreach ( array( 'pwd', 'pass1', 'password', 'edd_user_pass' ) as $key ) {
54 $submitted_pass = HTTP::post( $key );
55 if ( ! empty( $submitted_pass ) ) {
56 $password = $submitted_pass;
57 break;
58 }
59 }
60
61 return $password;
62 }
63
64 /**
65 * Makes an API request to the remote server and
66 * 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 $user_id = $user->ID;
121 if ( ! is_multisite() ) {
122 // User should have roles.
123 $this->log( sprintf( "User ID: %d doesn't have roles", $user_id ), 'password.log' );
124 return false;
125 } else {
126 $arr_user_blogs = get_blogs_of_user( $user_id );
127 if ( empty( $arr_user_blogs ) ) {
128 // User should be associated with some site.
129 $this->log( sprintf( 'User ID: %d is not associated with any site', $user_id ), 'password.log' );
130 return false;
131 }
132 $user_blog_id = array_key_first( $arr_user_blogs );
133 $user = new WP_User( $user_id, '', $user_blog_id );
134 }
135 }
136 $user_roles = $user->roles;
137
138 return ! empty( array_intersect( $selected_user_roles, $user_roles ) );
139 }
140
141 /**
142 * Get reset password redirect URL.
143 *
144 * @param WP_User $user
145 *
146 * @return string|null
147 */
148 public function get_reset_password_redirect_url( $user ) {
149 $url = null;
150
151 $key = get_password_reset_key( $user );
152 if ( ! is_wp_error( $key ) ) {
153 $url = add_query_arg(
154 array(
155 'action' => 'rp',
156 'key' => $key,
157 'login' => $user->user_login,
158 ),
159 wp_login_url()
160 );
161 // extra hosting checks
162 $this->hosting_compatibility( $key, $user->user_login );
163 }
164
165 return $url;
166 }
167
168 /**
169 * Reset password redirect.
170 *
171 * @param string|null $url
172 * @param bool $safe
173 *
174 * @return void
175 */
176 public function reset_password_redirect( $url, $safe = false ) {
177 if ( empty( $url ) ) {
178 return;
179 }
180 $url = esc_url_raw( $url );
181 header( 'Cache-Control: no-store, no-cache' );
182 $safe ? wp_safe_redirect( $url ) : wp_redirect( $url );
183 exit();
184 }
185
186 /**
187 * Set cookie notice.
188 *
189 * @param string $name
190 * @param string $value
191 * @param int $time
192 */
193 public function set_cookie_notice( $name, $value, $time ) {
194 if ( ! isset( $_COOKIE[ $name ] ) ) {
195 setcookie( $name, $value, $time, '/' );
196 }
197 }
198
199 /**
200 * Remove cookie notice
201 *
202 * @param string $name
203 * @param int $time
204 */
205 public function remove_cookie_notice( $name, $time ) {
206 if ( isset( $_COOKIE[ $name ] ) ) {
207 setcookie( $name, null, $time, '/' );
208 }
209 $this->remove_extra_cookies();
210 }
211
212 /**
213 * Get the time when the user's password has last been changed.
214 *
215 * @param WP_User|int $user
216 *
217 * @return int
218 */
219 protected function password_last_changed( $user ) {
220 if ( ! $user ) {
221 return 0;
222 }
223
224 $changed = (int) get_user_meta( $user->ID, 'wd_last_password_change', true );
225
226 if ( ! $changed ) {
227 return strtotime( $user->user_registered );
228 }
229
230 return $changed;
231 }
232
233 /**
234 * Is user password expired?
235 *
236 * @param WP_User $user
237 *
238 * @return bool
239 */
240 public function check_expired_password( $user ) {
241
242 return isset( $this->model->force_time ) && $this->model->force_time >= $this->password_last_changed( $user );
243 }
244
245 /**
246 * Set the last updated time when a password is updated.
247 *
248 * @param WP_User $user
249 */
250 public function handle_password_updated( $user ) {
251 update_user_meta( $user->ID, 'wd_last_password_change', time() );
252 }
253
254 /**
255 * @param string $key
256 * @param string $user_login
257 *
258 * @since 2.5.5
259 */
260 protected function hosting_compatibility( $key, $user_login ) {
261 $mask_login = new \WP_Defender\Model\Setting\Mask_Login();
262 if (
263 ! isset( $_COOKIE[ 'wp-resetpass-' . COOKIEHASH ] )
264 && $mask_login->is_active()
265 && 'flywheel' === \WP_Defender\Component\Security_Tweaks\Servers\Server::get_current_server()
266 ) {
267 $value = sprintf( '%s:%s', $user_login, $key );
268 setcookie( 'wp-resetpass-' . COOKIEHASH, $value, 0, $mask_login->get_new_login_url(), COOKIE_DOMAIN, is_ssl(), true );
269 }
270 }
271
272 /**
273 * @since 2.5.5
274 */
275 protected function remove_extra_cookies() {
276 if (
277 isset( $_COOKIE[ 'wp-resetpass-' . COOKIEHASH ] )
278 && 'flywheel' === \WP_Defender\Component\Security_Tweaks\Servers\Server::get_current_server()
279 ) {
280 setcookie( 'wp-resetpass-' . COOKIEHASH, '', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true );
281 }
282 }
283
284 /**
285 * Force reset password.
286 *
287 * @param \WP_User|\WP_Error $user WP_User object or WP_Error.
288 * @param string $password Password plain string.
289 *
290 * @return \WP_User|\WP_Error Return user object or error object.
291 */
292 public function do_force_reset( $user, $password ) {
293 if (
294 ! is_wp_error( $user ) &&
295 wp_check_password( $password, $user->user_pass, $user->ID ) &&
296 $this->is_force_reset( $user )
297 ) {
298 $action = 'password_reset';
299 $cookie_name = 'display_reset_password_warning';
300
301 $this->trigger_redirect( $user, $action, $cookie_name );
302 }
303
304 return $user;
305 }
306
307 /**
308 * Reset weak password.
309 *
310 * @param \WP_User|\WP_Error $user WP_User object or WP_Error.
311 * @param string $password Password plain string.
312 *
313 * @return \WP_User|\WP_Error Return user object or error object.
314 */
315 public function do_weak_reset( $user, $password ) {
316 if (
317 ! is_wp_error( $user ) &&
318 wp_check_password( $password, $user->user_pass, $user->ID ) &&
319 $this->is_weak_password( $user, $password )
320 ) {
321 $action = 'password_protection';
322 $cookie_name = 'display_pwned_password_warning';
323
324 $this->trigger_redirect( $user, $action, $cookie_name );
325 }
326
327 return $user;
328 }
329
330 /**
331 * Redirect to reset password with error message
332 *
333 * @param \WP_User|\WP_Error $user WP_User object or WP_Error.
334 * @param string $action Action query string name.
335 * @param string $cookie_name Cookie name.
336 */
337 private function trigger_redirect( $user, $action, $cookie_name ) {
338 // Set cookie to check and display the warning notice on reset password page.
339 $this->set_cookie_notice(
340 $cookie_name,
341 true,
342 time() + MINUTE_IN_SECONDS * 2
343 );
344 // Get the reset password URL.
345 $url = $this->get_reset_password_redirect_url( $user );
346 /**
347 * Fires before redirecting to the password reset page.
348 *
349 * @since 2.5.6
350 *
351 * @param string $url
352 * @param string $action
353 */
354 do_action( 'wd_forced_reset_password_url', $url, $action );
355 // Redirect to the reset password page.
356 $this->reset_password_redirect( $url );
357 }
358
359 /**
360 * Verify if password is weak.
361 *
362 * Is user role enabled for weak password verification and is the password weak?
363 * If so then return true else password is strong therefore return false.
364 *
365 * @since 2.6.1
366 *
367 * @param WP_User $user WP_User object.
368 * @param string $password Plain password string.
369 *
370 * @return bool If password weak then true else false.
371 */
372 public function is_weak_password( $user, $password ) {
373 $user_roles = $this->password_protection_model->user_roles;
374 $is_enabled_by_user = $this->is_enabled_by_user_role( $user, $user_roles );
375 $is_pwned = $this->check_pwned_password( $password );
376
377 $is_weak_password = $this->password_protection_model->is_active() &&
378 $is_enabled_by_user &&
379 ! is_wp_error( $is_pwned ) &&
380 $is_pwned;
381
382 return $is_weak_password;
383 }
384
385 /**
386 * Verify if password is need to be force reset.
387 *
388 * Is user role enabled for force password reset and is the password expired?
389 * If so then return true else password is non expired therefore return false.
390 *
391 * @since 2.6.1
392 *
393 * @param WP_User $user WP_User object.
394 *
395 * @return bool If password expired then true else false.
396 */
397 public function is_force_reset( $user ) {
398 $user_roles = $this->model->user_roles;
399 $is_enabled_by_user = $this->is_enabled_by_user_role( $user, $user_roles );
400 $is_expired = $this->check_expired_password( $user );
401
402 $is_expired_password = $this->model->is_active() &&
403 $is_enabled_by_user &&
404 $is_expired;
405
406 return $is_expired_password;
407 }
408 }
409