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 / controller / mask-login.php

mask-login.php in Defender Security – Malware Scanner, Login Security & Firewall 3.1.0, at src/controller/mask-login.php

967 lines 26.8 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\Controller;
4
5 use Calotes\Component\Request;
6 use Calotes\Helper\HTTP;
7 use Calotes\Helper\Route;
8 use WP_Defender\Component\Config\Config_Hub_Helper;
9 use WP_Defender\Controller;
10 use Calotes\Component\Response;
11 use WP_Defender\Traits\IO;
12 use WP_Defender\Traits\Permission;
13 use WP_User;
14
15 /**
16 * This going to mask the login url & signup url and prevent directly access in those cases:
17 * 1. visit wp-login.php & signup.php or any url with those as suffix.
18 * However, we will expose the mask url in:
19 * 1. Every login & signup links on frontend, if normal user click on the link, they shouldn't get block
20 * 2. Every email sends from WP which contains the login URL, should not get block.
21 *
22 * Instead of detect if the user logged in or not, we should have a hash of user id and salt for cookies,
23 * this way when user direct from other source like back from HUB or so, they won't get lockout.
24 *
25 * The condition for trigger is when user visit the right mask login, then we will generate.
26 *
27 * Class Mask_Login
28 * @package WP_Defender\Controller
29 */
30 class Mask_Login extends Controller {
31 use IO, Permission;
32
33 /**
34 * Use for cache.
35 * @var \WP_Defender\Model\Setting\Mask_Login
36 */
37 protected $model;
38
39 /**
40 * @var \WP_Defender\Component\Mask_Login
41 */
42 protected $service;
43
44 /**
45 * @var array
46 */
47 protected $compatibility_notices = array();
48
49 public function __construct() {
50 add_filter( 'wp_defender_advanced_tools_data', array( &$this, 'script_data' ) );
51 // Internal cache, so we don't need to query many times.
52 $this->model = wd_di()->get( \WP_Defender\Model\Setting\Mask_Login::class );
53 $this->service = wd_di()->get( \WP_Defender\Component\Mask_Login::class );
54 $this->register_routes();
55
56 if ( $this->get_model()->is_active() ) {
57 $auth_component = wd_di()->get( \WP_Defender\Component\Two_Fa::class );
58 require_once ABSPATH . 'wp-admin/includes/plugin.php';
59 $is_jetpack_sso = $auth_component->is_jetpack_sso();
60 $is_tml = $auth_component->is_tml();
61 if ( ! $is_jetpack_sso && ! $is_tml ) {
62 // Never catch if from cli.
63 if ( 'cli' !== php_sapi_name() ) {
64 add_action( 'init', array( &$this, 'before_mask_login_handle' ), 99 );
65 }
66 // Monitor wp-admin, wp-login.php.
67 add_action( 'init', array( &$this, 'handle_login_request' ), 99 );
68 add_filter( 'wp_redirect', array( &$this, 'filter_wp_redirect' ), 10 );
69 // Filter site_url & network_site_url so people won't get block screen.
70 add_filter( 'site_url', array( &$this, 'filter_site_url' ), 100, 2 );
71 add_filter( 'network_site_url', array( &$this, 'filter_site_url' ), 100, 2 );
72 // If this is enabled, then we should filter all the email links.
73 add_filter( 'wp_mail', array( &$this, 'replace_login_url_in_email' ), 10 );
74 // For prevent admin redirect.
75 remove_action( 'template_redirect', 'wp_redirect_admin_locations' );
76 // If Pro site is activated and user email is not defined, we need to update the email to match the new login URL.
77 add_filter( 'update_welcome_email', array( &$this, 'update_welcome_email_prosite_case' ), 10, 6 );
78 // Change password link for new user.
79 add_filter( 'wp_new_user_notification_email', array( &$this, 'change_new_user_notification_email' ), 10, 3 );
80 add_filter( 'lostpassword_redirect', array( &$this, 'change_lostpassword_redirect' ), 10 );
81 // Log links in email.
82 add_filter( 'report_email_logs_link', array( &$this, 'update_report_logs_link' ), 10, 2 );
83 if ( class_exists( 'bbPress' ) ) {
84 add_filter( 'bbp_redirect_login', array( &$this, 'make_sure_wpadmin_after_login' ), 10, 3 );
85 }
86
87 if ( 'flywheel' === \WP_Defender\Component\Security_Tweaks\Servers\Server::get_current_server() ) {
88 if ( ! is_user_logged_in() ) {
89 add_action( 'login_form_rp', array( $this, 'handle_password_reset' ) );
90 add_action( 'login_form_resetpass', array( $this, 'handle_password_reset' ) );
91 }
92 add_filter( 'retrieve_password_message', array( &$this, 'flywheel_change_password_message' ), 10, 4 );
93 } else {
94 // Change password link for exist user.
95 add_filter( 'retrieve_password_message', array( &$this, 'change_password_message' ), 10, 4 );
96 }
97
98 global $pagenow;
99 if ( is_network_admin() && 'sites.php' === $pagenow ) {
100 // Add 4th parameter $scheme when the plugin will support WP at least v5.8.
101 add_filter( 'admin_url', array( $this, 'change_subsites_admin_url' ), 10, 3 );
102 }
103 } else {
104 if ( $is_jetpack_sso ) {
105 $this->compatibility_notices[] = __( "We've detected a conflict with Jetpack's Wordpress.com Log In feature. Please disable it and return to this page to continue setup.", 'wpdef' );
106 }
107 if ( $is_tml ) {
108 $this->compatibility_notices[] = __( "We've detected a conflict with Theme my login. Please disable it and return to this page to continue setup.", 'wpdef' );
109 }
110 }
111 }
112 }
113
114 /**
115 * For fixing the issue when bbPress enable, after login, users redirect to home.
116 *
117 * @param string $url
118 * @param string $raw_url
119 * @param object $user
120 *
121 * @return string
122 */
123 public function make_sure_wpadmin_after_login( $url, $raw_url, $user ) {
124 if ( home_url() === $url ) {
125 $url = admin_url();
126 }
127
128 return apply_filters( 'defender_redirect_login', $url, $raw_url, $user );
129 }
130
131 /**
132 * We need to filter emails and replace the normal login URL with masked one.
133 *
134 * @param $attrs
135 *
136 * @return mixed
137 */
138 public function replace_login_url_in_email( $attrs ) {
139 if ( ! is_array( $attrs ) || ! isset( $attrs['message'] ) ) {
140 return $attrs;
141 }
142
143 $message = $attrs['message'];
144 $site_url = str_replace( '/', '\/', HTTP::strips_protocol( site_url() ) );
145 $pattern = '/https?:\/\/' . $site_url . '\/wp-login\.php?[^\s]+/';
146
147 if ( preg_match_all( $pattern, $message, $matches ) ) {
148 foreach ( $matches as $match ) {
149 foreach ( $match as $url ) {
150 $query = wp_parse_url( $url, PHP_URL_QUERY );
151 $query = $query ?? '';
152 parse_str( $query, $queries );
153 if ( is_array( $queries ) && count( $queries ) ) {
154 $new_url = add_query_arg( $queries, $this->get_model()->get_new_login_url() );
155 } else {
156 $new_url = $this->get_model()->get_new_login_url();
157 }
158 $message = str_replace( $url, $new_url, $message );
159 }
160 }
161 }
162 $attrs['message'] = $message;
163
164 return $attrs;
165 }
166
167 /**
168 * Show login page.
169 */
170 public function show_login_page() {
171 global $error, $interim_login, $action, $user_login, $user, $redirect_to;
172 require_once ABSPATH . 'wp-login.php';
173 die;
174 }
175
176 /**
177 * Before Mask Login handling.
178 *
179 * @since 2.8.0
180 * @return void
181 */
182 public function before_mask_login_handle() {
183 $current_url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] );
184 $login_url = $this->get_model()->get_new_login_url( $this->get_site_url() );
185
186 if (
187 ! is_user_logged_in() &&
188 '' !== $login_url &&
189 ! $this->service->is_land_on_masked_url( $this->model->mask_url ) &&
190 /**
191 * Filter to redirect current URL to Mask Login URL.
192 *
193 * @param bool $allowed Should we redirect to Mask Login URL?.
194 * @param string $current_url Current URL to check.
195 *
196 * @since 2.8.0
197 */
198 true === apply_filters( 'wpdef_maybe_redirect_to_mask_login_url', false, $current_url )
199 ) {
200 $modified_url = add_query_arg( 'redirect_to', rawurlencode( $current_url ), $login_url );
201
202 wp_redirect( $modified_url );
203 die();
204 }
205 }
206
207 /**
208 * If it is request to wp-admin, wp-login.php and similar slugs, we block for sure,
209 * if no,then follow the wp flown.
210 *
211 * @return mixed|void
212 */
213 public function handle_login_request() {
214 // Doesn't need to handle the login request for bots and crawlers.
215 if ( $this->service->is_bot_request() ) {
216 return;
217 }
218 // Need to check if the current request is for signup, login.
219 // If it is not the slug, then we redirect to the 404 redirect, or 403 wp die.
220 $requested_path = $this->service->get_request_path();
221 $requested_path_without_slash = ltrim( $requested_path, '/' );
222 if ( ! $requested_path_without_slash ) {
223 return;
224 }
225
226 if ( '/' . ltrim( $this->get_model()->mask_url, '/' ) === $requested_path ) {
227 // We need to redirect this one to wp-login and open it.
228 return $this->show_login_page();
229 }
230 /**
231 * Allowed if:
232 * it's AJAX,
233 * the user is logged in,
234 * it's an admin post request.
235 */
236 if (
237 defined( 'DOING_AJAX' )
238 || is_user_logged_in()
239 || $this->is_allowed_path( $requested_path_without_slash )
240 ) {
241 // Do nothing.
242 return;
243 }
244
245 // If user is not logged in but login cookie is set.
246 if ( isset( $_COOKIE[ LOGGED_IN_COOKIE ] ) && ! is_user_logged_in() ) {
247 $user_id = wp_validate_auth_cookie( $_COOKIE[ LOGGED_IN_COOKIE ], 'logged_in' );
248
249 if ( $user_id ) {
250 // Cookie is valid so login the user.
251 wp_set_current_user( $user_id );
252 // Return from here because of valid user found.
253 return;
254 }
255 }
256
257 $ticket = HTTP::get( 'ticket', false );
258 // Todo: need if express_tickets are not saved?
259 if ( false !== $ticket && $this->service->redeem_ticket( $ticket ) ) {
260 // Allow to pass.
261 return;
262 }
263
264 // If current is same then we show the login screen.
265 if ( $this->service->is_land_on_masked_url( $this->model->mask_url ) ) {
266 return $this->show_login_page();
267 }
268
269 // If it's the verification link to change Network Admin Email.
270 $is_multisite = is_multisite();
271 if (
272 $is_multisite
273 && false !== strpos( parse_url( $requested_path, PHP_URL_QUERY ), 'network_admin_hash' )
274 ) {
275 $logs_url = add_query_arg(
276 'redirect_to',
277 urlencode( $requested_path ),
278 $this->get_model()->get_new_login_url()
279 );
280 wp_safe_redirect( $logs_url );
281 die;
282 }
283 /**
284 * Block if it's:
285 * 1) no MU but there is an attempt to load the 'wp-signup.php' page,
286 * 2) from the list of forbidden slugs.
287 */
288 if (
289 ( ! $is_multisite && 'wp-signup.php' === $requested_path_without_slash )
290 || $this->service->is_on_login_page( $requested_path_without_slash )
291 ) {
292 // If they are here and the flow getting here, then just lock.
293 return $this->maybe_lock();
294 }
295 }
296
297 /**
298 * Store settings into db.
299 * @defender_route
300 */
301 public function save_settings( Request $request ) {
302 $data = $request->get_data_by_model( $this->model );
303 $this->model->import( $data );
304 if ( $this->model->validate() ) {
305 $this->model->save();
306 Config_Hub_Helper::set_clear_active_flag();
307
308 return new Response(
309 true,
310 array_merge(
311 array(
312 'message' => __( 'Your settings have been updated.', 'wpdef' ),
313 ),
314 $this->data_frontend()
315 )
316 );
317 }
318
319 return new Response(
320 false,
321 // Merge stored data to avoid errors.
322 array_merge( array( 'message' => $this->model->get_formatted_errors() ), $this->data_frontend() )
323 );
324 }
325
326 /**
327 * Filter every admin/login URL to return the masked one.
328 *
329 * @param string $site_url The complete URL.
330 * @param string $path The submitted path.
331 *
332 * @return string
333 */
334 public function filter_site_url( $site_url, $path ) {
335 return $this->alter_url( $site_url, $path );
336 }
337
338 /**
339 * @param string $location
340 *
341 * @return string
342 */
343 public function filter_wp_redirect( $location ) {
344 return $this->alter_url( $location, $location );
345 }
346
347 /**
348 * @param string $current_url
349 * @param string $path
350 *
351 * @return string
352 */
353 public function alter_url( $current_url, $path = '' ) {
354 // Doesn't need to alter the URL for bots.
355 // We will not unveil the masked login URL for them, instead show default login URL.
356 if ( $this->service->is_bot_request() ) {
357 return $current_url;
358 }
359
360 if ( is_user_logged_in() && false === stripos( $current_url, 'wp-login.php' ) ) {
361 // Do nothing.
362 return $current_url;
363 }
364
365 if ( false !== stripos( $current_url, 'wp-login.php' ) ) {
366 // This is URL go to old wp-login.php.
367 $query = wp_parse_url( $current_url, PHP_URL_QUERY );
368 $query = $query ?? '';
369 parse_str( $query, $params );
370
371 return add_query_arg( $params, $this->get_model()->get_new_login_url( $this->get_site_url() ) );
372 } else {
373 // This case when admin map a domain into subsite, we need to update the new domain/masked-login into the list.
374 if ( ! function_exists( 'get_current_screen' ) ) {
375 require_once( ABSPATH . 'wp-admin/includes/screen.php' );
376 }
377 $screen = get_current_screen();
378
379 if ( ! is_object( $screen ) ) {
380 return $current_url;
381 }
382 if ( 'sites-network' === $screen->id ) {
383 // Case URLs inside sites list, need to check those with custom domain cause when it's redirect, it will require re-login.
384 $requested_path = $this->service->get_request_path( $current_url );
385 if ( '/wp-admin' === $requested_path ) {
386 $current_domain = $_SERVER['HTTP_HOST'];
387 $sub_domain = parse_url( $current_url, PHP_URL_HOST );
388 if ( ! empty( $sub_domain ) && false === stripos( $sub_domain, $current_domain ) ) {
389
390 return $this->get_model()->get_new_login_url( $sub_domain );
391 }
392 }
393 }
394 /**
395 * Todo:
396 * add other condition ('my-sites' === $screen->id)
397 * create OTP key and link with the 'otp' arg inside
398 */
399 }
400
401 return $current_url;
402 }
403
404 /**
405 * Show the wp die screen for lockout, or redirect to defined URL.
406 */
407 public function maybe_lock() {
408 $forbidden_message = __( 'This feature is forbidden temporarily for security reason. Try login again.', 'wpdef' );
409
410 if ( 'custom_url' === $this->get_model()->redirect_traffic && strlen( $this->get_model()->redirect_traffic_url ) ) {
411 if ( 'url' === $this->get_model()->is_url_or_slug() ) {
412 $redirect_url = wp_sanitize_redirect( $this->get_model()->redirect_traffic_url );
413 $lp = @parse_url( $redirect_url );
414
415 // Give up if malformed URL.
416 if ( false === $lp ) {
417 wp_die( esc_html( $forbidden_message ) );
418 }
419 // If the URL is without scheme, e.g. example.com, then add 'http' protocol at the beginning of the URL.
420 if ( ! isset( $lp['scheme'] ) && isset( $lp['path'] ) ) {
421 $redirect_url = 'http://' . untrailingslashit( $redirect_url );
422 }
423 wp_redirect( $redirect_url );
424 } else {
425 wp_safe_redirect( home_url( $this->get_model()->redirect_traffic_url ) );
426 }
427 die;
428 }
429
430 if ( 'wp_page' === $this->get_model()->redirect_traffic ) {
431 $id = $this->get_model()->redirect_traffic_page_id;
432 $post = get_post( $id );
433 if ( is_object( $post ) ) {
434 wp_safe_redirect( get_permalink( $post ) );
435 exit;
436 }
437 }
438
439 // Handle user profile email change request.
440 $this->handle_email_change_request();
441
442 wp_die( esc_html( $forbidden_message ) );
443 }
444
445 /**
446 * Safe way to get cached model.
447 *
448 * @return \WP_Defender\Model\Setting\Mask_Login
449 */
450 private function get_model() {
451 if ( is_object( $this->model ) ) {
452 return $this->model;
453 }
454
455 return new \WP_Defender\Model\Setting\Mask_Login();
456 }
457
458 /**
459 * @param array $data
460 *
461 * @return array
462 * @throws \ReflectionException
463 */
464 public function script_data( $data ) {
465 $data['mask_login'] = $this->data_frontend();
466
467 return $data;
468 }
469
470 /**
471 * Login redirect.
472 *
473 * @param string $url
474 * @param string $raw_url Raw url
475 * @param object $user User object
476 *
477 * @return string
478 */
479 public function redirect_login( $url, $raw_url, $user ) {
480 if ( home_url() === $url ) {
481 $url = admin_url();
482 }
483
484 return apply_filters( 'defender_redirect_login', $url, $raw_url, $user );
485 }
486
487 /**
488 * @param null|int $blog_id
489 * @param string $path
490 * @param null $scheme
491 *
492 * @return string
493 */
494 private function get_site_url( $blog_id = null, $path = '', $scheme = null ) {
495 if ( empty( $blog_id ) || ! is_multisite() ) {
496 $url = get_option( 'siteurl' );
497 } else {
498 switch_to_blog( $blog_id );
499 $url = get_option( 'siteurl' );
500 restore_current_blog();
501 }
502
503 $url = set_url_scheme( $url, $scheme );
504
505 if ( $path && is_string( $path ) ) {
506 $url .= '/' . ltrim( $path, '/' );
507 }
508
509 if (
510 is_plugin_active( 'wp-ultimo/wp-ultimo.php' )
511 || is_plugin_active_for_network( 'wp-ultimo/wp-ultimo.php' )
512 ) {
513 return apply_filters( 'site_url', $url, $path, $scheme, $blog_id );
514 } else {
515 return $url;
516 }
517 }
518
519 public function remove_settings() {}
520
521 public function remove_data() {}
522
523 /**
524 * @return array
525 */
526 public function to_array() {
527 $model = new \WP_Defender\Model\Setting\Mask_Login();
528 [$routes, $nonces] = Route::export_routes( 'mask_login' );
529
530 return array(
531 'enabled' => $model->enabled,
532 'useable' => $model->is_active(),
533 'login_url' => $model->get_new_login_url(),
534 'endpoints' => $routes,
535 'nonces' => $nonces,
536 );
537 }
538
539 /**
540 * @return array
541 */
542 public function dashboard_widget() {
543 $model = new \WP_Defender\Model\Setting\Mask_Login();
544
545 return array(
546 'model' => $model->export(),
547 'is_active' => $model->is_active(),
548 );
549 }
550
551 /**
552 * @return array
553 */
554 public function data_frontend() {
555 // Don't use cache because wrong url is displayed for forbidden slugs.
556 $model = new \WP_Defender\Model\Setting\Mask_Login();
557
558 $data = array_merge(
559 array(
560 'model' => $model->export(),
561 'is_active' => $model->is_active(),
562 'new_login_url' => $model->get_new_login_url(),
563 'notices' => $this->compatibility_notices,
564 ),
565 $this->dump_routes_and_nonces()
566 );
567
568 if ( isset( $data['model']['redirect_traffic_page_id'] ) ) {
569 $id = $data['model']['redirect_traffic_page_id'];
570
571 $data['redirect_traffic_page_title'] = $id > 0 ? get_the_title( $id ) : '';
572 $data['redirect_traffic_page_url'] = $id > 0 ? get_the_permalink( $id ) : '#';
573 }
574
575 return $data;
576 }
577
578 public function import_data( $data ) {
579 $model = $this->get_model();
580
581 $model->import( $data );
582 if ( $model->validate() ) {
583 $model->save();
584 }
585 }
586
587 /**
588 * @param $welcome_email
589 * @param $blog_id
590 * @param $user_id
591 * @param $password
592 * @param $title
593 * @param $meta
594 *
595 * @return string
596 */
597 public function update_welcome_email_prosite_case( $welcome_email, $blog_id, $user_id, $password, $title, $meta ) {
598 $url = get_blogaddress_by_id( $blog_id );
599 $welcome_email = str_replace(
600 $url . 'wp-login.php',
601 $this->get_model()->get_new_login_url( rtrim( $url, '/' ) ),
602 $welcome_email
603 );
604
605 return $welcome_email;
606 }
607
608 /**
609 * @param string $logs_url
610 * @param string $email
611 *
612 * @return string
613 */
614 public function update_report_logs_link( $logs_url, $email ) {
615
616 return add_query_arg( 'redirect_to', $logs_url, $this->get_model()->get_new_login_url() );
617 }
618
619 /**
620 * Change password URL for new user.
621 *
622 * @param string $wp_new_user_notification_email
623 * @param object $user
624 * @param string $blogname
625 *
626 * @return string
627 */
628 public function change_new_user_notification_email( $wp_new_user_notification_email, $user, $blogname ) {
629 $wp_new_user_notification_email['message'] = str_replace(
630 network_site_url( 'wp-login.php' ),
631 $this->get_model()->get_new_login_url( $this->get_site_url() ),
632 $wp_new_user_notification_email['message']
633 );
634
635 return $wp_new_user_notification_email;
636 }
637
638 /**
639 * Change password URL for existed user if the user login has a space, e.g. 'Test user'.
640 * Change via str_replace() without rawurlencode() doesn't work.
641 *
642 * @param string $message
643 * @param string $key
644 * @param string $user_login
645 * @param WP_User $user_data
646 *
647 * @return string
648 */
649 public function change_password_message( $message, $key, $user_login, $user_data ) {
650 if ( false !== strpos( $user_login, ' ' ) ) {
651 $message = str_replace(
652 network_site_url( "wp-login.php?action=rp&key=$key&login=" . rawurlencode( $user_login ), 'login' ),
653 $this->get_model()->get_new_login_url( $this->get_site_url() )
654 . "?action=rp&key=$key&login=" . rawurlencode( $user_login ),
655 $message
656 );
657 }
658
659 return $message;
660 }
661
662 /**
663 * @param string $message
664 * @param string $key
665 * @param string $user_login
666 * @param WP_User $user_data
667 *
668 * @since 2.5.5
669 *
670 * @return string
671 */
672 public function flywheel_change_password_message( $message, $key, $user_login, $user_data ) {
673 $message = str_replace(
674 network_site_url( "wp-login.php?action=rp&key=$key&login=" . rawurlencode( $user_login ), 'login' ),
675 $this->get_model()->get_new_login_url( $this->get_site_url() )
676 . "?action=rp&key=$key&login=" . rawurlencode( $user_login ) . '&wd-ml-token=' . rawurlencode( $user_login ),
677 $message
678 );
679
680 return $message;
681 }
682
683
684 /**
685 * Change redirect param of the link 'Lost your password?'.
686 *
687 * @param string $lostpassword_redirect
688 *
689 * @return string
690 */
691 public function change_lostpassword_redirect( $lostpassword_redirect ) {
692 return $this->get_model()->get_new_login_url( $this->get_site_url() ) . '?checkemail=confirm';
693 }
694
695 /**
696 * Handle user profile email change request.
697 */
698 private function handle_email_change_request() {
699 // If it is not for admin request.
700 if ( ! is_admin() ) {
701 return;
702 }
703
704 // If `IS_PROFILE_PAGE` constant is defined.
705 if ( ! defined( 'IS_PROFILE_PAGE' ) ) {
706 return;
707 }
708
709 // If request is not for profile page.
710 if ( ! IS_PROFILE_PAGE ) {
711 return;
712 }
713
714 // If query data is not set.
715 if ( ! isset( $_GET['newuseremail'] ) ) {
716 return;
717 }
718
719 global $wpdb;
720 $hash = sanitize_text_field( $_GET['newuseremail'] );
721 $like = '%' . $wpdb->esc_like( $hash ) . '%';
722 $meta_key = $wpdb->get_var(
723 $wpdb->prepare( "SELECT meta_key FROM {$wpdb->usermeta} WHERE meta_value LIKE %s", $like )
724 );
725
726 // Hash not found.
727 if ( '_new_email' !== $meta_key ) {
728 return;
729 }
730
731 // Everything good, now redirect user to login page.
732 $current_url = add_query_arg( $_GET, admin_url( 'profile.php' ) );
733 $redirect_url = esc_url( wp_login_url( $current_url ) );
734 wp_redirect( $redirect_url );
735 die();
736 }
737
738 /**
739 * @return array
740 */
741 public function export_strings() {
742
743 return array(
744 $this->get_model()->is_active() ? __( 'Active', 'wpdef' ) : __( 'Inactive', 'wpdef' ),
745 );
746 }
747
748 /**
749 * @param array $config
750 * @param bool $is_pro
751 *
752 * @return array
753 */
754 public function config_strings( $config, $is_pro ) {
755
756 return array(
757 $config['enabled'] ? __( 'Active', 'wpdef' ) : __( 'Inactive', 'wpdef' ),
758 );
759 }
760
761 /**
762 * Support for the password reset page on various hosting.
763 */
764 public function handle_password_reset() {
765 // Get the email link.
766 if (
767 isset( $_GET['action'], $_GET['key'], $_GET['login'], $_GET['wd-ml-token'] )
768 && 'rp' === $_GET['action']
769 && $_GET['login'] === $_GET['wd-ml-token']
770 ) {
771 $key = wp_unslash( $_REQUEST['key'] );
772 $login = wp_unslash( $_REQUEST['login'] );
773 $user = check_password_reset_key( $key, $login );
774 if ( ! is_wp_error( $user ) ) {
775 $value = sprintf( '%s:%s', $login, $key );
776 set_site_transient( 'wd-rp-' . COOKIEHASH, $value, 2 * MINUTE_IN_SECONDS );
777 wp_safe_redirect( remove_query_arg( array( 'key', 'login', 'wd-ml-token' ) ) );
778 exit;
779 }
780 }
781 $value = get_site_transient( 'wd-rp-' . COOKIEHASH );
782 // Process the data and display the result.
783 if (
784 isset( $_GET['action'] )
785 && in_array( $_GET['action'], array( 'rp', 'resetpass' ), true )
786 && isset( $value ) && 0 < strpos( $value, ':' )
787 ) {
788 [$login, $key] = explode( ':', wp_unslash( $value ), 2 );
789 $user = check_password_reset_key( $key, $login );
790 if ( 'resetpass' === $_GET['action'] ) {
791 delete_site_transient( 'wd-rp-' . COOKIEHASH );
792 }
793 if ( ! is_wp_error( $user ) ) {
794 $this->render_partial(
795 'mask-login/reset',
796 array(
797 'user' => $user,
798 )
799 );
800 exit;
801 }
802 }
803 }
804
805 /**
806 * Check if a path is allowed without login masking.
807 *
808 * @param string $path Path to check.
809 *
810 * @since 2.6.4
811 * @return bool
812 */
813 private function is_allowed_path( $path ) {
814 // Admin post requests to admin-post.php should be allowed.
815 $allowed = 'wp-admin/admin-post.php' === $path && isset( $_REQUEST['action'] ); // phpcs:ignore
816
817 /**
818 * Filter to allow whitelisting paths from login masking.
819 *
820 * @param bool $allowed Is current path allowed?.
821 * @param string $path Path to check.
822 *
823 * @since 2.6.4
824 */
825 return apply_filters( 'wd_mask_login_is_allowed_path', $allowed, $path );
826 }
827
828 /**
829 * An endpoint for fetching Post/Page.
830 *
831 * @param Request $request Request data.
832 *
833 * @since 2.7.1
834 * @defender_route
835 */
836 public function get_posts( Request $request ) {
837 $data = $request->get_data(
838 array(
839 'per_page' => array(
840 'type' => 'int',
841 'sanitize' => 'sanitize_text_field',
842 ),
843 'search' => array(
844 'type' => 'string',
845 'sanitize' => 'sanitize_text_field',
846 ),
847 )
848 );
849
850 $per_page = $data['per_page'] ?? 50;
851 $search = $data['search'] ?? '';
852
853 add_filter( 'posts_where', array( $this, 'posts_where_title' ), 10, 2 );
854 $post_query = new \WP_Query(
855 array(
856 'post_type' => array( 'page', 'post' ),
857 'posts_per_page' => $per_page,
858 'search_by_post_title' => $search,
859 'post_status' => 'publish',
860 'orderby' => 'title',
861 'order' => 'ASC',
862 )
863 );
864 remove_filter( 'posts_where', array( $this, 'posts_where_title' ), 10 );
865
866 $posts_array = $post_query->posts;
867 $data = array();
868 foreach ( $posts_array as $post ) {
869 $data[] = array(
870 'id' => $post->ID,
871 'name' => $post->post_title,
872 'url' => get_the_permalink( $post->ID ),
873 );
874 }
875
876 wp_send_json_success( $data );
877 }
878
879 /**
880 * Filter the WHERE clause of the query.
881 *
882 * @param string $where
883 * @param WP_Query $wp_query
884 *
885 * @since 2.7.1
886 * @return string $where
887 */
888 public function posts_where_title( $where, $wp_query ) {
889 global $wpdb;
890
891 $search_term = $wp_query->get( 'search_by_post_title' );
892 if ( ! empty( $search_term ) ) {
893 $where .= ' AND ' . $wpdb->posts . '.post_title LIKE \'%' . esc_sql( $wpdb->esc_like( $search_term ) ) . '%\'';
894 }
895
896 return $where;
897 }
898
899 /**
900 * Update url to masked login url if domain is mapped.
901 *
902 * @param string $url
903 * @param string $path
904 * @param int|null $blog_id
905 *
906 * @return string
907 */
908 public function change_subsites_admin_url( $url, $path, $blog_id ) {
909 if ( empty( $path ) && ! empty( $blog_id ) ) {
910 $mask_url = trim( $this->model->mask_url );
911
912 if ( ! empty( $mask_url ) && $this->check_if_domain_is_mapped( $url ) ) {
913 $url = str_replace( 'wp-admin', $mask_url, untrailingslashit( $url ) );
914 }
915 }
916 return $url;
917 }
918
919 /**
920 * Check if domain is mapped.
921 *
922 * @param string $url
923 *
924 * @return string
925 */
926 public function check_if_domain_is_mapped( $url ) {
927 $is_mapped = false;
928
929 if ( ! empty( $url ) ) {
930 $url_arr = wp_parse_url( $url );
931 $net_url_arr = wp_parse_url( network_site_url() );
932
933 if (
934 ! empty( $url_arr['host'] ) &&
935 ! empty( $net_url_arr['host'] ) &&
936 $this->get_domain_from_host( $url_arr['host'] ) !== $this->get_domain_from_host( $net_url_arr['host'] )
937 ) {
938 $is_mapped = true;
939 }
940 }
941
942 return $is_mapped;
943 }
944
945 /**
946 * Extract domain from host.
947 *
948 * @param string $host
949 *
950 * @return string
951 */
952 public function get_domain_from_host( $host ) {
953 $host = strtolower( trim( $host ) );
954
955 $count = substr_count( $host, '.' );
956 if ( 2 === $count ) {
957 if ( strlen( explode( '.', $host )[1] ) > 3 ) {
958 $host = explode( '.', $host, 2 )[1];
959 }
960 } else if ( $count > 2 ) {
961 $host = $this->get_domain_from_host( explode( '.', $host, 2 )[1] );
962 }
963
964 return $host;
965 }
966 }
967