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

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