From 85b08f45b35253e246624b8452f34e13bc457e43 Mon Sep 17 00:00:00 2001 From: Champ Camba Date: Mon, 3 Jul 2023 23:28:49 +0800 Subject: [PATCH] Add Notification with Schedules --- includes/core/class-secure.php | 305 ++++++++++++++++++++++++++++++--- 1 file changed, 284 insertions(+), 21 deletions(-) diff --git a/includes/core/class-secure.php b/includes/core/class-secure.php index 058fc7fe..a2343cf8 100644 --- a/includes/core/class-secure.php +++ b/includes/core/class-secure.php @@ -1,6 +1,8 @@ banned_admin_capabilities as $i => $cap ) { + if ( in_array( $cap, array( 'manage_options', 'promote_users', 'level_10' ), true ) ) { + continue; + } + $banned_admin_capabilities_options[ $cap ] = $cap; + } + $settings['secure']['sections'] = array( '' => @@ -240,6 +282,33 @@ if ( ! class_exists( 'um\core\Secure' ) ) { 'value' => 'Logout Users(' . esc_attr( $count_users['total_users'] ) . ') ', 'description' => __( 'This will logout all users on your site and forces them to reset passwords
when "Display Login form notice to reset passwords" is enabled/checked.', 'ultimate-member' ), ), + array( + 'id' => 'banned_capabilities', + 'type' => 'multi_checkbox', + 'multi' => true, + 'columns' => 2, + 'options' => $banned_admin_capabilities_options, + 'value' => UM()->options()->get( 'banned_capabilities' ) ? array_keys( UM()->options()->get( 'banned_capabilities' ) ) : array_keys( $banned_admin_capabilities_options ), + 'label' => __( 'Banned Administrative Capabilities', 'ultimate-member' ), + 'description' => __( 'All the above are default Administrator & Super Admin capabilities. When someone tries to inject capabilities to the Profile & Register form submission, it will be flagged with this option. The manage_options, promote_users & level_10 capabilities are locked to ensure no users will be created with these capabilities.', 'ultimate-member' ), + ), + array( + 'id' => 'secure_notify_admins_banned_accounts', + 'type' => 'checkbox', + 'label' => __( 'Notify Administrators', 'ultimate-member' ), + 'description' => __( 'When enabled, All administrators will be notified when someone has suspicious activities in Profile & Register forms.', 'ultimate-member' ), + ), + array( + 'id' => 'secure_notify_admins_banned_accounts__interval', + 'type' => 'select', + 'options' => array( + 'instant' => __( 'Send Immediately', 'ultimate-member' ), + 'hourly' => __( 'Hourly', 'ultimate-member' ), + 'daily' => __( 'Daily', 'ultimate-member' ), + ), + 'label' => __( 'Notification Schedule', 'ultimate-member' ), + 'conditional' => array( 'secure_notify_admins_banned_accounts', '=', 1 ), + ), ), ), ); @@ -306,23 +375,33 @@ if ( ! class_exists( 'um\core\Secure' ) ) { * Secure user capabilities and revoke administrative ones * @since 2.6.8 */ - public function secure_user_capabilities( $user_id, $submitted_data, $form_data ) { + public function secure_user_capabilities( $user_id ) { global $wpdb; // Fetch the WP_User object of our user. um_fetch_user( $user_id ); - $user = new \WP_User( $user_id ); - $has_admin_cap = false; - if ( ! empty( $this->banned_admin_capabilities ) ) { - foreach ( $this->banned_admin_capabilities as $i => $cap ) { - /** - * 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; - $this->revoke_caps( $user ); - break; - } + $user = new \WP_User( $user_id ); + $has_admin_cap = false; + $arr_banned_caps = array(); + + if ( UM()->options()->get( 'banned_capabilities' ) ) { + $arr_banned_caps = array_keys( UM()->options()->get( 'banned_capabilities' ) ); + } else { + $arr_banned_caps = $this->banned_admin_capabilities; + } + + // Add locked administratrive capabilities + $arr_banned_caps[] = 'manage_options'; + $arr_banned_caps[] = 'promote_users'; + + foreach ( $arr_banned_caps as $i => $cap ) { + /** + * 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; + $this->revoke_caps( $user ); + break; } } @@ -339,7 +418,24 @@ if ( ! class_exists( 'um\core\Secure' ) ) { } if ( $has_admin_cap ) { - wp_die( esc_html__( 'Security Check!', 'ultimate-member' ) ); + /** + * 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 ) { + $this->send_email( array( $user->get( 'ID' ) ) ); + } + } + + // Destroy Sessions & Redirect. + wp_destroy_current_session(); + wp_logout(); + session_unset(); + $login_url = add_query_arg( 'err', 'inactive', um_get_core_page( 'login' ) ); + wp_safe_redirect( $login_url ); + exit; + } } @@ -352,8 +448,17 @@ if ( ! class_exists( 'um\core\Secure' ) ) { * @since 2.6.8 */ public function revoke_caps( $user ) { + $captured = array( + 'capabilities' => $user->allcaps, + 'submitted' => $_REQUEST, //phpcs:ignore WordPress.Security.NonceVerification + ); + update_user_meta( $user->get( 'ID' ), 'um_user_blocked__metadata', $captured ); $user->remove_all_caps(); - UM()->user()->set_status( 'rejected' ); + if ( is_user_logged_in() ) { + UM()->user()->set_status( 'inactive' ); + } else { + UM()->user()->set_status( 'rejected' ); + } update_user_meta( $user->get( 'ID' ), 'um_user_blocked', 'suspicious_activity' ); update_user_meta( $user->get( 'ID' ), 'um_user_blocked__datetime', current_time( 'mysql' ) ); } @@ -383,6 +488,164 @@ if ( ! class_exists( 'um\core\Secure' ) ) { return $val; } + /** + * Register Events + * + * @since 2.6.8 + */ + public function schedule_events() { + + if ( UM()->options()->get( 'secure_notify_admins_banned_accounts' ) ) { + add_action( 'um_secure_notify_administrator_hourly', array( $this, 'notify_administrators_hourly' ) ); + add_action( 'um_secure_notify_administrator_daily', array( $this, 'notify_administrators_daily' ) ); + if ( ! wp_next_scheduled( 'um_secure_notify_administrator' ) ) { + wp_schedule_event( current_time( 'mysql' ), 'hourly', 'um_secure_notify_administrator_hourly' ); + wp_schedule_event( current_time( 'mysql' ), 'daily', 'um_secure_notify_administrator_daily' ); + } + } + } + + /** + * Notify Administrators hourly - Suspicious activities in an hour + * + * @since 2.6.8 + */ + public function notify_administrators_hourly() { + + $interval = UM()->options()->get( 'secure_notify_admins_banned_accounts__interval' ); + if ( 'hourly' === $interval ) { + $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', + ), + ), + ); + + $users = new \WP_User_Query( $args ); + + $this->send_email( array_values( $users->get_results() ) ); + } + + } + + /** + * Notify Administrators daily - Today's suspicious activity + * + * @since 2.6.8 + */ + public function notify_administrators_daily() { + + $interval = UM()->options()->get( 'secure_notify_admins_banned_accounts__interval' ); + if ( 'daily' === $interval ) { + $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', + ), + array( + 'key' => 'um_user_blocked__datetime', + 'value' => gmdate( 'Y-m-d H:i:s', strtotime( 'now' ) ), + 'compare' => '<=', + 'type' => 'DATE', + ), + ), + ); + + $users = new \WP_User_Query( $args ); + + $this->send_email( array_values( $users->get_results() ) ); + + } + + } + + /** + * 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 .= '
'; + $body .= '{user_profile_link}'; + $body .= '

'; + $body .= 'Due to that we have set the account status to ' . $action . ', Revoked Roles & Destroyed the Login Session.'; + $body .= '
'; + } else { + $body = 'This is to inform you that there are suspicious activities with the following accounts: '; + $body .= '
'; + $body .= '{user_profile_link}'; + $body .= '

'; + $body .= 'Due to that we have set each account\'s status to ' . $action . ', revoked roles & destroyed the login session.'; + $body .= '
'; + } + + $urls = implode( '
', $profile_urls ); + $body = str_replace( '{user_profile_link}', $urls, $body ); + $body .= '

- 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; + } + + if ( count( $user_ids ) <= 1 ) { + $subject = __( 'Suspicious Account Activity on ', 'ultimate-member' ) . wp_parse_url( get_site_url() )['host']; + $url = UM()->user()->get_profile_link( $user_ids[0] ); + $body = $this->get_email_template( true, array( $url ) ); + } else { + $subject = __( 'Suspicious Accounts & Activities on ', 'ultimate-member' ) . wp_parse_url( get_site_url() )['host']; + $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 ); + + } + }