diff --git a/includes/admin/assets/js/um-admin-secure.js b/includes/admin/assets/js/um-admin-secure.js new file mode 100644 index 00000000..fe13e8eb --- /dev/null +++ b/includes/admin/assets/js/um-admin-secure.js @@ -0,0 +1,79 @@ +jQuery(document).on("ready", function(){ + + const scan_results_wrapper = jQuery(".um-secure-scan-results"); + const scan_button_elem = jQuery(".um-secure-scan-content"); + const scan_capabilities = jQuery("input[data-field_id^='banned_capabilities']"); + + var UM_Secure = { + init: function() { + scan_results_wrapper.css({ + 'margin-top': '10px', + 'padding': '10px', + 'padding-bottom': '10px', + 'background-color': '#fff', + 'display': 'block', + 'max-height': '200px', + 'height': '500px', + 'overflow-y': 'scroll', + }); + + scan_button_elem.on("click", function(e){ + UM_Secure.effect(); + e.preventDefault(); + var me = jQuery(this); + me.prop("disabled", true); + scan_results_wrapper.empty(); + + UM_Secure.log( wp.i18n.__( 'Scanning site..', 'ultimate-member' ) ); + + UM_Secure.ajax(''); + + }); + scan_capabilities.on("change", function(){ + scan_button_elem.attr('disabled', true ); + scan_button_elem.after( ' ' + wp.i18n.__( 'You must save the settings before you can run the scan.', 'ultimate-member' ) + '' ); + scan_capabilities.off("change"); + }) + }, + ajax: function( last_capability ) { + let checkedCaps = []; + let checkedCapsInputs = scan_results_wrapper.parents('.um-form-table').find('input[type="checkbox"][data-field_id^="banned_capabilities_"]:checked'); + checkedCapsInputs.each(function (){ + checkedCaps.push( jQuery(this).data('field_id').replace('banned_capabilities_', '') ); + }); + + var request = { + nonce: um_admin_scripts.nonce, + capabilities: checkedCaps, + last_scanned_capability: last_capability, + }; + + wp.ajax.send('um_secure_scan_affected_users', { + data: request, + success: function (response) { + if ( ! response.completed ) { + UM_Secure.ajax( response.last_scanned_capability ); + UM_Secure.log( response.message ); + } else if ( response.completed ) { + scan_results_wrapper.empty(); + UM_Secure.log( response.recommendations ); + scan_results_wrapper.find('.current').removeClass('current'); + scan_button_elem.removeAttr('disabled'); + } + }, + }); + }, + log: function( str ) { + scan_results_wrapper.find('.current').removeClass('current'); + scan_results_wrapper.append( '' + str + '
' ); + }, + effect: function() { + var blink = function(){ + scan_results_wrapper.find(".current").fadeTo(100, 0.1).fadeTo(200, 1.0); + }; + setInterval(blink, 1000); + } + }; + + UM_Secure.init(); +}); diff --git a/includes/admin/class-admin.php b/includes/admin/class-admin.php index 8684b5ae..d2ca2d03 100644 --- a/includes/admin/class-admin.php +++ b/includes/admin/class-admin.php @@ -88,6 +88,11 @@ if ( ! class_exists( 'um\admin\Admin' ) ) { add_filter( 'post_updated_messages', array( &$this, 'post_updated_messages' ) ); } + public function includes() { + $this->notices(); + $this->secure(); + } + function init_variables() { $this->role_meta = apply_filters( @@ -2056,13 +2061,12 @@ if ( ! class_exists( 'um\admin\Admin' ) ) { return $parent_file; } - /** * @since 2.0 * * @return core\Admin_Notices() */ - function notices() { + public function notices() { if ( empty( UM()->classes['admin_notices'] ) ) { UM()->classes['admin_notices'] = new core\Admin_Notices(); } @@ -2072,13 +2076,13 @@ if ( ! class_exists( 'um\admin\Admin' ) ) { /** * @since 2.6.8 * - * @return core\Secure + * @return Secure */ public function secure() { - if ( empty( UM()->classes['admin_secure'] ) ) { - UM()->classes['admin_secure'] = new core\Secure(); + if ( empty( UM()->classes['um\admin\secure'] ) ) { + UM()->classes['um\admin\secure'] = new Secure(); } - return UM()->classes['admin_secure']; + return UM()->classes['um\admin\secure']; } } } diff --git a/includes/admin/core/class-secure.php b/includes/admin/class-secure.php similarity index 71% rename from includes/admin/core/class-secure.php rename to includes/admin/class-secure.php index 3abda370..6f03f854 100644 --- a/includes/admin/core/class-secure.php +++ b/includes/admin/class-secure.php @@ -1,18 +1,28 @@ admin_enqueue()->js_url . 'um-admin-secure.js', array( 'jquery' ), UM_VERSION, true ); + wp_enqueue_script( 'um_admin_secure' ); } /** @@ -68,7 +93,7 @@ if ( ! class_exists( 'um\admin\core\Secure' ) ) { if ( ! empty( $users ) ) { foreach ( $users as $user_id ) { // Get an instance of WP_User_Meta_Session_Tokens - $sessions_manager = \WP_Session_Tokens::get_instance( $user_id ); + $sessions_manager = WP_Session_Tokens::get_instance( $user_id ); // Remove all the session for instance user. $sessions_manager->destroy_all(); @@ -119,7 +144,7 @@ if ( ! class_exists( 'um\admin\core\Secure' ) ) { // Delete blocked meta. delete_user_meta( $user_id, 'um_user_blocked__metadata' ); delete_user_meta( $user_id, 'um_user_blocked' ); - delete_user_meta( $user_id, 'um_user_blocked__datetime' ); + delete_user_meta( $user_id, 'um_user_blocked__timestamp' ); // Don't need to reset a password. update_user_meta( $user_id, 'um_secure_has_reset_password', true ); @@ -144,77 +169,49 @@ if ( ! class_exists( 'um\admin\core\Secure' ) ) { $nonce = wp_create_nonce( 'um-secure-expire-session-nonce' ); $count_users = count_users(); - /** - * UM hook - * - * @type filter - * @title um_secure_register_form_banned_capabilities - * @description Modify banned capabilities for Register forms - * @input_vars - * [{"var":"$capabilities","type":"array","desc":"WordPress Administratrive Capabilities"}] - * @change_log - * ["Since: 2.6.8"] - * @usage - * - * @example - * - */ - $banned_admin_capabilities = apply_filters( - 'um_secure_register_form_banned_capabilities', - array( - 'create_sites', - 'delete_sites', - 'manage_network', - 'manage_sites', - 'manage_network_users', - 'manage_network_plugins', - 'manage_network_themes', - 'manage_network_options', - 'upgrade_network', - 'setup_network', - 'activate_plugins', - 'edit_dashboard', - 'edit_theme_options', - 'export', - 'import', - 'list_users', - 'remove_users', - 'switch_themes', - 'customize', - 'delete_site', - 'update_core', - 'update_plugins', - 'update_themes', - 'install_plugins', - 'install_themes', - 'delete_themes', - 'delete_plugins', - 'edit_plugins', - 'edit_themes', - 'edit_files', - 'edit_users', - 'add_users', - 'create_users', - 'delete_users', - 'level_10', - 'manage_options', - 'promote_users', - ) - ); - - $banned_capabilities = array(); + $banned_capabilities = array(); + $banned_admin_capabilities = UM()->common()->secure()->get_banned_capabilities_list(); foreach ( $banned_admin_capabilities as $cap ) { $banned_capabilities[ $cap ] = $cap; } + $disabled_capabilities = UM()->options()->get_default( 'banned_capabilities' ); + $disabled_capabilities_text = '' . implode( ', ', $disabled_capabilities ) . ''; + + $scanner_content = ''; + $scanner_content .= ''; + $scanner_content .= esc_html__( 'Last scan:', 'ultimate-member' ) . ' '; + $scan_status = get_option( 'um_secure_scan_status' ); + $last_scanned_time = get_option( 'um_secure_last_time_scanned' ); + if ( ! empty( $last_scanned_time ) ) { + $scanner_content .= human_time_diff( strtotime( $last_scanned_time ), strtotime( current_time( 'mysql' ) ) ) . ' ' . esc_html__( 'ago', 'ultimate-member' ); + if ( 'started' === $scan_status ) { + $scanner_content .= ' - ' . esc_html__( 'Not Completed.', 'ultimate-member' ); + } + } else { + $scanner_content .= esc_html__( 'Not Scanned yet.', 'ultimate-member' ); + } + $scanner_content .= ''; + $secure_fields = array( + array( + 'id' => 'banned_capabilities', + 'type' => 'multi_checkbox', + 'multi' => true, + 'columns' => 2, + 'options_disabled' => $disabled_capabilities, + 'options' => $banned_capabilities, + 'label' => __( 'Banned Administrative Capabilities', 'ultimate-member' ), + // translators: %s are disabled default capabilities that are enabled by default. + 'description' => sprintf( __( 'All the above are default Administrator & Super Admin capabilities. When someone tries to inject capabilities to the Account, Profile & Register forms submission, it will be flagged with this option. The %s capabilities are locked to ensure no users will be created with these capabilities.', 'ultimate-member' ), $disabled_capabilities_text ), + ), + array( + 'id' => 'secure_scan_affected_users', + 'type' => 'info_text', + 'label' => __( 'Scanner', 'ultimate-member' ), + 'value' => $scanner_content, + 'description' => __( 'Scan your site to check for vulnerabilities prior to Ultimate Member version 2.6.7 and get recommendations to secure your site.', 'ultimate-member' ), + ), array( 'id' => 'lock_register_forms', 'type' => 'checkbox', @@ -241,28 +238,21 @@ if ( ! class_exists( 'um\admin\core\Secure' ) ) { ); } - $disabled_capabilities = UM()->options()->get_default( 'banned_capabilities' ); - $disabled_capabilities_text = '' . implode( ', ', $disabled_capabilities ) . ''; - $secure_fields = array_merge( $secure_fields, array( array( - 'id' => 'banned_capabilities', - 'type' => 'multi_checkbox', - 'multi' => true, - 'columns' => 2, - 'options_disabled' => UM()->options()->get_default( 'banned_capabilities' ), - 'options' => $banned_capabilities, - 'label' => __( 'Banned Administrative Capabilities', 'ultimate-member' ), - // translators: %s are disabled default capabilities that are enabled by default. - 'description' => sprintf( __( 'All the above are default Administrator & Super Admin capabilities. When someone tries to inject capabilities to the Account, Profile & Register forms submission, it will be flagged with this option. The %s capabilities are locked to ensure no users will be created with these capabilities.', 'ultimate-member' ), $disabled_capabilities_text ), + 'id' => 'secure_ban_admins_accounts', + 'type' => 'checkbox', + 'label' => __( 'Enable ban for administrative capabilities', 'ultimate-member' ), + 'description' => __( ' When someone tries to inject capabilities to the Account, Profile & Register forms submission, it will be banned.', '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' ), + 'conditional' => array( 'secure_ban_admins_accounts', '=', 1 ), ), array( 'id' => 'secure_notify_admins_banned_accounts__interval', @@ -303,7 +293,7 @@ if ( ! class_exists( 'um\admin\core\Secure' ) ) { $is_blocked = um_user( 'um_user_blocked' ); $account_status = um_user( 'account_status' ); if ( ! empty( $is_blocked ) && in_array( $account_status, array( 'rejected', 'inactive' ), true ) ) { - $datetime = um_user( 'um_user_blocked__datetime' ); + $datetime = um_user( 'um_user_blocked__timestamp' ); $val .= '
' . esc_html__( 'Blocked Due to Suspicious Activity', 'ultimate-member' ) . '
'; $nonce = wp_create_nonce( 'um-security-restore-account-nonce-' . $user_id ); $restore_account_url = admin_url( 'users.php?user_id=' . $user_id . '&um_secure_restore_account=1&_wpnonce=' . $nonce ); diff --git a/includes/admin/core/class-admin-notices.php b/includes/admin/core/class-admin-notices.php index 0b50d46f..f1ad8a7c 100644 --- a/includes/admin/core/class-admin-notices.php +++ b/includes/admin/core/class-admin-notices.php @@ -61,6 +61,8 @@ if ( ! class_exists( 'um\admin\core\Admin_Notices' ) ) { //$this->future_changed(); + $this->common_secure(); + /** * UM hook * @@ -522,7 +524,7 @@ if ( ! class_exists( 'um\admin\core\Admin_Notices' ) ) { $messages[1]['content'] = __( 'Other users have been updated.', 'ultimate-member' ); break; case 'um_secure_expire_sessions': - $messages[0]['content'] = __( 'All users sessions have been expired.', 'ultimate-member' ); + $messages[0]['content'] = __( 'All users sessions have been successfully destroyed.', 'ultimate-member' ); break; case 'um_secure_restore': $messages[0]['content'] = __( 'Account has been successfully restored.', 'ultimate-member' ); @@ -786,6 +788,65 @@ if ( ! class_exists( 'um\admin\core\Admin_Notices' ) ) { ); } + public function common_secure() { + if ( UM()->options()->get( 'lock_register_forms' ) ) { + ob_start(); + ?> +

+ Settings > Secure > Lock All Register Forms.', 'ultimate-member' ); ?> +

+ add_notice( + 'common_secure_register', + array( + 'class' => 'warning', + 'message' => $message, + 'dismissible' => true, + ), + 1 + ); + } + + if ( UM()->options()->get( 'display_login_form_notice' ) ) { + ob_start(); + ?> +

+ Settings > Secure > Display Login form notice to reset passwords.', 'ultimate-member' ); ?> +

+ add_notice( + 'common_secure_password_reset', + array( + 'class' => 'warning', + 'message' => $message, + 'dismissible' => true, + ), + 1 + ); + } + + if ( UM()->options()->get( 'secure_ban_admins_accounts' ) ) { + ob_start(); + ?> +

+ Settings > Secure > Enable ban for administrative capabilities.', 'ultimate-member' ); ?> +

+ add_notice( + 'common_secure_suspicious_activity', + array( + 'class' => 'warning', + 'message' => $message, + 'dismissible' => true, + ), + 1 + ); + } + } + public function dismiss_notice() { UM()->admin()->check_ajax_nonce(); diff --git a/includes/ajax/class-init.php b/includes/ajax/class-init.php new file mode 100644 index 00000000..4772a48c --- /dev/null +++ b/includes/ajax/class-init.php @@ -0,0 +1,38 @@ +secure(); + } + + /** + * @since 2.6.8 + * + * @return Secure + */ + public function secure() { + if ( empty( UM()->classes['um\ajax\secure'] ) ) { + UM()->classes['um\ajax\secure'] = new Secure(); + } + return UM()->classes['um\ajax\secure']; + } + } +} diff --git a/includes/ajax/class-secure.php b/includes/ajax/class-secure.php new file mode 100644 index 00000000..43b3cf5c --- /dev/null +++ b/includes/ajax/class-secure.php @@ -0,0 +1,410 @@ +options()->get( 'banned_capabilities' ); + } + + $proceed = false; + $completed = false; + $message = ''; + $scanned_cap = ''; + $user_affected = false; + $count_users = 0; + + $last_element = end( $arr_banned_caps ); + + foreach ( $arr_banned_caps as $cap ) { + + if ( empty( $last_scanned_capability ) ) { + $proceed = true; + } else { + if ( $last_scanned_capability === $cap ) { // if this was the last capability, skip this and proceed the next loop. + $proceed = true; + continue; + } + } + + if ( ! $proceed ) { + continue; + } + + $args = array( + 'capability' => $cap, + 'role__not_in' => array( 'administrator' ), + 'fields' => 'ids', + ); + $wp_user_query = new WP_User_Query( $args ); + $count_users = $wp_user_query->get_total(); + if ( $count_users <= 0 ) { + $message = '`' . esc_html( $cap ) . '` ' . esc_html__( ' is safe ' ) . ''; + } else { + $user_affected = true; + $message = '`' . esc_html( $cap ) . '` ' . sprintf( /* translators: has affected %d user account */ _n( 'has affected %d user account.', ' has affected %d user accounts.', $count_users, 'ultimate-member' ), $count_users ) . ''; + } + + if ( $last_element === $cap ) { + $completed = true; + update_option( 'um_secure_scan_status', 'completed' ); + } + + $scanned_cap = $cap; + + break; + } + + if ( $user_affected ) { + $scan_details['affected_caps'][] = $scanned_cap; + + $scan_details['scanned_caps'][ $scanned_cap ] = array( + 'total_affected_users' => $count_users, + 'users' => $wp_user_query->get_results(), + ); + + if ( ! isset( $scan_details['scanned_caps']['total_all_cap_flagged'] ) ) { + $scan_details['scanned_caps']['total_all_cap_flagged'] = 1; + } else { + ++$scan_details['scanned_caps']['total_all_cap_flagged']; + } + } + + if ( ! isset( $scan_details['scanned_caps']['total_all_affected_users'] ) ) { + $scan_details['scanned_caps']['total_all_affected_users'] = absint( $count_users ); + } else { + $scan_details['scanned_caps']['total_all_affected_users'] = absint( $scan_details['scanned_caps']['total_all_affected_users'] ) + absint( $count_users ); + } + + update_option( 'um_secure_scanned_details', $scan_details ); + + wp_send_json_success( + array( + 'last_scanned_capability' => $scanned_cap, + 'message' => $message, + 'completed' => $completed, + 'recommendations' => $completed ? wp_kses( $this->scan_recommendations(), UM()->get_allowed_html( 'templates' ) ) : '', + ) + ); + } + + /** + * Recommendations after the scan completed. + * + * @return string + */ + public function scan_recommendations() { + global $wp_roles, $wpdb; + $br = '
'; + $check = ''; + $flag = ''; + $warning = ''; + + $all_plugins = get_plugins(); + $active_plugins = apply_filters( 'active_plugins', get_option( 'active_plugins' ) ); + + $content = '-----' . $br . $br; + + $suspicious_accounts = new WP_User_Query( + array( + 'relation' => 'AND', + 'number' => -1, + 'meta_query' => array( + 'relation' => 'OR', + array( + 'key' => 'submitted', + 'value' => sprintf( ':"%s";', $wpdb->prefix . '_capabilities' ), + 'compare' => 'LIKE', + ), + array( + 'key' => 'submitted', + 'value' => sprintf( ':"%s";', $wpdb->prefix . '_user_level' ), + 'compare' => 'LIKE', + ), + array( + 'key' => 'submitted', + 'value' => sprintf( '.*%s";', '_capabilities' ), + 'compare' => 'REGEXP', + ), + array( + 'key' => 'submitted', + 'value' => sprintf( '.*%s";', '_user_level' ), + 'compare' => 'LIKE', + ), + ), + ) + ); + + $suspicious_accounts_count = $suspicious_accounts->get_total(); + $susp_accounts = $suspicious_accounts->get_results(); + + /** + * Disable and Kickout Suspicious accounts. + */ + if ( $suspicious_accounts_count > 0 ) { + $arr_might_lookout_accounts = array(); + $arr_dates_registered = array(); + $arr_suspected_accounts = array(); + if ( ! empty( $susp_accounts ) ) { + foreach ( $susp_accounts as $user ) { + + $arr_suspected_accounts[] = $user->ID; + $arr_dates_registered[] = strtotime( $user->user_registered ); + + if ( $user->__get( 'um_user_blocked' ) ) { + continue; + } + + UM()->common()->secure()->revoke_caps( $user ); + // Get an instance of WP_User_Meta_Session_Tokens + $sessions_manager = WP_Session_Tokens::get_instance( $user->ID ); + // Remove all the session data for all users. + $sessions_manager->destroy_all(); + } + } + + $date_query = array(); + $oldest_date = min( $arr_dates_registered ); + $newest_date = max( $arr_dates_registered ); + + $might_affected_users = new WP_User_Query( + array( + 'number' => -1, + 'relation' => 'AND', + 'date_query' => array( + 'after' => human_time_diff( $oldest_date, strtotime( current_time( 'mysql' ) ) ) . ' ago', + ), + ) + ); + } + + $content .= '' . ( $suspicious_accounts_count > 0 ? $warning : $check ) . __( 'Scan Complete.', 'ultimate-member' ) . ''; + + $option = get_option( 'um_secure_scanned_details', $susp_accounts ); + $scan_details = $option['scanned_caps']; + + if ( $suspicious_accounts_count > 0 ) { + update_option( 'um_secure_found_suspicious_accounts', true ); + $content .= $br . $flag . 'Suspcious Accounts Detected!
'; + $content .= $br . __( 'We have found ', 'ultimate-member' ) . '' . /* translators: %s suspcious account */ sprintf( _n( '%s suspcious account', '%s suspcious accounts', $suspicious_accounts_count, 'ultimate-member' ), $suspicious_accounts_count ) . ' ' . __( 'created on your site via Ultimate Member Forms.', 'ultimate-member' ); + $content .= $br . __( 'We\'ve temporarily disabled the suspcious account(s) for you to take actions.', 'ultimate-member' ); + + if ( $might_affected_users->get_total() > 0 ) { + $od = gmdate( 'F m, Y', $oldest_date ); + $nd = gmdate( 'F m, Y', $newest_date ); + if ( $od !== $nd ) { + $date_registered = $od . ' to ' . $nd; + } else { + $date_registered = $od; + } + $content .= $br . $br . __( 'Also, We\'ve found ', 'ultimate-member' ) . '' . /* translators: %s suspcious account */ sprintf( _n( '%s account', '%s accounts', $might_affected_users->get_total(), 'ultimate-member' ), $might_affected_users->get_total() ) . ' ' . sprintf( _n( 'created on %s when the suspicious account was created.', 'created on %s when the suspicious accounts were created.', $suspicious_accounts_count, 'ultimate-member' ), $date_registered ); + + } + } else { + $content .= $br . 'Suspcious Accounts
'; + $content .= $br . $check . ' No suspicious accounts found.
'; + } + + $content .= $br . $br . __( 'PLEASE READ OUR RECOMMENDATIONS BELOW: ', 'ultimate-member' ) . $br; + + $content .= $br . '
WARNING: Ensure that you\'ve created a full backup of your site as your restoration point before changing anything on your site with our recommendations.
'; + if ( $suspicious_accounts_count > 0 ) { + $lock_register_forms_url = admin_url( 'admin.php?page=um_options&tab=secure&um_secure_lock_register_forms=1&_wpnonce=' . wp_create_nonce( 'um_secure_lock_register_forms' ) ); + $content .= $br . '1. Please temporarily lock all your active Register forms. Click here to lock them now. You can unblock the Register forms later. Just go to Ultimate Member > Settings > Secure > uncheck the option "Lock All Register Forms".'; + $content .= $br . $br; + $suspicious_accounts_url = admin_url( 'users.php?um_status=inactive' ); + + $content .= '2. Review all suspicious accounts and delete them completely. Click here to review accounts.'; + $content .= $br . $br; + + $nonce = wp_create_nonce( 'um-secure-expire-session-nonce' ); + $destroy_all_sessions_url = admin_url( '?um_secure_expire_all_sessions=1&_wpnonce=' . esc_attr( $nonce ) . '&except_me=1' ); + $content .= '3. If accounts are suspicious to you, please destroy all user sessions to logout active users on your site. Click here to Destroy Sessions now'; + + $content .= $br . $br; + $content .= '4. Run a complete scan on your site using third-party Security plugins such as WPScan/Jetpack Protect or WordFence Security.'; + + $content .= $br . $br; + $nonce = wp_create_nonce( 'um-secure-enable-reset-pass-nonce' ); + $reset_pass_sessions_url = admin_url( '?um_secure_enable_reset_password=1&_wpnonce=' . esc_attr( $nonce ) . '&except_me=1' ); + + $content .= '5. Force users to Reset their Passwords. Click here to enable this option. When this option is enabled, users will be asked to reset their passwords(one-time) on the next login in the UM Login form.'; + $content .= $br . $br; + + $content .= '6. Once your site is secured, please create or enable Daily Backups of your server/site. You can contact your hosting provider to assist you on this matter.'; + $content .= $br . $br; + + $content .= '👇 Read more Recommendations below.'; + $content .= $br; + } + + if ( get_option( 'users_can_register' ) ) { + $content .= $br . $flag . 'Default WP Register Form is Enabled'; + $content .= $br . 'The default WordPress Register form is enabled. If you\'re getting Spam User Registrations, we recommend that you enable a Challenge-Response plugin such as our Ultimate Member - ReCaptcha extension.'; + $content .= $br; + } + + $content .= $br . 'Block Disposable Email Addresses/Domains'; + if ( empty( UM()->options()->get( 'blocked_emails' ) ) ) { + $content .= $br . $flag . 'You are not blocking email addresses or disposable email domains that are mostly used for Spam Account Registrations. You can get the list of disposable email domains from this repository and then add them to Blocked Email Addresses options.'; + $content .= $br; + } else { + $content .= $br . 'The default WordPress Register form is enabled. If you\'re getting Spam User Registrations, we recommend that you enable a Challenge-Response plugin such as our Ultimate Member - ReCaptcha extension.'; + $content .= $br; + } + + $content .= $br . 'Manage User Roles & Capabilities
'; + if ( absint( $scan_details['total_all_affected_users'] ) > 0 ) { + $count_flagged_caps = $scan_details['total_all_cap_flagged']; + $count_users = $scan_details['total_all_affected_users']; + $affected_caps = $option['affected_caps']; + $affected_roles = array(); + + $all_roles = $wp_roles->roles; + $editable_roles = apply_filters( 'editable_roles', $all_roles ); + foreach ( $affected_caps as $cap ) { + foreach ( $editable_roles as $role_key => $role ) { + if ( in_array( $cap, array_keys( $role['capabilities'] ), true ) ) { + $affected_roles[ $role_key ] = $role['name']; + } + } + } + $content .= $br . $flag . 'We have found ' . sprintf( /* translators: */ _n( ' %d user account', ' %d user accounts ', $count_users, 'ultimate-member' ), $count_users ); + $content .= sprintf( /* translators: */ _n( ' affected by %d capability selected in the Banned Administrative Capabilities.', ' affected by one of the %d capabilities selected in the Banned Administrative Capabilities.', $count_flagged_caps, 'ultimate-member' ), $count_flagged_caps ); + + $content .= $br . '- ' . implode( '
- ', $affected_caps ); + + $content .= $br . $br . 'The flagged capabilities are related to the following roles: ' . $br . ' - ' . implode( '
- ', array_values( $affected_roles ) ); + + $content .= $br . $br . 'The affected user accounts will be flagged as suspicious when they update their Profile/Account. If you are not using these capabilities, you may remove them from the roles in the User Role settings. If the roles are not created via Ultimate Member > User Roles, you can use a third-party plugin to modify the role capability.'; + $content .= $br . $br . 'We strongly recommend that you never assign roles with the same capabilities as your administrators for your members/users and that may allow them to access the admin-side features and functionalities of your WordPress site.'; + } else { + $content .= $check . 'Roles & Capabilities are all secured. No users are using the same capabilities as your administrators.'; + } + + $content .= $br . $br . 'Require Strong Passwords'; + if ( ! UM()->options()->get( 'require_strongpass' ) ) { + $content .= $br . $flag . 'We recommend that you enable and require "Strong Password" feature for all the Register, Reset Password & Account forms.'; + $content .= $br . ' Click here to enable.'; + } else { + $content .= $br . $check . 'Your forms are already configured to require of using strong passwords.'; + } + + $content .= $br . $br . 'Secure Site\'s Connection'; + if ( ! isset( $_SERVER['HTTPS'] ) || 'on' !== $_SERVER['HTTPS'] ) { + $content .= $br . $flag . 'Your site cannot provide a secure connection. Please contact your hosting provider to enable SSL certifications on your server.'; + } + + $content .= $br . $br . 'Install Challenge-Response plugin to Login & Register Forms'; + if ( ! array_key_exists( 'um-recaptcha/um-recaptcha.php', $all_plugins ) ) { + if ( ! isset( $_SERVER['HTTPS'] ) || 'on' !== $_SERVER['HTTPS'] ) { + $content .= $br . $flag . 'We recommend that you install and enable ReCaptcha to Login & Register forms.'; + } + } else { + if ( in_array( 'um-recaptcha/um-recaptcha.php', $active_plugins, true ) ) { + $content .= $br . $check . 'Ultimate Member ReCaptcha is actived.'; + $um_forms = get_posts( 'post_type=um_form&numberposts=-1&fields=ids' ); + foreach ( $um_forms as $fid ) { + switch ( get_post_meta( $fid, '_um_mode', true ) ) { + case 'register': + $has_captcha = absint( get_post_meta( $fid, '_um_register_g_recaptcha_status', true ) ); + $content .= $br . '  - Register: ' . get_the_title( $fid ) . ' recaptcha ' . ( 1 === $has_captcha ? ' is enabled ' . $check : 'is disabled ' . $flag ); + break; + case 'login': + $has_captcha = absint( get_post_meta( $fid, '_um_login_g_recaptcha_status', true ) ); + $content .= $br . '  - Login: ' . get_the_title( $fid ) . ' recaptcha ' . ( 1 === $has_captcha ? ' is enabled ' . $check : 'is disabled ' . $flag ); + break; + } + } + $reset_pass_form = absint( UM()->options()->get( 'g_recaptcha_password_reset' ) ); + $content .= $br . '  - Reset Password Form\'s recaptcha ' . ( 1 === $reset_pass_form ? ' is enabled ' . $check : 'is disabled ' . $flag ); + + } elseif ( array_key_exists( 'um-recaptcha/um-recaptcha.php', $all_plugins ) ) { + $content .= $br . $flag . 'Ultimate Member ReCaptcha is installed but not activated.'; + } else { + $content .= $br . $flag . 'We recommend that you install and enable ReCaptcha to Login & Register forms.'; + } + } + + $update_plugins = get_site_transient( 'update_plugins' ); + $update_themes = get_site_transient( 'update_themes' ); + $update_wp_core = get_site_transient( 'update_core' ); + global $wp_version; + $content .= $br . $br . 'Keep Themes & Plugins up to date.'; + $content .= $br . __( 'It is important that you update your themes/plugins if the theme/plugin creators update is aimed at fixing security, bug and vulnerability issues. It is not a good idea to ignore available updates as this may give hackers an advantage when trying to access your website.', 'ultimate-member' ); + + if ( isset( $update_plugins->response ) && ! empty( $update_plugins->response ) ) { + $content .= $br . $br . $flag . sprintf( /* translators: */ _n( 'There\'s %d plugin that requires an update.', 'There are %d plugins that require updates', count( $update_plugins->response ), 'ultimate-member' ), count( $update_plugins->response ) ) . ' Update Plugins Now'; + foreach ( $update_plugins->response as $plugin_name => $data ) { + $content .= $br . '  - ' . $plugin_name; + } + } else { + $content .= $br . $br . $check . __( 'Plugins are up to date.', 'ultimate-member' ); + } + + if ( isset( $update_themes->response ) && ! empty( $update_themes->response ) ) { + $content .= $br . $br . $flag . sprintf( /* translators: */ _n( 'There\'s %d theme that requires an update.', 'There are %d themes that require updates', count( $update_plugins->response ), 'ultimate-member' ), count( $update_plugins->response ) ) . ' Update Themes Now'; + foreach ( $update_themes->response as $theme_name => $data ) { + $content .= $br . '  - ' . $theme_name; + } + } else { + $content .= $br . $br . $check . __( 'Themes are up to date.', 'ultimate-member' ); + } + + if ( isset( $update_themes->current ) && $wp_version !== $update_themes->current ) { + $content .= $br . $br . $flag . __( 'There\'s a new version of WordPress.', 'ultimate-member' ) . 'Update WordPress Now'; + } else { + $content .= $br . $br . $check . __( 'You\'re using the latest version of WordPress', 'ultimate-member' ) . '(' . esc_attr( $wp_version ) . ')'; + } + + $content .= $br . $br . __( 'That\'s all. If you have any recommendation on how to secure your site or have questions, please contact us on our feedback page. ', 'ultimate-member' ); + + update_option( 'um_secure_scan_result_content', $content ); + + return $content; + } +} diff --git a/includes/class-config.php b/includes/class-config.php index aaccca73..26259166 100644 --- a/includes/class-config.php +++ b/includes/class-config.php @@ -495,10 +495,18 @@ if ( ! class_exists( 'um\Config' ) ) { 'body' => '{display_name} has just deleted their {site_name} account.', 'description' => __('Whether to receive notification when an account is deleted','ultimate-member'), 'recipient' => 'admin' - ) + ), + 'suspicious-activity' => array( + 'key' => 'suspicious-activity', + 'title' => __( 'Secure: Suspicious Account Activity', 'ultimate-member' ), + 'subject' => __( '[{site_name}] Suspicious Account Activity', 'ultimate-member' ), + 'body' => 'This is to inform you that there are suspicious activities with the following accounts: {user_profile_link}', + 'description' => __( 'Whether to receive notification when suspicious account activity is detected.', 'ultimate-member' ), + 'recipient' => 'admin', + 'default_active' => true, + ), ) ); - //settings defaults $this->settings_defaults = array( 'restricted_access_post_metabox' => array( 'post' => 1, 'page' => 1 ), @@ -577,6 +585,7 @@ if ( ! class_exists( 'um\Config' ) ) { 'activation_link_expiry_time' => '', 'lock_register_forms' => false, 'display_login_form_notice' => false, + 'secure_ban_admins_accounts' => false, 'banned_capabilities' => array( 'manage_options', 'promote_users', 'level_10' ), 'secure_notify_admins_banned_accounts' => false, 'secure_notify_admins_banned_accounts__interval' => 'instant', diff --git a/includes/class-init.php b/includes/class-init.php index a5a7d43f..2e40ea66 100644 --- a/includes/class-init.php +++ b/includes/class-init.php @@ -527,7 +527,7 @@ if ( ! class_exists( 'UM' ) ) { } //run setup - $this->common()->create_post_types(); + $this->common()->cpt()->create_post_types(); $this->setup()->run_setup(); } @@ -549,10 +549,11 @@ if ( ! class_exists( 'UM' ) ) { */ public function includes() { - $this->common(); + $this->common()->includes(); $this->access(); if ( $this->is_request( 'ajax' ) ) { + $this->ajax()->includes(); $this->admin(); $this->ajax_init(); $this->admin_ajax_hooks(); @@ -565,6 +566,7 @@ if ( ! class_exists( 'UM' ) ) { $this->plugin_updater(); $this->theme_updater(); } elseif ( $this->is_request( 'admin' ) ) { + $this->admin()->includes(); $this->admin(); $this->admin_menu(); $this->admin_upgrade(); @@ -572,8 +574,6 @@ if ( ! class_exists( 'UM' ) ) { $this->columns(); $this->admin_enqueue(); $this->metabox(); - $this->admin()->notices(); - $this->admin()->secure(); $this->users(); $this->dragdrop(); $this->admin_gdpr(); @@ -581,6 +581,7 @@ if ( ! class_exists( 'UM' ) ) { $this->plugin_updater(); $this->theme_updater(); } elseif ( $this->is_request( 'frontend' ) ) { + $this->frontend()->includes(); $this->enqueue(); $this->account(); $this->password(); @@ -651,21 +652,6 @@ if ( ! class_exists( 'UM' ) ) { return $this->classes['blocks']; } - - /** - * @since 2.6.7 - * - * @return um\core\Secure() - */ - public function secure() { - if ( empty( $this->classes['secure'] ) ) { - $this->classes['secure'] = new um\core\Secure(); - } - - return $this->classes['secure']; - } - - /** * Get extension API * @@ -704,19 +690,43 @@ if ( ! class_exists( 'UM' ) ) { return $this->classes[ $key ]; } + /** + * @since 2.6.8 + * + * @return um\ajax\Init + */ + public function ajax() { + if ( empty( $this->classes['um\ajax\init'] ) ) { + $this->classes['um\ajax\init'] = new um\ajax\Init(); + } + + return $this->classes['um\ajax\init']; + } /** * @since 2.0 + * @since 2.6.8 changed namespace and class content. * - * @return um\core\Common() + * @return um\common\Init */ - function common() { - if ( empty( $this->classes['common'] ) ) { - $this->classes['common'] = new um\core\Common(); + public function common() { + if ( empty( $this->classes['um\common\init'] ) ) { + $this->classes['um\common\init'] = new um\common\Init(); } - return $this->classes['common']; + return $this->classes['um\common\init']; } + /** + * @since 2.6.8 + * + * @return um\frontend\Init + */ + public function frontend() { + if ( empty( $this->classes['um\frontend\init'] ) ) { + $this->classes['um\frontend\init'] = new um\frontend\Init(); + } + return $this->classes['um\frontend\init']; + } /** * @since 2.0 @@ -776,7 +786,6 @@ if ( ! class_exists( 'UM' ) ) { new um\core\AJAX_Common(); } - /** * @since 2.0.30 */ @@ -791,9 +800,9 @@ if ( ! class_exists( 'UM' ) ) { /** * @since 2.0 * - * @return um\admin\Admin() + * @return um\admin\Admin */ - function admin() { + public function admin() { if ( empty( $this->classes['admin'] ) ) { $this->classes['admin'] = new um\admin\Admin(); } diff --git a/includes/common/class-cpt.php b/includes/common/class-cpt.php new file mode 100644 index 00000000..fe3828f1 --- /dev/null +++ b/includes/common/class-cpt.php @@ -0,0 +1,92 @@ + array( + 'name' => __( 'Forms', 'ultimate-member' ), + 'singular_name' => __( 'Form', 'ultimate-member' ), + 'add_new' => __( 'Add New', 'ultimate-member' ), + 'add_new_item' => __( 'Add New Form', 'ultimate-member' ), + 'edit_item' => __( 'Edit Form', 'ultimate-member' ), + 'not_found' => __( 'You did not create any forms yet', 'ultimate-member' ), + 'not_found_in_trash' => __( 'Nothing found in Trash', 'ultimate-member' ), + 'search_items' => __( 'Search Forms', 'ultimate-member' ), + ), + 'capabilities' => array( + 'edit_post' => 'manage_options', + 'read_post' => 'manage_options', + 'delete_post' => 'manage_options', + 'edit_posts' => 'manage_options', + 'edit_others_posts' => 'manage_options', + 'delete_posts' => 'manage_options', + 'publish_posts' => 'manage_options', + 'read_private_posts' => 'manage_options', + ), + 'show_ui' => true, + 'show_in_menu' => false, + 'public' => false, + 'show_in_rest' => true, + 'supports' => array( 'title' ), + ) + ); + + if ( UM()->options()->get( 'members_page' ) ) { + register_post_type( + 'um_directory', + array( + 'labels' => array( + 'name' => __( 'Member Directories', 'ultimate-member' ), + 'singular_name' => __( 'Member Directory', 'ultimate-member' ), + 'add_new' => __( 'Add New', 'ultimate-member' ), + 'add_new_item' => __( 'Add New Member Directory', 'ultimate-member' ), + 'edit_item' => __( 'Edit Member Directory', 'ultimate-member' ), + 'not_found' => __( 'You did not create any member directories yet', 'ultimate-member' ), + 'not_found_in_trash' => __( 'Nothing found in Trash', 'ultimate-member' ), + 'search_items' => __( 'Search Member Directories', 'ultimate-member' ), + ), + 'capabilities' => array( + 'edit_post' => 'manage_options', + 'read_post' => 'manage_options', + 'delete_post' => 'manage_options', + 'edit_posts' => 'manage_options', + 'edit_others_posts' => 'manage_options', + 'delete_posts' => 'manage_options', + 'publish_posts' => 'manage_options', + 'read_private_posts' => 'manage_options', + ), + 'show_ui' => true, + 'show_in_menu' => false, + 'public' => false, + 'show_in_rest' => true, + 'supports' => array( 'title' ), + ) + ); + } + } + } +} diff --git a/includes/common/class-init.php b/includes/common/class-init.php new file mode 100644 index 00000000..b33146f1 --- /dev/null +++ b/includes/common/class-init.php @@ -0,0 +1,64 @@ +cpt()->hooks(); + $this->screen(); + $this->secure()->hooks(); + } + + /** + * @since 2.6.8 + * + * @return CPT + */ + public function cpt() { + if ( empty( UM()->classes['um\common\cpt'] ) ) { + UM()->classes['um\common\cpt'] = new CPT(); + } + return UM()->classes['um\common\cpt']; + } + + /** + * @since 2.6.8 + * + * @return Screen + */ + public function screen() { + if ( empty( UM()->classes['um\common\screen'] ) ) { + UM()->classes['um\common\screen'] = new Screen(); + } + return UM()->classes['um\common\screen']; + } + + /** + * @since 2.6.8 + * + * @return Secure + */ + public function secure() { + if ( empty( UM()->classes['um\common\secure'] ) ) { + UM()->classes['um\common\secure'] = new Secure(); + } + return UM()->classes['um\common\secure']; + } + } +} diff --git a/includes/common/class-screen.php b/includes/common/class-screen.php new file mode 100644 index 00000000..494cdae8 --- /dev/null +++ b/includes/common/class-screen.php @@ -0,0 +1,44 @@ +options()->get( 'secure_ban_admins_accounts' ) ) { + return; + } + + if ( UM()->options()->get( 'secure_notify_admins_banned_accounts' ) ) { + $notification_interval = UM()->options()->get( 'secure_notify_admins_banned_accounts__interval' ); + if ( 'instant' === $notification_interval ) { + return; + } + + 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' ) ); + } + } + } + + /** + * Notify Administrators hourly - Suspicious activities in an hour + * + * @since 2.6.8 + */ + public function notify_administrators_hourly() { + $user_ids = get_users( + array( + 'fields' => 'ids', + 'meta_query' => array( + 'relation' => 'AND', + array( + 'key' => 'um_user_blocked__timestamp', + 'value' => gmdate( 'Y-m-d H:i:s', strtotime( '-1 hour' ) ), + 'compare' => '>=', + 'type' => 'DATETIME', + ), + ), + ) + ); + + $this->send_notification( $user_ids ); + } + + /** + * Notify Administrators daily - Today's suspicious activity + * + * @since 2.6.8 + */ + public function notify_administrators_daily() { + $user_ids = get_users( + array( + 'fields' => 'ids', + 'relation' => 'AND', + 'meta_query' => array( + 'relation' => 'AND', + array( + 'key' => 'um_user_blocked__timestamp', + 'value' => gmdate( 'Y-m-d H:i:s', strtotime( '-1 day' ) ), + 'compare' => '>=', + 'type' => 'DATE', + ), + array( + 'key' => 'um_user_blocked__timestamp', + 'value' => gmdate( 'Y-m-d H:i:s', strtotime( 'now' ) ), + 'compare' => '<=', + 'type' => 'DATE', + ), + ), + ) + ); + + $this->send_notification( $user_ids ); + } + + public function send_notification( $user_ids ) { + $banned_profile_links = ''; + foreach ( $user_ids as $uid ) { + um_fetch_user( $uid ); + $banned_profile_links .= UM()->user()->get_profile_link( $uid ) . ' ' . um_user( 'account_status' ) . '
'; + } + um_reset_user(); + + $emails = um_multi_admin_email(); + if ( ! empty( $emails ) ) { + foreach ( $emails as $email ) { + UM()->mail()->send( + $email, + 'suspicious-activity', + array( + 'admin' => true, + 'tags' => array( + 'banned_profile_links', + ), + 'tags_replace' => array( + $banned_profile_links, + ), + ) + ); + } + } + } + + /** + * Get the banned capabilities list. + * + * @return array + */ + public function get_banned_capabilities_list() { + /** + * Filters the banned capabilities for UM Register forms. + * + * @param {array} $capabilities WordPress Administrative Capabilities. + * + * @return {array} Banned admin capabilities. + * + * @since 2.6.8 + * @hook um_secure_register_form_banned_capabilities + * + * @example Added `read` capability as banned. + * function my_banned_capabilities( $capabilities ) { + * $capabilities[] = 'read'; + * return $capabilities; + * } + * add_filter( 'um_secure_register_form_banned_capabilities', 'my_banned_capabilities' ); + */ + $banned_admin_capabilities = apply_filters( + 'um_secure_register_form_banned_capabilities', + array( + 'create_sites', + 'delete_sites', + 'manage_network', + 'manage_sites', + 'manage_network_users', + 'manage_network_plugins', + 'manage_network_themes', + 'manage_network_options', + 'upgrade_network', + 'setup_network', + 'activate_plugins', + 'edit_dashboard', + 'edit_theme_options', + 'export', + 'import', + 'list_users', + 'remove_users', + 'switch_themes', + 'customize', + 'delete_site', + 'update_core', + 'update_plugins', + 'update_themes', + 'install_plugins', + 'install_themes', + 'delete_themes', + 'delete_plugins', + 'edit_plugins', + 'edit_themes', + 'edit_files', + 'edit_users', + 'add_users', + 'create_users', + 'delete_users', + 'level_10', + 'manage_options', + 'promote_users', + ) + ); + return $banned_admin_capabilities; + } + + /** + * Revoke Caps & Mark rejected as suspicious + * + * @param WP_User $user + * + * @since 2.6.8 + */ + public function revoke_caps( $user ) { + $user_agent = ''; + if ( isset( $_SERVER['HTTP_USER_AGENT'] ) ) { + $user_agent = sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); + } + // Capture details. + $captured = array( + 'capabilities' => $user->allcaps, + 'submitted' => ! empty( UM()->form()->post_form ) ? UM()->form()->post_form : '', + 'roles' => $user->roles, + 'user_agent' => $user_agent, + 'account_status' => get_user_meta( $user->ID, 'account_status', true ), + ); + update_user_meta( $user->ID, 'um_user_blocked__metadata', $captured ); + + $user->remove_all_caps(); + $user->update_user_level_from_caps(); + um_fetch_user( $user->ID ); + if ( is_user_logged_in() ) { + UM()->user()->set_status( 'inactive' ); + } else { + UM()->user()->set_status( 'rejected' ); + } + um_reset_user(); + update_user_meta( $user->ID, 'um_user_blocked', 'suspicious_activity' ); + update_user_meta( $user->ID, 'um_user_blocked__timestamp', current_time( 'mysql' ) ); + } + } +} diff --git a/includes/core/class-common.php b/includes/core/class-common.php deleted file mode 100644 index 77f6e7f9..00000000 --- a/includes/core/class-common.php +++ /dev/null @@ -1,118 +0,0 @@ - array( - 'name' => __( 'Forms', 'ultimate-member' ), - 'singular_name' => __( 'Form', 'ultimate-member' ), - 'add_new' => __( 'Add New', 'ultimate-member' ), - 'add_new_item' => __( 'Add New Form', 'ultimate-member' ), - 'edit_item' => __( 'Edit Form', 'ultimate-member' ), - 'not_found' => __( 'You did not create any forms yet', 'ultimate-member' ), - 'not_found_in_trash' => __( 'Nothing found in Trash', 'ultimate-member' ), - 'search_items' => __( 'Search Forms', 'ultimate-member' ), - ), - 'capabilities' => array( - 'edit_post' => 'manage_options', - 'read_post' => 'manage_options', - 'delete_post' => 'manage_options', - 'edit_posts' => 'manage_options', - 'edit_others_posts' => 'manage_options', - 'delete_posts' => 'manage_options', - 'publish_posts' => 'manage_options', - 'read_private_posts' => 'manage_options', - ), - 'show_ui' => true, - 'show_in_menu' => false, - 'public' => false, - 'show_in_rest' => true, - 'supports' => array( 'title' ), - ) ); - - if ( UM()->options()->get( 'members_page' ) || ! get_option( 'um_options' ) ) { - - register_post_type( 'um_directory', array( - 'labels' => array( - 'name' => __( 'Member Directories', 'ultimate-member' ), - 'singular_name' => __( 'Member Directory', 'ultimate-member' ), - 'add_new' => __( 'Add New', 'ultimate-member' ), - 'add_new_item' => __( 'Add New Member Directory', 'ultimate-member' ), - 'edit_item' => __( 'Edit Member Directory', 'ultimate-member' ), - 'not_found' => __( 'You did not create any member directories yet', 'ultimate-member' ), - 'not_found_in_trash' => __( 'Nothing found in Trash', 'ultimate-member' ), - 'search_items' => __( 'Search Member Directories', 'ultimate-member' ), - ), - 'capabilities' => array( - 'edit_post' => 'manage_options', - 'read_post' => 'manage_options', - 'delete_post' => 'manage_options', - 'edit_posts' => 'manage_options', - 'edit_others_posts' => 'manage_options', - 'delete_posts' => 'manage_options', - 'publish_posts' => 'manage_options', - 'read_private_posts' => 'manage_options', - ), - 'show_ui' => true, - 'show_in_menu' => false, - 'public' => false, - 'show_in_rest' => true, - 'supports' => array( 'title' ), - ) ); - - } - - } - } -} \ No newline at end of file diff --git a/includes/frontend/class-init.php b/includes/frontend/class-init.php new file mode 100644 index 00000000..21eff21a --- /dev/null +++ b/includes/frontend/class-init.php @@ -0,0 +1,38 @@ +secure(); + } + + /** + * @since 2.6.8 + * + * @return Secure + */ + public function secure() { + if ( empty( UM()->classes['um\frontend\secure'] ) ) { + UM()->classes['um\frontend\secure'] = new Secure(); + } + return UM()->classes['um\frontend\secure']; + } + } +} diff --git a/includes/core/class-secure.php b/includes/frontend/class-secure.php similarity index 56% rename from includes/core/class-secure.php rename to includes/frontend/class-secure.php index 55f107f5..e2877e46 100644 --- a/includes/core/class-secure.php +++ b/includes/frontend/class-secure.php @@ -1,27 +1,26 @@ options()->get( 'secure_ban_admins_accounts' ) ) { + return; + } + /** * Checks the integrity of Current User's Capabilities */ @@ -79,7 +77,7 @@ if ( ! class_exists( 'um\core\Secure' ) ) { echo "

"; echo wp_kses( sprintf( - // translators: One-time change requires you to reset your password + // translators: One-time change requires you to reset your password __( 'Important: Your password has expired. This (one-time) change requires you to reset your password. Please click here to reset your password via Email.', 'ultimate-member' ), um_get_core_page( 'password-reset' ) ), @@ -125,10 +123,9 @@ if ( ! class_exists( 'um\core\Secure' ) ) { /** * Block all UM Register form submissions. * - * @param array $args Form settings. * @since 2.6.8 */ - public function block_register_forms( $args ) { + public function block_register_forms() { if ( UM()->options()->get( 'lock_register_forms' ) ) { $login_url = add_query_arg( 'notice', 'maintenance', um_get_core_page( 'login' ) ); nocache_headers(); @@ -186,6 +183,8 @@ if ( ! class_exists( 'um\core\Secure' ) ) { * Secure user capabilities and revoke administrative ones. * * @since 2.6.8 + * + * @param int $user_id */ public function secure_user_capabilities( $user_id ) { global $wpdb; @@ -229,14 +228,14 @@ if ( ! class_exists( 'um\core\Secure' ) ) { } if ( $has_admin_cap ) { - $this->revoke_caps( $user ); + UM()->common()->secure()->revoke_caps( $user ); /** * 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_id ) ); + UM()->common()->secure()->send_notification( array( $user_id ) ); } } @@ -259,12 +258,13 @@ if ( ! class_exists( 'um\core\Secure' ) ) { } /** - * Secure user capabilities and revoke administrative ones. + * Set meta (no need to reset his password) if the user is a new registered. * * @since 2.6.8 + * + * @param int $user_id */ public function maybe_set_whitelisted_password( $user_id ) { - global $wpdb; $user = get_userdata( $user_id ); if ( empty( $user ) ) { return; @@ -275,187 +275,5 @@ if ( ! class_exists( 'um\core\Secure' ) ) { update_user_meta( $user_id, 'um_secure_has_reset_password__timestamp', current_time( 'mysql' ) ); } } - - /** - * Revoke Caps & Mark rejected as suspicious - * - * @param object $user \WP_User - * - * @since 2.6.8 - */ - public function revoke_caps( $user ) { - $user_agent = ''; - if ( isset( $_SERVER['HTTP_USER_AGENT'] ) ) { - $user_agent = sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); - } - // Capture details. - $captured = array( - 'capabilities' => $user->allcaps, - 'submitted' => UM()->form()->post_form, - 'roles' => $user->roles, - 'user_agent' => $user_agent, - 'account_status' => get_user_meta( $user->ID, 'account_status', true ), - ); - update_user_meta( $user->ID, 'um_user_blocked__metadata', $captured ); - - $user->remove_all_caps(); - if ( is_user_logged_in() ) { - UM()->user()->set_status( 'inactive' ); - } else { - UM()->user()->set_status( 'rejected' ); - } - update_user_meta( $user->ID, 'um_user_blocked', 'suspicious_activity' ); - update_user_meta( $user->ID, 'um_user_blocked__datetime', current_time( 'mysql' ) ); - } - - /** - * Add callbacks to Schedule Events. - * - * @since 2.6.8 - */ - public function schedule_events() { - if ( UM()->options()->get( 'secure_notify_admins_banned_accounts' ) ) { - $notification_interval = UM()->options()->get( 'secure_notify_admins_banned_accounts__interval' ); - if ( 'instant' === $notification_interval ) { - return; - } - - 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' ) ); - } - } - } - - /** - * Notify Administrators hourly - Suspicious activities in an hour - * - * @since 2.6.8 - */ - public function notify_administrators_hourly() { - $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() { - $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; - } - - $subject = _n( 'Suspicious Account Activity on ', 'Suspicious Accounts & Activities on ', count( $user_ids ), 'ultimate-member' ) . wp_parse_url( get_site_url() )['host']; - - if ( count( $user_ids ) <= 1 ) { - $url = UM()->user()->get_profile_link( $user_ids[0] ); - $body = $this->get_email_template( true, array( $url ) ); - } 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 ); - - } } } diff --git a/templates/email/suspicious-activity.php b/templates/email/suspicious-activity.php new file mode 100644 index 00000000..3f60c14b --- /dev/null +++ b/templates/email/suspicious-activity.php @@ -0,0 +1,36 @@ + +

+ +
+
{site_name}
+ +
+
+ +
+ +
This is to inform you that there are suspicious activities with the following account(s):
+
{banned_profile_links}
+
Due to that we have set each account(s) status to rejected or deactivated, revoked roles & destroyed the login session.
+ +
+ +
+ +
- Sent via Ultimate Member plugin.
+ +
+ +