2023-07-01 17:48:45 +08:00
< ? php
namespace um\core ;
2023-07-06 01:56:59 +03:00
use WP_Error ;
2023-07-05 12:00:26 +08:00
use WP_User ;
use WP_User_Query ;
2023-07-01 17:48:45 +08:00
if ( ! defined ( 'ABSPATH' ) ) {
exit ;
}
if ( ! class_exists ( 'um\core\Secure' ) ) {
/**
* Class Secure
*
* @package um\core
*
2023-07-03 15:38:29 +08:00
* @since 2.6.8
2023-07-01 17:48:45 +08:00
*/
class Secure {
/**
* Login constructor.
2023-07-03 15:38:29 +08:00
* @since 2.6.8
2023-07-01 17:48:45 +08:00
*/
public function __construct () {
2023-07-06 01:56:59 +03:00
add_action ( 'init' , array ( $this , 'init' ) );
2023-07-01 17:48:45 +08:00
2023-07-06 01:56:59 +03:00
add_action ( 'um_before_login_fields' , array ( $this , 'reset_password_notice' ), 1 );
2023-07-01 17:48:45 +08:00
2023-07-06 01:56:59 +03:00
add_action ( 'um_before_login_fields' , array ( $this , 'under_maintenance_notice' ), 1 );
2023-07-01 17:48:45 +08:00
add_action ( 'um_submit_form_register' , array ( $this , 'block_register_forms' ) );
add_action ( 'um_user_login' , array ( $this , 'login_validate_expired_pass' ), 1 );
add_action ( 'validate_password_reset' , array ( $this , 'avoid_old_password' ), 1 , 2 );
2023-07-03 23:28:49 +08:00
/**
* WP Schedule Events for Notification
*/
add_action ( 'wp' , array ( $this , 'schedule_events' ) );
}
/**
2023-07-06 01:56:59 +03:00
* Adds handlers on form submissions.
2023-07-03 23:28:49 +08:00
*
* @since 2.6.8
*/
public function init () {
/**
* Checks the integrity of Current User's Capabilities
*/
2023-07-06 01:56:59 +03:00
add_action ( 'um_after_save_registration_details' , array ( $this , 'secure_user_capabilities' ), 1 );
add_action ( 'um_after_save_registration_details' , array ( $this , 'maybe_set_whitelisted_password' ), 2 );
2023-07-03 23:28:49 +08:00
if ( is_user_logged_in () && ! current_user_can ( 'manage_options' ) ) { // Exclude current Logged-in Administrator from validation checks.
add_action ( 'um_after_user_updated' , array ( $this , 'secure_user_capabilities' ), 1 );
2023-07-04 03:34:19 +08:00
add_action ( 'um_after_user_account_updated' , array ( $this , 'secure_user_capabilities' ), 1 );
2023-07-03 23:28:49 +08:00
}
2023-07-01 17:48:45 +08:00
}
/**
* Add Login notice for Reset Password
*
2023-07-03 15:38:29 +08:00
* @since 2.6.8
2023-07-01 17:48:45 +08:00
*/
2023-07-06 01:56:59 +03:00
public function reset_password_notice () {
2023-07-01 17:48:45 +08:00
if ( ! UM () -> options () -> get ( 'display_login_form_notice' ) ) {
return ;
}
// phpcs:disable WordPress.Security.NonceVerification
if ( ! isset ( $_REQUEST [ 'notice' ] ) || 'expired_password' !== $_REQUEST [ 'notice' ] ) {
return ;
}
// phpcs:enable WordPress.Security.NonceVerification
echo " <p class='um-notice warning'> " ;
echo wp_kses (
sprintf (
// translators: One-time change requires you to reset your password
__ ( '<strong>Important:</strong> Your password has expired. This (one-time) change requires you to reset your password. Please <a href="%s">click here</a> to reset your password via Email.' , 'ultimate-member' ),
um_get_core_page ( 'password-reset' )
),
array (
'strong' => array (),
'a' => array (
'href' => array (),
),
)
);
echo '</p>' ;
}
/**
* Add Login notice for Under Maintance
*
2023-07-03 15:38:29 +08:00
* @since 2.6.8
2023-07-01 17:48:45 +08:00
*/
2023-07-06 01:56:59 +03:00
public function under_maintenance_notice () {
2023-07-01 17:48:45 +08:00
if ( ! UM () -> options () -> get ( 'lock_register_forms' ) ) {
return ;
}
// phpcs:disable WordPress.Security.NonceVerification
2023-07-06 01:56:59 +03:00
if ( ! isset ( $_GET [ 'notice' ] ) || 'maintenance' !== $_GET [ 'notice' ] ) {
2023-07-01 17:48:45 +08:00
return ;
}
// phpcs:enable WordPress.Security.NonceVerification
echo " <p class='um-notice warning'> " ;
echo wp_kses (
2023-07-06 01:56:59 +03:00
__ ( '<strong>Important:</strong> This site is currently under maintenance. Please log in or check back soon.' , 'ultimate-member' ),
2023-07-01 17:48:45 +08:00
array (
'strong' => array (),
'a' => array (
'href' => array (),
),
)
);
echo '</p>' ;
}
/**
* Block all UM Register form submissions.
*
* @param array $args Form settings.
2023-07-03 15:38:29 +08:00
* @since 2.6.8
2023-07-01 17:48:45 +08:00
*/
public function block_register_forms ( $args ) {
if ( UM () -> options () -> get ( 'lock_register_forms' ) ) {
2023-07-06 01:56:59 +03:00
$login_url = add_query_arg ( 'notice' , 'maintenance' , um_get_core_page ( 'login' ) );
2023-07-01 17:48:45 +08:00
nocache_headers ();
wp_safe_redirect ( $login_url );
exit ;
}
}
/**
* Validate when user has expired password
*
2023-07-03 15:38:29 +08:00
* @since 2.6.8
2023-07-01 17:48:45 +08:00
*/
2023-07-06 01:56:59 +03:00
public function login_validate_expired_pass () {
2023-07-01 17:48:45 +08:00
if ( UM () -> options () -> get ( 'display_login_form_notice' ) ) {
2023-07-06 01:56:59 +03:00
$expired_password_reset = get_user_meta ( um_user ( 'ID' ), 'um_secure_has_reset_password' , true );
if ( ! $expired_password_reset ) {
2023-07-01 17:48:45 +08:00
$login_url = add_query_arg ( 'notice' , 'expired_password' , um_get_core_page ( 'login' ) );
wp_safe_redirect ( $login_url );
exit ;
}
}
}
/**
2023-07-06 01:56:59 +03:00
* Prevent users from using Old Passwords on UM Password Reset form.
*
* @param WP_Error $errors
* @param WP_User|WP_Error $user
2023-07-01 17:48:45 +08:00
*
2023-07-03 15:38:29 +08:00
* @since 2.6.8
2023-07-01 17:48:45 +08:00
*/
public function avoid_old_password ( $errors , $user ) {
2023-07-06 01:56:59 +03:00
if ( empty ( $_POST [ '_um_password_change' ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
return ;
}
if ( ! UM () -> options () -> get ( 'display_login_form_notice' ) ) {
return ;
}
2023-07-01 17:48:45 +08:00
if ( isset ( $_REQUEST [ 'user_password' ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
2023-07-06 01:56:59 +03:00
$new_user_pass = wp_unslash ( $_REQUEST [ 'user_password' ] ); // phpcs:ignore WordPress.Security.NonceVerification
if ( wp_check_password ( $new_user_pass , $user -> data -> user_pass , $user -> ID ) ) {
2023-07-01 17:48:45 +08:00
UM () -> form () -> add_error ( 'user_password' , __ ( 'Your new password cannot be same as old password.' , 'ultimate-member' ) );
2023-07-06 01:56:59 +03:00
$errors -> add ( 'um_block_old_password' , __ ( 'Your new password cannot be same as old password.' , 'ultimate-member' ) );
2023-07-01 17:48:45 +08:00
} else {
2023-07-06 01:56:59 +03:00
update_user_meta ( $user -> ID , 'um_secure_has_reset_password' , true );
update_user_meta ( $user -> ID , 'um_secure_has_reset_password__timestamp' , current_time ( 'mysql' ) );
2023-07-01 17:48:45 +08:00
}
}
}
/**
2023-07-06 01:56:59 +03:00
* Secure user capabilities and revoke administrative ones.
*
2023-07-03 19:38:16 +08:00
* @since 2.6.8
2023-07-01 17:48:45 +08:00
*/
2023-07-03 23:28:49 +08:00
public function secure_user_capabilities ( $user_id ) {
2023-07-03 15:38:29 +08:00
global $wpdb ;
2023-07-06 01:56:59 +03:00
$user = get_userdata ( $user_id );
if ( empty ( $user ) ) {
return ;
}
2023-07-01 17:48:45 +08:00
// Fetch the WP_User object of our user.
2023-07-03 16:54:16 +08:00
um_fetch_user ( $user_id );
2023-07-03 23:28:49 +08:00
$has_admin_cap = false ;
$arr_banned_caps = array ();
if ( UM () -> options () -> get ( 'banned_capabilities' ) ) {
$arr_banned_caps = array_keys ( UM () -> options () -> get ( 'banned_capabilities' ) );
}
2023-07-06 01:56:59 +03:00
// Add locked administrative capabilities.
$arr_banned_caps = array_merge ( $arr_banned_caps , UM () -> options () -> get_default ( 'banned_capabilities' ) );
2023-07-03 23:28:49 +08:00
2023-07-06 01:56:59 +03:00
foreach ( $arr_banned_caps as $cap ) {
2023-07-03 23:28:49 +08:00
/**
* When there's at least one administrator cap added to the user,
* immediately revoke caps and mark as rejected.
*/
if ( $user -> has_cap ( $cap ) ) {
$has_admin_cap = true ;
break ;
2023-07-01 17:48:45 +08:00
}
}
2023-07-06 01:56:59 +03:00
if ( ! $has_admin_cap ) {
/**
* Double-check if *_user_level has been modified with the highest level
* when user has no administrative capabilities.
*/
$user_level = um_user ( $wpdb -> get_blog_prefix () . 'user_level' );
if ( ! empty ( $user_level ) && 10 === absint ( $user_level ) ) {
2023-07-03 16:48:36 +08:00
$has_admin_cap = true ;
2023-07-01 17:48:45 +08:00
}
}
if ( $has_admin_cap ) {
2023-07-06 01:56:59 +03:00
$this -> revoke_caps ( $user );
2023-07-03 23:28:49 +08:00
/**
* Notify Administrators Immediately
*/
if ( UM () -> options () -> get ( 'secure_notify_admins_banned_accounts' ) ) {
$interval = UM () -> options () -> get ( 'secure_notify_admins_banned_accounts__interval' );
if ( 'instant' === $interval ) {
2023-07-06 01:56:59 +03:00
$this -> send_email ( array ( $user_id ) );
2023-07-03 23:28:49 +08:00
}
}
// Destroy Sessions & Redirect.
wp_destroy_current_session ();
wp_logout ();
session_unset ();
2023-07-04 00:18:36 +08:00
$redirect = apply_filters ( 'um_secure_blocked_user_redirect_immediately' , true );
if ( $redirect ) {
$login_url = add_query_arg ( 'err' , 'inactive' , um_get_core_page ( 'login' ) );
wp_safe_redirect ( $login_url );
exit ;
}
2023-07-06 01:56:59 +03:00
} else {
if ( UM () -> options () -> get ( 'display_login_form_notice' ) ) {
update_user_meta ( $user_id , 'um_secure_has_reset_password' , true );
update_user_meta ( $user_id , 'um_secure_has_reset_password__timestamp' , current_time ( 'mysql' ) );
}
}
}
2023-07-04 00:18:36 +08:00
2023-07-06 01:56:59 +03:00
/**
* Secure user capabilities and revoke administrative ones.
*
* @since 2.6.8
*/
public function maybe_set_whitelisted_password ( $user_id ) {
global $wpdb ;
$user = get_userdata ( $user_id );
if ( empty ( $user ) ) {
return ;
2023-07-01 17:48:45 +08:00
}
2023-07-04 00:18:36 +08:00
2023-07-06 01:56:59 +03:00
if ( UM () -> options () -> get ( 'display_login_form_notice' ) ) {
update_user_meta ( $user_id , 'um_secure_has_reset_password' , true );
update_user_meta ( $user_id , 'um_secure_has_reset_password__timestamp' , current_time ( 'mysql' ) );
}
2023-07-01 17:48:45 +08:00
}
/**
2023-07-03 16:48:36 +08:00
* Revoke Caps & Mark rejected as suspicious
2023-07-01 17:48:45 +08:00
*
* @param object $user \WP_User
2023-07-03 19:38:16 +08:00
*
* @since 2.6.8
2023-07-01 17:48:45 +08:00
*/
2023-07-03 15:38:29 +08:00
public function revoke_caps ( $user ) {
2023-07-06 01:56:59 +03:00
$user_agent = '' ;
if ( isset ( $_SERVER [ 'HTTP_USER_AGENT' ] ) ) {
$user_agent = sanitize_text_field ( wp_unslash ( $_SERVER [ 'HTTP_USER_AGENT' ] ) );
2023-07-04 03:57:29 +08:00
}
2023-07-05 12:00:26 +08:00
// Capture details.
2023-07-03 23:28:49 +08:00
$captured = array (
2023-07-05 12:00:26 +08:00
'capabilities' => $user -> allcaps ,
'submitted' => UM () -> form () -> post_form ,
'roles' => $user -> roles ,
2023-07-06 01:56:59 +03:00
'user_agent' => $user_agent ,
'account_status' => get_user_meta ( $user -> ID , 'account_status' , true ),
2023-07-03 23:28:49 +08:00
);
2023-07-06 01:56:59 +03:00
update_user_meta ( $user -> ID , 'um_user_blocked__metadata' , $captured );
2023-07-05 12:00:26 +08:00
2023-07-03 15:38:29 +08:00
$user -> remove_all_caps ();
2023-07-03 23:28:49 +08:00
if ( is_user_logged_in () ) {
UM () -> user () -> set_status ( 'inactive' );
} else {
UM () -> user () -> set_status ( 'rejected' );
}
2023-07-06 01:56:59 +03:00
update_user_meta ( $user -> ID , 'um_user_blocked' , 'suspicious_activity' );
update_user_meta ( $user -> ID , 'um_user_blocked__datetime' , current_time ( 'mysql' ) );
2023-07-03 19:38:16 +08:00
}
/**
2023-07-06 01:56:59 +03:00
* Add callbacks to Schedule Events.
2023-07-03 23:28:49 +08:00
*
* @since 2.6.8
*/
public function schedule_events () {
if ( UM () -> options () -> get ( 'secure_notify_admins_banned_accounts' ) ) {
2023-07-06 01:56:59 +03:00
$notification_interval = UM () -> options () -> get ( 'secure_notify_admins_banned_accounts__interval' );
if ( 'instant' === $notification_interval ) {
return ;
2023-07-04 00:37:17 +08:00
}
2023-07-06 01:56:59 +03:00
if ( 'hourly' === $notification_interval ) {
add_action ( 'um_hourly_scheduled_events' , array ( $this , 'notify_administrators_hourly' ) );
} elseif ( 'daily' === $notification_interval ) {
add_action ( 'um_daily_scheduled_events' , array ( $this , 'notify_administrators_daily' ) );
2023-07-03 23:28:49 +08:00
}
}
}
/**
* Notify Administrators hourly - Suspicious activities in an hour
*
* @since 2.6.8
*/
public function notify_administrators_hourly () {
2023-07-06 01:56:59 +03:00
$args = array (
'fields' => 'ID' ,
'meta_query' => array (
'relation' => 'AND' ,
array (
'key' => 'um_user_blocked__datetime' ,
'value' => gmdate ( 'Y-m-d H:i:s' , strtotime ( '-1 hour' ) ),
'compare' => '>=' ,
'type' => 'DATETIME' ,
2023-07-03 23:28:49 +08:00
),
2023-07-06 01:56:59 +03:00
),
);
2023-07-03 23:28:49 +08:00
2023-07-06 01:56:59 +03:00
$users = new WP_User_Query ( $args );
2023-07-03 23:28:49 +08:00
2023-07-06 01:56:59 +03:00
$this -> send_email ( array_values ( $users -> get_results () ) );
2023-07-03 23:28:49 +08:00
}
/**
* Notify Administrators daily - Today's suspicious activity
*
* @since 2.6.8
*/
public function notify_administrators_daily () {
2023-07-06 01:56:59 +03:00
$args = array (
'fields' => 'ID' ,
'relation' => 'AND' ,
'meta_query' => array (
'relation' => 'AND' ,
array (
'key' => 'um_user_blocked__datetime' ,
'value' => gmdate ( 'Y-m-d H:i:s' , strtotime ( '-1 day' ) ),
'compare' => '>=' ,
'type' => 'DATE' ,
2023-07-03 23:28:49 +08:00
),
2023-07-06 01:56:59 +03:00
array (
'key' => 'um_user_blocked__datetime' ,
'value' => gmdate ( 'Y-m-d H:i:s' , strtotime ( 'now' ) ),
'compare' => '<=' ,
'type' => 'DATE' ,
),
),
);
2023-07-03 23:28:49 +08:00
2023-07-06 01:56:59 +03:00
$users = new WP_User_Query ( $args );
2023-07-03 23:28:49 +08:00
2023-07-06 01:56:59 +03:00
$this -> send_email ( array_values ( $users -> get_results () ) );
2023-07-03 23:28:49 +08:00
}
/**
* Get Email template
*
* @param bool $single Whether the template is for single or multiple user activities
* @param array $profile_urls Profile URLs to include in the email body
*
* @since 2.6.8
*/
public function get_email_template ( $single = true , $profile_urls = array () ) {
$action = '' ;
if ( ! is_user_logged_in () ) {
$action = 'Rejected' ;
} else {
$action = 'Deactivated' ;
}
$body = '' ;
if ( $single ) {
$body = 'This is to inform you that there\'s a suspicious activity with the following account: ' ;
$body .= '<br/>' ;
$body .= '{user_profile_link}' ;
$body .= '<br/><br/>' ;
$body .= 'Due to that we have set the account status to ' . $action . ', Revoked Roles & Destroyed the Login Session.' ;
$body .= '</br>' ;
} else {
$body = 'This is to inform you that there are suspicious activities with the following accounts: ' ;
$body .= '</br>' ;
$body .= '{user_profile_link}' ;
$body .= '</br></br>' ;
$body .= 'Due to that we have set each account\'s status to ' . $action . ', revoked roles & destroyed the login session.' ;
$body .= '</br>' ;
}
$urls = implode ( '</br>' , $profile_urls );
$body = str_replace ( '{user_profile_link}' , $urls , $body );
$body .= '<br/><br/>- Sent via Ultimate Member plugin. ' ;
return $body ;
}
/**
* Send Email
*
* @param array $user_ids User IDs.
*
* @since 2.6.8
*/
public function send_email ( $user_ids = array () ) {
if ( empty ( $user_ids ) ) {
return '' ;
}
$multiple_recipients = array ();
$admins = get_users ( 'role=Administrator' );
foreach ( $admins as $user ) {
$multiple_recipients [] = $user -> user_email ;
}
2023-07-04 02:53:53 +08:00
$subject = _n ( 'Suspicious Account Activity on ' , 'Suspicious Accounts & Activities on ' , count ( $user_ids ), 'ultimate-member' ) . wp_parse_url ( get_site_url () )[ 'host' ];
2023-07-03 23:28:49 +08:00
if ( count ( $user_ids ) <= 1 ) {
2023-07-04 02:53:53 +08:00
$url = UM () -> user () -> get_profile_link ( $user_ids [ 0 ] );
$body = $this -> get_email_template ( true , array ( $url ) );
2023-07-03 23:28:49 +08:00
} else {
$arr_urls = array ();
foreach ( $user_ids as $i => $uid ) {
$arr_urls [] = UM () -> user () -> get_profile_link ( $uid );
}
$body = $this -> get_email_template ( false , $arr_urls );
}
wp_mail ( $multiple_recipients , $subject , $body );
}
2023-07-05 12:00:26 +08:00
}
2023-07-01 17:48:45 +08:00
}