From a2a56c8af76a7f6f8f521fe76d848649496be67f Mon Sep 17 00:00:00 2001
From: Champ Camba
Date: Sat, 1 Jul 2023 17:48:45 +0800
Subject: [PATCH 01/45] Add secure class for security measures
---
includes/class-init.php | 15 ++
includes/core/class-secure.php | 279 +++++++++++++++++++++++++++++++++
2 files changed, 294 insertions(+)
create mode 100644 includes/core/class-secure.php
diff --git a/includes/class-init.php b/includes/class-init.php
index 3c839c9a..7a3a0c27 100644
--- a/includes/class-init.php
+++ b/includes/class-init.php
@@ -607,6 +607,7 @@ if ( ! class_exists( 'UM' ) ) {
$this->gdpr();
$this->member_directory();
$this->blocks();
+ $this->secure();
//if multisite networks active
if ( is_multisite() ) {
@@ -650,6 +651,20 @@ if ( ! class_exists( 'UM' ) ) {
}
+ /**
+ * @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
*
diff --git a/includes/core/class-secure.php b/includes/core/class-secure.php
new file mode 100644
index 00000000..f6f40c88
--- /dev/null
+++ b/includes/core/class-secure.php
@@ -0,0 +1,279 @@
+drop_sessions();
+ wp_safe_redirect( admin_url() );
+ exit;
+ }
+
+ }
+
+ /**
+ * Add Login notice for Reset Password
+ *
+ * @param array $args
+ * @since 2.6.7
+ */
+ public function reset_password_notice( $args ) {
+
+ 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 "";
+ echo wp_kses(
+ sprintf(
+ // 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' )
+ ),
+ array(
+ 'strong' => array(),
+ 'a' => array(
+ 'href' => array(),
+ ),
+ )
+ );
+ echo '
';
+ }
+
+ /**
+ * Add Login notice for Under Maintance
+ *
+ * @param array $args
+ * @since 2.6.7
+ */
+ public function under_maintanance_notice( $args ) {
+
+ if ( ! UM()->options()->get( 'lock_register_forms' ) ) {
+ return;
+ }
+
+ // phpcs:disable WordPress.Security.NonceVerification
+ if ( ! isset( $_REQUEST['notice'] ) || 'maintanance' !== $_REQUEST['notice'] ) {
+ return;
+ }
+ // phpcs:enable WordPress.Security.NonceVerification
+
+ echo "";
+ echo wp_kses(
+ sprintf(
+ // translators: One-time change requires you to reset your password
+ __( 'Important: This site is currently under maintance. Come back soon.', 'ultimate-member' ),
+ um_get_core_page( 'password-reset' )
+ ),
+ array(
+ 'strong' => array(),
+ 'a' => array(
+ 'href' => array(),
+ ),
+ )
+ );
+ echo '
';
+ }
+
+ /**
+ * Register Secure Settings
+ *
+ * @param array $settings
+ * @since 2.6.7
+ */
+ public function add_settings( $settings ) {
+ $nonce = wp_create_nonce( 'um-secure-expire-session-nonce' );
+ $count_users = count_users();
+ $settings['secure']['title'] = __( 'Secure', 'ultimate-member' );
+ $settings['secure']['sections'] =
+ array(
+ '' =>
+ array(
+ 'title' => __( 'Secure Ultimate Member', 'ultimate-member' ),
+ 'fields' => array(
+ array(
+ 'id' => 'lock_register_forms',
+ 'type' => 'checkbox',
+ 'label' => __( 'Lock All Register Forms', 'ultimate-member' ),
+ 'description' => __( 'This prevents all users from registering with Ultimate Member on your site temporarily.', 'ultimate-member' ),
+ ),
+ array(
+ 'id' => 'display_login_form_notice',
+ 'type' => 'checkbox',
+ 'label' => __( 'Display Login form notice to reset their passwords', 'ultimate-member' ),
+ 'description' => __( 'Enforces users to reset their passwords( one-time ) and prevent from entering old password.', 'ultimate-member' ),
+ ),
+ array(
+ 'id' => 'force_reset_passwords',
+ 'type' => 'info_text',
+ 'label' => __( 'Expire All Users Sessions', 'ultimate-member' ),
+ '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 their passwords" is enabled. ', 'ultimate-member' ),
+ ),
+ ),
+ ),
+ );
+
+ return $settings;
+ }
+
+ /**
+ * Block all UM Register form submissions.
+ *
+ * @param array $args Form settings.
+ * @since 2.6.7
+ */
+ public function block_register_forms( $args ) {
+ if ( UM()->options()->get( 'lock_register_forms' ) ) {
+ $login_url = add_query_arg( 'notice', 'maintanance', um_get_core_page( 'login' ) );
+ nocache_headers();
+ wp_safe_redirect( $login_url );
+ exit;
+ }
+ }
+
+ /**
+ * Validate when user has expired password
+ *
+ * @param array $submitted_data
+ * @since 2.6.7
+ */
+ public function login_validate_expired_pass( $submitted_data ) {
+
+ if ( UM()->options()->get( 'display_login_form_notice' ) ) {
+ $has_expired = get_user_meta( um_user( 'ID' ), 'um_secure_has_reset_password', true );
+ if ( ! $has_expired ) {
+ $login_url = add_query_arg( 'notice', 'expired_password', um_get_core_page( 'login' ) );
+ wp_safe_redirect( $login_url );
+ exit;
+ }
+ }
+ }
+
+ /**
+ * Prevent users from using Old Passwords on Password Reset form
+ *
+ * @param object $errors
+ * @param object $user
+ * @since 2.6.7
+ */
+ public function avoid_old_password( $errors, $user ) {
+ $wp_hasher = new \PasswordHash( 8, true );
+
+ if ( isset( $_REQUEST['user_password'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
+ $new_user_pass = $_REQUEST['user_password']; // phpcs:ignore WordPress.Security.NonceVerification
+ if ( $wp_hasher->CheckPassword( $new_user_pass, $user->data->user_pass ) ) {
+ UM()->form()->add_error( 'user_password', __( 'Your new password cannot be same as old password.', 'ultimate-member' ) );
+ $errors->add( 'invalid_old_password', __( 'Your new password cannot be same as old password.', 'ultimate-member' ) );
+ } else {
+ update_user_meta( $user->data->ID, 'um_secure_has_reset_password', true );
+ update_user_meta( $user->data->ID, 'um_secure_has_reset_password__timestamp', current_time( 'mysql' ) );
+ }
+ }
+ }
+
+ /**
+ * Checks user capabilities and remove administrative ones
+ */
+ public function check_user_capabilities( $user_id, $submitted_data, $form_data ) {
+
+ // Fetch the WP_User object of our user.
+ um_fetch_user( $user_id );
+ $user = new \WP_User( $user_id );
+ $has_admin_cap = false;
+ $disallowed_roles = array( 'administrator', 'editor' );
+ foreach ( $disallowed_roles as $role ) {
+ $admin_caps = get_role( $role )->capabilities;
+ foreach ( $admin_caps as $cap ) {
+ if ( user_can( $user_id, $cap ) ) {
+ $has_admin_cap = true;
+ $this->revoke_caps( $cap, $user );
+ }
+ }
+ }
+
+ if ( user_can( $user_id, 'manage_options' ) ) {
+ $this->revoke_caps( $cap, $user );
+ $has_admin_cap = true;
+ }
+
+ $arr_levels = array( 'level_10', 'level_9', 'level_8' );
+ foreach ( $arr_levels as $level ) {
+ if ( user_can( $user_id, $level ) ) {
+ $this->revoke_caps( $level, $user );
+ $has_admin_cap = true;
+ }
+ }
+
+ if ( $has_admin_cap ) {
+ wp_die( esc_html__( 'Security Check!', 'ultimate-member' ) );
+ }
+ }
+
+ /**
+ * Revoka Caps
+ *
+ * @param string $cap Capability slug
+ * @param object $user \WP_User
+ */
+ public function revoke_caps( $cap, $user ) {
+ $user->remove_cap( $cap ); //revoke editable capabilities
+ $user->set_role( 'rejected' ); // Set role to rejected
+ UM()->user()->set_status( 'rejected' ); // Set UM role to rejected
+ }
+
+ }
+
+
+}
From 77cd63ca75e3af8952fafa799c1fb92a2b941385 Mon Sep 17 00:00:00 2001
From: Champ Camba
Date: Sat, 1 Jul 2023 17:52:55 +0800
Subject: [PATCH 02/45] Tweak cap
---
includes/core/class-secure.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/includes/core/class-secure.php b/includes/core/class-secure.php
index f6f40c88..948d393b 100644
--- a/includes/core/class-secure.php
+++ b/includes/core/class-secure.php
@@ -244,7 +244,7 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
}
if ( user_can( $user_id, 'manage_options' ) ) {
- $this->revoke_caps( $cap, $user );
+ $this->revoke_caps( 'manage_options', $user );
$has_admin_cap = true;
}
From fefdc5ecb8ccef4ae5e4b04c784628b79142b0b2 Mon Sep 17 00:00:00 2001
From: Champ Camba
Date: Sat, 1 Jul 2023 18:02:16 +0800
Subject: [PATCH 03/45] Set array_keys to expose capabilities slugs
---
includes/core/class-secure.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/includes/core/class-secure.php b/includes/core/class-secure.php
index 948d393b..86dd3d7f 100644
--- a/includes/core/class-secure.php
+++ b/includes/core/class-secure.php
@@ -234,8 +234,8 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
$has_admin_cap = false;
$disallowed_roles = array( 'administrator', 'editor' );
foreach ( $disallowed_roles as $role ) {
- $admin_caps = get_role( $role )->capabilities;
- foreach ( $admin_caps as $cap ) {
+ $admin_caps = array_keys( get_role( $role )->capabilities );
+ foreach ( $admin_caps as $i => $cap ) {
if ( user_can( $user_id, $cap ) ) {
$has_admin_cap = true;
$this->revoke_caps( $cap, $user );
From 5c1b6d99e1b094c19b416420d0a4011424afd308 Mon Sep 17 00:00:00 2001
From: Champ Camba
Date: Sat, 1 Jul 2023 18:19:02 +0800
Subject: [PATCH 04/45] Tweak text
---
includes/core/class-secure.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/includes/core/class-secure.php b/includes/core/class-secure.php
index 86dd3d7f..15ae12db 100644
--- a/includes/core/class-secure.php
+++ b/includes/core/class-secure.php
@@ -113,7 +113,7 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
echo wp_kses(
sprintf(
// translators: One-time change requires you to reset your password
- __( 'Important: This site is currently under maintance. Come back soon.', 'ultimate-member' ),
+ __( 'Important: This site is currently under maintenance. Please check back soon.', 'ultimate-member' ),
um_get_core_page( 'password-reset' )
),
array(
From 9b3447ab54d076c86932619bc267b664fb42ed07 Mon Sep 17 00:00:00 2001
From: Champ Camba
Date: Mon, 3 Jul 2023 15:38:29 +0800
Subject: [PATCH 05/45] Initial update
---
includes/core/class-secure.php | 73 ++++++++++++++++++++--------------
1 file changed, 43 insertions(+), 30 deletions(-)
diff --git a/includes/core/class-secure.php b/includes/core/class-secure.php
index 15ae12db..baf9ccea 100644
--- a/includes/core/class-secure.php
+++ b/includes/core/class-secure.php
@@ -5,7 +5,6 @@ if ( ! defined( 'ABSPATH' ) ) {
exit;
}
-
if ( ! class_exists( 'um\core\Secure' ) ) {
/**
@@ -13,13 +12,13 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
*
* @package um\core
*
- * @since 2.6.7
+ * @since 2.6.8
*/
class Secure {
/**
* Login constructor.
- * @since 2.6.7
+ * @since 2.6.8
*/
public function __construct() {
add_action( 'admin_init', array( $this, 'admin_init' ) );
@@ -36,9 +35,14 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
add_action( 'validate_password_reset', array( $this, 'avoid_old_password' ), 1, 2 );
- add_action( 'um_after_save_registration_details', array( $this, 'check_user_capabilities' ), 10, 3 );
+ add_action( 'um_after_save_registration_details', array( $this, 'secure_user_capabilities' ), 10, 3 );
}
+ /**
+ * Admin Init
+ *
+ * @since 2.6.8
+ */
public function admin_init() {
if ( isset( $_REQUEST['um_secure_expire_all_sessions'] ) ) {
if ( ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'um-secure-expire-session-nonce' ) ) {
@@ -60,7 +64,7 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
* Add Login notice for Reset Password
*
* @param array $args
- * @since 2.6.7
+ * @since 2.6.8
*/
public function reset_password_notice( $args ) {
@@ -95,7 +99,7 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
* Add Login notice for Under Maintance
*
* @param array $args
- * @since 2.6.7
+ * @since 2.6.8
*/
public function under_maintanance_notice( $args ) {
@@ -130,7 +134,7 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
* Register Secure Settings
*
* @param array $settings
- * @since 2.6.7
+ * @since 2.6.8
*/
public function add_settings( $settings ) {
$nonce = wp_create_nonce( 'um-secure-expire-session-nonce' );
@@ -172,7 +176,7 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
* Block all UM Register form submissions.
*
* @param array $args Form settings.
- * @since 2.6.7
+ * @since 2.6.8
*/
public function block_register_forms( $args ) {
if ( UM()->options()->get( 'lock_register_forms' ) ) {
@@ -187,7 +191,7 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
* Validate when user has expired password
*
* @param array $submitted_data
- * @since 2.6.7
+ * @since 2.6.8
*/
public function login_validate_expired_pass( $submitted_data ) {
@@ -206,7 +210,7 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
*
* @param object $errors
* @param object $user
- * @since 2.6.7
+ * @since 2.6.8
*/
public function avoid_old_password( $errors, $user ) {
$wp_hasher = new \PasswordHash( 8, true );
@@ -215,7 +219,7 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
$new_user_pass = $_REQUEST['user_password']; // phpcs:ignore WordPress.Security.NonceVerification
if ( $wp_hasher->CheckPassword( $new_user_pass, $user->data->user_pass ) ) {
UM()->form()->add_error( 'user_password', __( 'Your new password cannot be same as old password.', 'ultimate-member' ) );
- $errors->add( 'invalid_old_password', __( 'Your new password cannot be same as old password.', 'ultimate-member' ) );
+ $errors->add( 'block_old_password', __( 'Your new password cannot be same as old password.', 'ultimate-member' ) );
} else {
update_user_meta( $user->data->ID, 'um_secure_has_reset_password', true );
update_user_meta( $user->data->ID, 'um_secure_has_reset_password__timestamp', current_time( 'mysql' ) );
@@ -224,35 +228,43 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
}
/**
- * Checks user capabilities and remove administrative ones
+ * Secure user capabilities and revoke administrative ones
*/
- public function check_user_capabilities( $user_id, $submitted_data, $form_data ) {
-
+ public function secure_user_capabilities( $user_id, $submitted_data, $form_data ) {
+ 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;
- $disallowed_roles = array( 'administrator', 'editor' );
+ $disallowed_roles = array( 'administrator' );
foreach ( $disallowed_roles as $role ) {
$admin_caps = array_keys( get_role( $role )->capabilities );
foreach ( $admin_caps as $i => $cap ) {
- if ( user_can( $user_id, $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( $cap, $user );
+ $this->revoke_caps( $user );
+ break;
}
}
}
- if ( user_can( $user_id, 'manage_options' ) ) {
- $this->revoke_caps( 'manage_options', $user );
- $has_admin_cap = true;
- }
-
- $arr_levels = array( 'level_10', 'level_9', 'level_8' );
- foreach ( $arr_levels as $level ) {
- if ( user_can( $user_id, $level ) ) {
- $this->revoke_caps( $level, $user );
- $has_admin_cap = true;
+ /**
+ * Double-check if *_user_level has been modified with the highest level
+ * when user has no administrator capabilities.
+ */
+ $user_level = um_user( $wpdb->get_blog_prefix() . 'user_level' );
+ if ( ! empty( $user_level ) ) {
+ $arr_levels = array( 'level_10' );
+ foreach ( $arr_levels as $level ) {
+ if ( $level === $user_level ) {
+ $this->revoke_caps( $user );
+ $has_admin_cap = true;
+ break;
+ }
}
}
@@ -262,13 +274,14 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
}
/**
- * Revoka Caps
+ * Revoke Caps
*
* @param string $cap Capability slug
* @param object $user \WP_User
*/
- public function revoke_caps( $cap, $user ) {
- $user->remove_cap( $cap ); //revoke editable capabilities
+ public function revoke_caps( $user ) {
+
+ $user->remove_all_caps();
$user->set_role( 'rejected' ); // Set role to rejected
UM()->user()->set_status( 'rejected' ); // Set UM role to rejected
}
From 1313d592518f2017afdc31df58f215a53f651354 Mon Sep 17 00:00:00 2001
From: Champ Camba
Date: Mon, 3 Jul 2023 16:48:36 +0800
Subject: [PATCH 06/45] Add banned capabilities
---
includes/core/class-secure.php | 110 ++++++++++++++++++++++++++-------
1 file changed, 88 insertions(+), 22 deletions(-)
diff --git a/includes/core/class-secure.php b/includes/core/class-secure.php
index baf9ccea..3eaa14e4 100644
--- a/includes/core/class-secure.php
+++ b/includes/core/class-secure.php
@@ -16,6 +16,15 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
*/
class Secure {
+ /**
+ * Banned Administrative Capabilities
+ *
+ * @since 2.6.8
+ *
+ * @var array
+ */
+ private $banned_admin_capabilities = array();
+
/**
* Login constructor.
* @since 2.6.8
@@ -35,6 +44,70 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
add_action( 'validate_password_reset', array( $this, 'avoid_old_password' ), 1, 2 );
+ /**
+ * 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
+ *
+ */
+ $this->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',
+ 'manage_options',
+ 'promote_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',
+ )
+ );
+
add_action( 'um_after_save_registration_details', array( $this, 'secure_user_capabilities' ), 10, 3 );
}
@@ -150,12 +223,12 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
'id' => 'lock_register_forms',
'type' => 'checkbox',
'label' => __( 'Lock All Register Forms', 'ultimate-member' ),
- 'description' => __( 'This prevents all users from registering with Ultimate Member on your site temporarily.', 'ultimate-member' ),
+ 'description' => __( 'This prevents all users from registering with Ultimate Member on your site.', 'ultimate-member' ),
),
array(
'id' => 'display_login_form_notice',
'type' => 'checkbox',
- 'label' => __( 'Display Login form notice to reset their passwords', 'ultimate-member' ),
+ 'label' => __( 'Display Login form notice to reset passwords', 'ultimate-member' ),
'description' => __( 'Enforces users to reset their passwords( one-time ) and prevent from entering old password.', 'ultimate-member' ),
),
array(
@@ -163,7 +236,7 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
'type' => 'info_text',
'label' => __( 'Expire All Users Sessions', 'ultimate-member' ),
'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 their passwords" is enabled. ', 'ultimate-member' ),
+ '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' ),
),
),
),
@@ -233,13 +306,11 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
public function secure_user_capabilities( $user_id, $submitted_data, $form_data ) {
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;
- $disallowed_roles = array( 'administrator' );
- foreach ( $disallowed_roles as $role ) {
- $admin_caps = array_keys( get_role( $role )->capabilities );
- foreach ( $admin_caps as $i => $cap ) {
+ um_fetch_user( 29 );
+ $user = new \WP_User( 29 );
+ $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.
@@ -254,17 +325,13 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
/**
* Double-check if *_user_level has been modified with the highest level
- * when user has no administrator capabilities.
+ * when user has no administrative capabilities.
*/
$user_level = um_user( $wpdb->get_blog_prefix() . 'user_level' );
if ( ! empty( $user_level ) ) {
- $arr_levels = array( 'level_10' );
- foreach ( $arr_levels as $level ) {
- if ( $level === $user_level ) {
- $this->revoke_caps( $user );
- $has_admin_cap = true;
- break;
- }
+ if ( 10 === $user_level ) {
+ $this->revoke_caps( $user );
+ $has_admin_cap = true;
}
}
@@ -274,16 +341,15 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
}
/**
- * Revoke Caps
+ * Revoke Caps & Mark rejected as suspicious
*
* @param string $cap Capability slug
* @param object $user \WP_User
*/
public function revoke_caps( $user ) {
-
$user->remove_all_caps();
- $user->set_role( 'rejected' ); // Set role to rejected
- UM()->user()->set_status( 'rejected' ); // Set UM role to rejected
+ UM()->user()->set_status( 'rejected' );
+ update_user_meta( $user->get( 'ID' ), 'um_user_blocked', 'suspicious_activity' );
}
}
From a7fe0b17f08158d0387fb8ea7e2e142f5faacb02 Mon Sep 17 00:00:00 2001
From: Champ Camba
Date: Mon, 3 Jul 2023 16:54:16 +0800
Subject: [PATCH 07/45] Remove test account
---
includes/core/class-secure.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/includes/core/class-secure.php b/includes/core/class-secure.php
index 3eaa14e4..e6c79fc1 100644
--- a/includes/core/class-secure.php
+++ b/includes/core/class-secure.php
@@ -306,8 +306,8 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
public function secure_user_capabilities( $user_id, $submitted_data, $form_data ) {
global $wpdb;
// Fetch the WP_User object of our user.
- um_fetch_user( 29 );
- $user = new \WP_User( 29 );
+ 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 ) {
From 2ac32d921d75e65bbf0f5312d75e009ef0ba4939 Mon Sep 17 00:00:00 2001
From: Champ Camba
Date: Mon, 3 Jul 2023 19:38:16 +0800
Subject: [PATCH 08/45] Add blocked status & time in the account status column
---
includes/core/class-secure.php | 31 +++++++++++++++++++++++++++++++
1 file changed, 31 insertions(+)
diff --git a/includes/core/class-secure.php b/includes/core/class-secure.php
index e6c79fc1..058fc7fe 100644
--- a/includes/core/class-secure.php
+++ b/includes/core/class-secure.php
@@ -109,6 +109,8 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
);
add_action( 'um_after_save_registration_details', array( $this, 'secure_user_capabilities' ), 10, 3 );
+
+ add_filter( 'manage_users_custom_column', array( $this, 'manage_users_custom_column' ), 10, 3 );
}
/**
@@ -302,6 +304,7 @@ 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 ) {
global $wpdb;
@@ -345,11 +348,39 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
*
* @param string $cap Capability slug
* @param object $user \WP_User
+ *
+ * @since 2.6.8
*/
public function revoke_caps( $user ) {
$user->remove_all_caps();
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' ) );
+ }
+
+ /**
+ * Append blocked status to the `account_status` column rows
+ *
+ * @param string $val Default column row value
+ * @param string $column_name Current column name
+ * @param integer $user_id User ID in loop
+ *
+ * @since 2.6.8
+ *
+ * @return string $val
+ */
+ public function manage_users_custom_column( $val, $column_name, $user_id ) {
+ if ( 'account_status' === $column_name ) {
+ $is_blocked = get_user_meta( $user_id, 'um_user_blocked', true );
+ if ( ! empty( $is_blocked ) ) {
+ $datetime = get_user_meta( $user_id, 'um_user_blocked__datetime', true );
+ $val = $val . '' . __( 'Blocked Due to Suspicious Activity', 'ultimate-member' ) . '
';
+ if ( ! empty( $datetime ) ) {
+ $val = $val . '' . human_time_diff( strtotime( $datetime ), strtotime( current_time( 'mysql' ) ) ) . ' ' . __( 'ago', 'ultimate-member' ) . '
';
+ }
+ }
+ }
+ return $val;
}
}
From 85b08f45b35253e246624b8452f34e13bc457e43 Mon Sep 17 00:00:00 2001
From: Champ Camba
Date: Mon, 3 Jul 2023 23:28:49 +0800
Subject: [PATCH 09/45] 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 );
+
+ }
+
}
From 753db264f8565e62695a078ccff3fa33747fa8af Mon Sep 17 00:00:00 2001
From: Champ Camba
Date: Tue, 4 Jul 2023 00:18:36 +0800
Subject: [PATCH 10/45] Add filter hook for redirecting flagged user
---
includes/core/class-secure.php | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/includes/core/class-secure.php b/includes/core/class-secure.php
index a2343cf8..d740f42a 100644
--- a/includes/core/class-secure.php
+++ b/includes/core/class-secure.php
@@ -432,11 +432,18 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
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;
+ $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;
+ }
+
+ return true;
}
+
+ return false;
}
/**
From 42188baea6e7e81f770ef733b81d6caa2d5093dc Mon Sep 17 00:00:00 2001
From: Champ Camba
Date: Tue, 4 Jul 2023 00:37:17 +0800
Subject: [PATCH 11/45] Fix wp schedule event for email notifications
---
includes/core/class-secure.php | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/includes/core/class-secure.php b/includes/core/class-secure.php
index d740f42a..117c8109 100644
--- a/includes/core/class-secure.php
+++ b/includes/core/class-secure.php
@@ -505,10 +505,15 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
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' ) ) {
+
+ if ( ! wp_next_scheduled( 'um_secure_notify_administrator_hourly' ) ) {
wp_schedule_event( current_time( 'mysql' ), 'hourly', 'um_secure_notify_administrator_hourly' );
+ }
+
+ if ( ! wp_next_scheduled( 'um_secure_notify_administrator_daily' ) ) {
wp_schedule_event( current_time( 'mysql' ), 'daily', 'um_secure_notify_administrator_daily' );
}
+
}
}
From e1c3950fb9f16b72628499840104b602ef53e2a5 Mon Sep 17 00:00:00 2001
From: Champ Camba
Date: Tue, 4 Jul 2023 01:05:54 +0800
Subject: [PATCH 12/45] capture submitted data for flagged users
---
includes/core/class-secure.php | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/includes/core/class-secure.php b/includes/core/class-secure.php
index 117c8109..20fbd3e6 100644
--- a/includes/core/class-secure.php
+++ b/includes/core/class-secure.php
@@ -457,7 +457,8 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
public function revoke_caps( $user ) {
$captured = array(
'capabilities' => $user->allcaps,
- 'submitted' => $_REQUEST, //phpcs:ignore WordPress.Security.NonceVerification
+ 'submitted' => UM()->form()->post_form,
+ 'roles' => $user->roles,
);
update_user_meta( $user->get( 'ID' ), 'um_user_blocked__metadata', $captured );
$user->remove_all_caps();
From 46720f0a3254c0a42c092b482e7a2bf7fa538a49 Mon Sep 17 00:00:00 2001
From: Champ Camba
Date: Tue, 4 Jul 2023 01:42:27 +0800
Subject: [PATCH 13/45] Tweak property visibillity for WP CLI
---
includes/core/class-secure.php | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/includes/core/class-secure.php b/includes/core/class-secure.php
index 20fbd3e6..cc4fb300 100644
--- a/includes/core/class-secure.php
+++ b/includes/core/class-secure.php
@@ -1,8 +1,6 @@
Date: Tue, 4 Jul 2023 02:53:53 +0800
Subject: [PATCH 14/45] Refactor email subject
---
includes/core/class-secure.php | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/includes/core/class-secure.php b/includes/core/class-secure.php
index cc4fb300..3948af22 100644
--- a/includes/core/class-secure.php
+++ b/includes/core/class-secure.php
@@ -512,7 +512,6 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
if ( ! wp_next_scheduled( 'um_secure_notify_administrator_daily' ) ) {
wp_schedule_event( current_time( 'mysql' ), 'daily', 'um_secure_notify_administrator_daily' );
}
-
}
}
@@ -640,12 +639,12 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
$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 ) {
- $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 ) );
+ $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 );
From 8dba1534a0ac553ca6e90eff414b4465ddd4f0f8 Mon Sep 17 00:00:00 2001
From: Champ Camba
Date: Tue, 4 Jul 2023 03:34:19 +0800
Subject: [PATCH 15/45] Add account form to check capabilities integrity
---
includes/core/class-secure.php | 1 +
1 file changed, 1 insertion(+)
diff --git a/includes/core/class-secure.php b/includes/core/class-secure.php
index 3948af22..ac5682cf 100644
--- a/includes/core/class-secure.php
+++ b/includes/core/class-secure.php
@@ -139,6 +139,7 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
add_action( 'um_after_save_registration_details', array( $this, 'secure_user_capabilities' ), 10 );
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 );
+ add_action( 'um_after_user_account_updated', array( $this, 'secure_user_capabilities' ), 1 );
}
}
From 85ebc8b83b113c244222cd792ca8f660b211fce2 Mon Sep 17 00:00:00 2001
From: Champ Camba
Date: Tue, 4 Jul 2023 03:57:29 +0800
Subject: [PATCH 16/45] Include flagged user's browser data in the captured
metadata
---
includes/core/class-secure.php | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/includes/core/class-secure.php b/includes/core/class-secure.php
index ac5682cf..344b2937 100644
--- a/includes/core/class-secure.php
+++ b/includes/core/class-secure.php
@@ -454,10 +454,19 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
* @since 2.6.8
*/
public function revoke_caps( $user ) {
+
+ if ( ! class_exists( '\Browser' ) ) {
+ require_once um_path . 'includes/lib/browser.php';
+ }
+
+ // Detect browser
+ $browser = new \Browser();
+
$captured = array(
'capabilities' => $user->allcaps,
'submitted' => UM()->form()->post_form,
'roles' => $user->roles,
+ 'user_browser' => $browser,
);
update_user_meta( $user->get( 'ID' ), 'um_user_blocked__metadata', $captured );
$user->remove_all_caps();
From a3cdba07cb6ef3f43e19f8b6bfd436a9bce73567 Mon Sep 17 00:00:00 2001
From: Champ Camba
Date: Wed, 5 Jul 2023 12:00:26 +0800
Subject: [PATCH 17/45] Add secure's restore account
---
includes/core/class-secure.php | 99 +++++++++++++++++++++++++++-------
1 file changed, 79 insertions(+), 20 deletions(-)
diff --git a/includes/core/class-secure.php b/includes/core/class-secure.php
index 344b2937..17fe9d3a 100644
--- a/includes/core/class-secure.php
+++ b/includes/core/class-secure.php
@@ -1,6 +1,9 @@
get( 'um_user_blocked__metadata' );
+
+ // Restore Roles.
+ if ( isset( $metadata['roles'] ) ) {
+ foreach ( $metadata['roles'] as $role ) {
+ $user->add_role( $role );
+ }
+ }
+ // Restore Account Status.
+ if ( isset( $metadata['account_status'] ) ) {
+ update_user_meta( $user_id, 'account_status', $metadata['account_status'] );
+ }
+ // Clear Cache
+ UM()->user()->remove_cache( $user_id );
+
+ set_transient( 'um_secure_restore_account_notice_success', 1, 5 );
+
+ wp_safe_redirect( wp_get_referer() );
+ exit;
+
+ }
+
}
/**
@@ -378,19 +416,18 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
global $wpdb;
// Fetch the WP_User object of our user.
um_fetch_user( $user_id );
- $user = new \WP_User( $user_id );
+ $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';
+ $arr_banned_caps[] = 'level_10';
foreach ( $arr_banned_caps as $i => $cap ) {
/**
@@ -459,16 +496,18 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
require_once um_path . 'includes/lib/browser.php';
}
- // Detect browser
+ // Detect browser.
$browser = new \Browser();
-
+ // Capture details.
$captured = array(
- 'capabilities' => $user->allcaps,
- 'submitted' => UM()->form()->post_form,
- 'roles' => $user->roles,
- 'user_browser' => $browser,
+ 'capabilities' => $user->allcaps,
+ 'submitted' => UM()->form()->post_form,
+ 'roles' => $user->roles,
+ 'user_browser' => $browser,
+ 'account_status' => get_user_meta( $user->get( 'ID' ), 'account_status', true ),
);
update_user_meta( $user->get( 'ID' ), 'um_user_blocked__metadata', $captured );
+
$user->remove_all_caps();
if ( is_user_logged_in() ) {
UM()->user()->set_status( 'inactive' );
@@ -492,14 +531,20 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
*/
public function manage_users_custom_column( $val, $column_name, $user_id ) {
if ( 'account_status' === $column_name ) {
- $is_blocked = get_user_meta( $user_id, 'um_user_blocked', true );
- if ( ! empty( $is_blocked ) ) {
- $datetime = get_user_meta( $user_id, 'um_user_blocked__datetime', true );
- $val = $val . '' . __( 'Blocked Due to Suspicious Activity', 'ultimate-member' ) . '
';
+ um_fetch_user( $user_id );
+ $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' );
+ $val = $val . '' . __( '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 );
+ $action = ' · ' . __( 'Restore Account', 'ultimate-member' ) . ' ';
if ( ! empty( $datetime ) ) {
- $val = $val . '' . human_time_diff( strtotime( $datetime ), strtotime( current_time( 'mysql' ) ) ) . ' ' . __( 'ago', 'ultimate-member' ) . '
';
+ $val = $val . '' . human_time_diff( strtotime( $datetime ), strtotime( current_time( 'mysql' ) ) ) . ' ' . __( 'ago', 'ultimate-member' ) . ' ' . $action . '
';
}
}
+ um_reset_user();
}
return $val;
}
@@ -547,7 +592,7 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
),
);
- $users = new \WP_User_Query( $args );
+ $users = new WP_User_Query( $args );
$this->send_email( array_values( $users->get_results() ) );
}
@@ -583,7 +628,7 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
),
);
- $users = new \WP_User_Query( $args );
+ $users = new WP_User_Query( $args );
$this->send_email( array_values( $users->get_results() ) );
@@ -666,7 +711,21 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
}
+ public function admin_notice() {
+ if ( get_transient( 'um_secure_restore_account_notice_success' ) ) { ?>
+
+
Date: Wed, 5 Jul 2023 12:51:48 +0800
Subject: [PATCH 18/45] Add secure global notice for first-time
---
includes/core/class-secure.php | 24 +++++++++++++++++++++++-
1 file changed, 23 insertions(+), 1 deletion(-)
diff --git a/includes/core/class-secure.php b/includes/core/class-secure.php
index 17fe9d3a..d519503d 100644
--- a/includes/core/class-secure.php
+++ b/includes/core/class-secure.php
@@ -203,6 +203,10 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
}
+ if ( isset( $_REQUEST['um_dismiss_security_first_time_notice'] ) ) {
+ set_transient( 'um_secure_first_time_admin_notice', 1, 5 );
+ }
+
}
/**
@@ -327,7 +331,7 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
'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' ),
+ 'description' => __( '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 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',
@@ -724,6 +728,24 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
// Delete transient, only display this notice once.
delete_transient( 'um_secure_restore_account_notice_success' );
}
+ // phpcs:disable WordPress.Security.NonceVerification
+ if ( ! get_transient( 'um_secure_first_time_admin_notice' ) && ( ! isset( $_REQUEST['page'] ) || 'um_options' !== $_REQUEST['page'] ) ) {
+ ?>
+
+
Date: Wed, 5 Jul 2023 12:54:04 +0800
Subject: [PATCH 19/45] Fix admin notice expiration
---
includes/core/class-secure.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/includes/core/class-secure.php b/includes/core/class-secure.php
index d519503d..d621575e 100644
--- a/includes/core/class-secure.php
+++ b/includes/core/class-secure.php
@@ -204,7 +204,7 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
}
if ( isset( $_REQUEST['um_dismiss_security_first_time_notice'] ) ) {
- set_transient( 'um_secure_first_time_admin_notice', 1, 5 );
+ set_transient( 'um_secure_first_time_admin_notice', 1, 0 );
}
}
From 51a5b5ed66ebefdf14f93c0c5adfa58d741ea24e Mon Sep 17 00:00:00 2001
From: Champ Camba
Date: Wed, 5 Jul 2023 12:57:42 +0800
Subject: [PATCH 20/45] Add secure doc article link
---
includes/core/class-secure.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/includes/core/class-secure.php b/includes/core/class-secure.php
index d621575e..437e6de0 100644
--- a/includes/core/class-secure.php
+++ b/includes/core/class-secure.php
@@ -740,7 +740,7 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
-
+
Date: Wed, 5 Jul 2023 13:04:08 +0800
Subject: [PATCH 21/45] Fix default locked capabilities
---
includes/core/class-secure.php | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/includes/core/class-secure.php b/includes/core/class-secure.php
index 437e6de0..16093100 100644
--- a/includes/core/class-secure.php
+++ b/includes/core/class-secure.php
@@ -291,8 +291,9 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
$settings['secure']['title'] = __( 'Secure', 'ultimate-member' );
$banned_admin_capabilities_options = array();
+ $default_locked_cap_options = array( 'manage_options', 'promote_users', 'level_10' );
foreach ( $this->banned_admin_capabilities as $i => $cap ) {
- if ( in_array( $cap, array( 'manage_options', 'promote_users', 'level_10' ), true ) ) {
+ if ( in_array( $cap, $default_locked_cap_options, true ) ) {
continue;
}
$banned_admin_capabilities_options[ $cap ] = $cap;
@@ -329,7 +330,7 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
'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 ),
+ 'value' => UM()->options()->get( 'banned_capabilities' ) ? array_keys( UM()->options()->get( 'banned_capabilities' ) ) : array_keys( $default_locked_cap_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 Account, Profile & Register forms 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' ),
),
From a0b52f26989e53876de21df31aa38e3c94c47b82 Mon Sep 17 00:00:00 2001
From: Champ Camba
Date: Wed, 5 Jul 2023 13:13:33 +0800
Subject: [PATCH 22/45] Add disabled options in multi checkbox render
---
includes/admin/core/class-admin-forms.php | 22 ++++++++++++++--------
1 file changed, 14 insertions(+), 8 deletions(-)
diff --git a/includes/admin/core/class-admin-forms.php b/includes/admin/core/class-admin-forms.php
index 92355732..e91efd3b 100644
--- a/includes/admin/core/class-admin-forms.php
+++ b/includes/admin/core/class-admin-forms.php
@@ -1204,19 +1204,20 @@ if ( ! class_exists( 'um\admin\core\Admin_Forms' ) ) {
$values = array();
}
- $i = 0;
+ $i = 0;
$html = '';
$columns = ( ! empty( $field_data['columns'] ) && is_numeric( $field_data['columns'] ) ) ? $field_data['columns'] : 1;
while ( $i < $columns ) {
- $per_page = ceil( count( $field_data['options'] ) / $columns );
- $section_fields_per_page = array_slice( $field_data['options'], $i*$per_page, $per_page, true );
- $html .= '';
+ $per_page = ceil( count( $field_data['options'] ) / $columns );
+ $section_fields_per_page = array_slice( $field_data['options'], $i * $per_page, $per_page, true );
+ $html .= '';
foreach ( $section_fields_per_page as $k => $title ) {
- $id_attr = ' id="' . esc_attr( $id . '_' . $k ) . '" ';
- $for_attr = ' for="' . esc_attr( $id . '_' . $k ) . '" ';
- $name_attr = ' name="' . $name . '[' . $k . ']" ';
+ $id_attr = ' id="' . esc_attr( $id . '_' . $k ) . '" ';
+ $for_attr = ' for="' . esc_attr( $id . '_' . $k ) . '" ';
+ $name_attr = ' name="' . $name . '[' . $k . ']" ';
+ $disabed_attr = '';
$data = array(
'field_id' => $field_data['id'] . '_' . $k,
@@ -1234,8 +1235,13 @@ if ( ! class_exists( 'um\admin\core\Admin_Forms' ) ) {
$data_attr .= ' data-' . $key . '="' . esc_attr( $value ) . '" ';
}
+ if ( isset( $field_data['options_disabled'] ) && in_array( $k, $field_data['options_disabled'], true ) ) {
+ $disabed_attr = 'disabled="disabled"';
+ $values = array_merge( $values, $field_data['options_disabled'] );
+ }
+
$html .= "
-
+
$title
";
}
From 110f22c92e1070c45888a21f3a68445130689773 Mon Sep 17 00:00:00 2001
From: Champ Camba
Date: Wed, 5 Jul 2023 13:16:38 +0800
Subject: [PATCH 23/45] Add locked capabilities in the settings to improve
readability
---
includes/core/class-secure.php | 24 +++++++++++-------------
1 file changed, 11 insertions(+), 13 deletions(-)
diff --git a/includes/core/class-secure.php b/includes/core/class-secure.php
index 16093100..3b860170 100644
--- a/includes/core/class-secure.php
+++ b/includes/core/class-secure.php
@@ -88,8 +88,6 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
'export',
'import',
'list_users',
- 'manage_options',
- 'promote_users',
'remove_users',
'switch_themes',
'customize',
@@ -109,6 +107,8 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
'create_users',
'delete_users',
'level_10',
+ 'manage_options',
+ 'promote_users',
)
);
@@ -293,9 +293,6 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
$banned_admin_capabilities_options = array();
$default_locked_cap_options = array( 'manage_options', 'promote_users', 'level_10' );
foreach ( $this->banned_admin_capabilities as $i => $cap ) {
- if ( in_array( $cap, $default_locked_cap_options, true ) ) {
- continue;
- }
$banned_admin_capabilities_options[ $cap ] = $cap;
}
@@ -325,14 +322,15 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
'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( $default_locked_cap_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 Account, Profile & Register forms 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' ),
+ 'id' => 'banned_capabilities',
+ 'type' => 'multi_checkbox',
+ 'multi' => true,
+ 'columns' => 2,
+ 'options_disabled' => $default_locked_cap_options,
+ 'options' => $banned_admin_capabilities_options,
+ 'value' => UM()->options()->get( 'banned_capabilities' ) ? array_keys( UM()->options()->get( 'banned_capabilities' ) ) : array_keys( $default_locked_cap_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 Account, Profile & Register forms 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',
From 25aa40b1c290c82d879aeedf207af4d430bbbee5 Mon Sep 17 00:00:00 2001
From: Mykyta Synelnikov
Date: Thu, 6 Jul 2023 01:56:59 +0300
Subject: [PATCH 24/45] - review for secure functionality;
---
includes/admin/class-admin.php | 12 +
includes/admin/core/class-admin-forms.php | 19 +-
includes/admin/core/class-admin-notices.php | 77 ++-
includes/admin/core/class-admin-settings.php | 15 +
includes/admin/core/class-secure.php | 348 ++++++++++++
includes/class-config.php | 12 +-
includes/class-functions.php | 3 +
includes/class-init.php | 1 +
includes/core/class-options.php | 5 +-
includes/core/class-secure.php | 531 +++++--------------
10 files changed, 581 insertions(+), 442 deletions(-)
create mode 100644 includes/admin/core/class-secure.php
diff --git a/includes/admin/class-admin.php b/includes/admin/class-admin.php
index d9131416..8684b5ae 100644
--- a/includes/admin/class-admin.php
+++ b/includes/admin/class-admin.php
@@ -2068,5 +2068,17 @@ if ( ! class_exists( 'um\admin\Admin' ) ) {
}
return UM()->classes['admin_notices'];
}
+
+ /**
+ * @since 2.6.8
+ *
+ * @return core\Secure
+ */
+ public function secure() {
+ if ( empty( UM()->classes['admin_secure'] ) ) {
+ UM()->classes['admin_secure'] = new core\Secure();
+ }
+ return UM()->classes['admin_secure'];
+ }
}
}
diff --git a/includes/admin/core/class-admin-forms.php b/includes/admin/core/class-admin-forms.php
index e91efd3b..4b1a7b51 100644
--- a/includes/admin/core/class-admin-forms.php
+++ b/includes/admin/core/class-admin-forms.php
@@ -117,13 +117,14 @@ if ( ! class_exists( 'um\admin\core\Admin_Forms' ) ) {
$data['value'] = esc_attr( $data['value'] );
}
- if( in_array( $data['type'], array('info_text') ) ){
+ if ( 'info_text' === $data['type'] ) {
$arr_kses = array(
'a' => array(
- 'href' => array(),
- 'title' => array(),
- 'target' => array(),
- 'class' => array(),
+ 'href' => array(),
+ 'title' => array(),
+ 'target' => array(),
+ 'class' => array(),
+ 'onclick' => array(),
),
'button' => array(
'class' => array(),
@@ -1185,15 +1186,14 @@ if ( ! class_exists( 'um\admin\core\Admin_Forms' ) ) {
* @return bool|string
*/
function render_multi_checkbox( $field_data ) {
-
if ( empty( $field_data['id'] ) ) {
return false;
}
$id = ( ! empty( $this->form_data['prefix_id'] ) ? $this->form_data['prefix_id'] : '' ) . '_' . $field_data['id'];
- $class = ! empty( $field_data['class'] ) ? $field_data['class'] : '';
- $class .= ! empty( $field_data['size'] ) ? $field_data['size'] : 'um-long-field';
+ $class = ! empty( $field_data['class'] ) ? $field_data['class'] : '';
+ $class .= ! empty( $field_data['size'] ) ? $field_data['size'] : 'um-long-field';
$class_attr = ' class="um-forms-field ' . esc_attr( $class ) . '" ';
$name = $field_data['id'];
@@ -1229,7 +1229,7 @@ if ( ! class_exists( 'um\admin\core\Admin_Forms' ) ) {
$data_attr = '';
foreach ( $data as $key => $value ) {
- if ( $value == 'checkbox_key' ) {
+ if ( 'checkbox_key' === $value ) {
$value = $k;
}
$data_attr .= ' data-' . $key . '="' . esc_attr( $value ) . '" ';
@@ -1237,7 +1237,6 @@ if ( ! class_exists( 'um\admin\core\Admin_Forms' ) ) {
if ( isset( $field_data['options_disabled'] ) && in_array( $k, $field_data['options_disabled'], true ) ) {
$disabed_attr = 'disabled="disabled"';
- $values = array_merge( $values, $field_data['options_disabled'] );
}
$html .= "
diff --git a/includes/admin/core/class-admin-notices.php b/includes/admin/core/class-admin-notices.php
index d19e13da..0b50d46f 100644
--- a/includes/admin/core/class-admin-notices.php
+++ b/includes/admin/core/class-admin-notices.php
@@ -1,9 +1,9 @@
admin()->is_um_screen() ) {
+ $this->secure_settings();
+ }
+ }
+
/**
* @return array
@@ -511,8 +519,14 @@ if ( ! class_exists( 'um\admin\core\Admin_Notices' ) ) {
case 'err_users_updated':
$messages[0]['err_content'] = __( 'Super administrators cannot be modified.', 'ultimate-member' );
- $messages[1]['content'] = __( 'Other users have been updated.', 'ultimate-member' );
-
+ $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' );
+ break;
+ case 'um_secure_restore':
+ $messages[0]['content'] = __( 'Account has been successfully restored.', 'ultimate-member' );
+ break;
}
if ( ! empty( $messages ) ) {
@@ -715,7 +729,6 @@ if ( ! class_exists( 'um\admin\core\Admin_Notices' ) ) {
), 2 );
}
-
/**
* Check Templates Versions notice
*/
@@ -746,26 +759,60 @@ if ( ! class_exists( 'um\admin\core\Admin_Notices' ) ) {
}
}
+ /**
+ * First time installed Secure settings.
+ */
+ public function secure_settings() {
+ ob_start();
+ ?>
+
+
+
+
+
+
+
+
+ add_notice(
+ 'secure_settings',
+ array(
+ 'class' => 'warning',
+ 'message' => $message,
+ 'dismissible' => true,
+ ),
+ 1
+ );
+ }
- function dismiss_notice() {
+ public function dismiss_notice() {
UM()->admin()->check_ajax_nonce();
if ( empty( $_POST['key'] ) ) {
wp_send_json_error( __( 'Wrong Data', 'ultimate-member' ) );
}
- $hidden_notices = get_option( 'um_hidden_admin_notices', array() );
- if ( ! is_array( $hidden_notices ) ) {
- $hidden_notices = array();
- }
-
- $hidden_notices[] = sanitize_key( $_POST['key'] );
-
- update_option( 'um_hidden_admin_notices', $hidden_notices );
+ $this->dismiss( sanitize_key( $_POST['key'] ) );
wp_send_json_success();
}
+ /**
+ * Dismiss notice by key.
+ *
+ * @param string $key
+ *
+ * @return void
+ */
+ public function dismiss( $key ) {
+ $hidden_notices = get_option( 'um_hidden_admin_notices', array() );
+ if ( ! is_array( $hidden_notices ) ) {
+ $hidden_notices = array();
+ }
+ $hidden_notices[] = $key;
+ update_option( 'um_hidden_admin_notices', $hidden_notices );
+ }
function force_dismiss_notice() {
if ( ! empty( $_REQUEST['um_dismiss_notice'] ) && ! empty( $_REQUEST['um_admin_nonce'] ) ) {
diff --git a/includes/admin/core/class-admin-settings.php b/includes/admin/core/class-admin-settings.php
index 455f85de..ef8a95c9 100644
--- a/includes/admin/core/class-admin-settings.php
+++ b/includes/admin/core/class-admin-settings.php
@@ -947,6 +947,21 @@ if ( ! class_exists( 'um\admin\core\Admin_Settings' ) ) {
'uninstall_on_delete' => array(
'sanitize' => 'bool',
),
+ 'lock_register_forms' => array(
+ 'sanitize' => 'bool',
+ ),
+ 'display_login_form_notice' => array(
+ 'sanitize' => 'bool',
+ ),
+ 'banned_capabilities' => array(
+ 'sanitize' => array( UM()->admin(), 'sanitize_wp_capabilities' ),
+ ),
+ 'secure_notify_admins_banned_accounts' => array(
+ 'sanitize' => 'bool',
+ ),
+ 'secure_notify_admins_banned_accounts__interval' => array(
+ 'sanitize' => 'key',
+ ),
)
);
diff --git a/includes/admin/core/class-secure.php b/includes/admin/core/class-secure.php
new file mode 100644
index 00000000..3abda370
--- /dev/null
+++ b/includes/admin/core/class-secure.php
@@ -0,0 +1,348 @@
+admin()->notices()->dismiss( 'secure_settings' );
+ }
+
+ if ( isset( $_REQUEST['um_secure_expire_all_sessions'] ) && ! wp_doing_ajax() ) {
+ if ( ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'um-secure-expire-session-nonce' ) || ! current_user_can( 'manage_options' ) ) {
+ // This nonce is not valid or current logged-in user has no administrative rights.
+ wp_die( esc_html__( 'Security check', 'ultimate-member' ) );
+ }
+
+ $users = get_users(
+ array(
+ 'fields' => 'ids',
+ )
+ );
+
+ $users = array_values( array_diff( $users, array( get_current_user_id() ) ) );
+
+ 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 );
+ // Remove all the session for instance user.
+ $sessions_manager->destroy_all();
+
+ // Remove all the session data for all users.
+ //$sessions_manager::drop_sessions();
+ }
+
+ if ( UM()->options()->get( 'display_login_form_notice' ) ) {
+ global $wpdb;
+ $wpdb->query(
+ $wpdb->prepare(
+ "DELETE FROM {$wpdb->usermeta} WHERE user_id != %d AND ( meta_key = 'um_secure_has_reset_password' OR meta_key = 'um_secure_has_reset_password__timestamp' )",
+ get_current_user_id()
+ )
+ );
+ }
+ }
+
+ wp_safe_redirect( add_query_arg( 'update', 'um_secure_expire_sessions', wp_get_referer() ) );
+ exit;
+ }
+
+ if ( isset( $_REQUEST['um_secure_restore_account'], $_REQUEST['user_id'] ) && ! wp_doing_ajax() ) {
+ $user_id = absint( $_REQUEST['user_id'] );
+ if ( ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'um-security-restore-account-nonce-' . $user_id ) || ! current_user_can( 'manage_options' ) ) {
+ // This nonce is not valid or current logged-in user has no administrative rights.
+ wp_die( esc_html__( 'Security check', 'ultimate-member' ) );
+ }
+
+ $user = get_userdata( $user_id );
+ if ( ! $user ) {
+ wp_die( esc_html__( 'Invalid user.', 'ultimate-member' ) );
+ }
+
+ $metadata = get_user_meta( $user_id, 'um_user_blocked__metadata', true );
+
+ // Restore Roles.
+ if ( isset( $metadata['roles'] ) ) {
+ foreach ( $metadata['roles'] as $role ) {
+ $user->add_role( $role );
+ }
+ }
+ // Restore Account Status.
+ if ( isset( $metadata['account_status'] ) ) {
+ UM()->user()->set_status( $metadata['account_status'] );
+ }
+
+ // 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' );
+
+ // Don't need to reset a password.
+ 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' ) );
+
+ // Clear Cache.
+ UM()->user()->remove_cache( $user_id );
+ wp_safe_redirect( add_query_arg( 'update', 'um_secure_restore', wp_get_referer() ) );
+ exit;
+ }
+ }
+
+ /**
+ * Register Secure Settings
+ *
+ * @since 2.6.8
+ *
+ * @param array $settings
+ * @return array
+ */
+ public function add_settings( $settings ) {
+ $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();
+ foreach ( $banned_admin_capabilities as $cap ) {
+ $banned_capabilities[ $cap ] = $cap;
+ }
+
+ $secure_fields = array(
+ array(
+ 'id' => 'lock_register_forms',
+ 'type' => 'checkbox',
+ 'label' => __( 'Lock All Register Forms', 'ultimate-member' ),
+ 'description' => __( 'This prevents all users from registering with Ultimate Member on your site.', 'ultimate-member' ),
+ ),
+ array(
+ 'id' => 'display_login_form_notice',
+ 'type' => 'checkbox',
+ 'label' => __( 'Display Login form notice to reset passwords', 'ultimate-member' ),
+ 'description' => __( 'Enforces users to reset their passwords( one-time ) and prevent from entering old password.', 'ultimate-member' ),
+ ),
+ );
+
+ $count_users_exclude_me = $count_users['total_users'] - 1;
+ if ( $count_users_exclude_me > 0 ) {
+ $secure_fields[] = array(
+ 'id' => 'force_reset_passwords',
+ 'type' => 'info_text',
+ 'label' => __( 'Expire All Users Sessions', 'ultimate-member' ),
+ // translators: %d is the users count.
+ 'value' => '' . esc_html( sprintf( __( 'Logout Users (%d)', 'ultimate-member' ), $count_users_exclude_me ) ) . ' ',
+ 'description' => __( 'This will log out all users on your site and forces them to reset passwords when "Display Login form notice to reset passwords" is enabled/checked. ', 'ultimate-member' ),
+ );
+ }
+
+ $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 ),
+ ),
+ 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 ),
+ ),
+ )
+ );
+
+ $settings['secure'] = array(
+ 'title' => __( 'Secure', 'ultimate-member' ),
+ 'fields' => $secure_fields,
+ );
+
+ return $settings;
+ }
+
+ /**
+ * Append blocked status to the `account_status` column rows.
+ *
+ * @param string $val Default column row value.
+ * @param string $column_name Current column name.
+ * @param int $user_id User ID in loop.
+ *
+ * @since 2.6.8
+ *
+ * @return string
+ */
+ public function add_restore_account( $val, $column_name, $user_id ) {
+ if ( 'account_status' === $column_name ) {
+ um_fetch_user( $user_id );
+ $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' );
+ $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 );
+ $action = ' · ' . esc_html__( 'Restore Account', 'ultimate-member' ) . ' ';
+ if ( ! empty( $datetime ) ) {
+ $val .= '' . human_time_diff( strtotime( $datetime ), strtotime( current_time( 'mysql' ) ) ) . ' ' . __( 'ago', 'ultimate-member' ) . ' ' . $action . '
';
+ }
+ }
+ um_reset_user();
+ }
+ return $val;
+ }
+
+ /**
+ *
+ */
+ public function check_secure_changes() {
+ if ( isset( $_POST['um_options']['display_login_form_notice'] ) ) {
+ $current_option_value = UM()->options()->get( 'display_login_form_notice' );
+ if ( empty( $current_option_value ) ) {
+ return;
+ }
+
+ if ( empty( $_POST['um_options']['display_login_form_notice'] ) ) {
+ $this->need_flush_meta = true;
+ }
+ }
+ }
+
+ /**
+ *
+ */
+ public function on_settings_save() {
+ if ( isset( $_POST['um_options']['display_login_form_notice'] ) && ! empty( $this->need_flush_meta ) ) {
+ global $wpdb;
+ $wpdb->query(
+ "DELETE FROM {$wpdb->usermeta} WHERE meta_key = 'um_secure_has_reset_password' OR meta_key = 'um_secure_has_reset_password__timestamp'"
+ );
+ }
+ }
+ }
+}
diff --git a/includes/class-config.php b/includes/class-config.php
index 903b4515..aaccca73 100644
--- a/includes/class-config.php
+++ b/includes/class-config.php
@@ -195,7 +195,6 @@ if ( ! class_exists( 'um\Config' ) ) {
'_um_secondary_color',
);
-
/**
* UM hook
*
@@ -559,9 +558,9 @@ if ( ! class_exists( 'um\Config' ) ) {
'form_asterisk' => 0,
'profile_title' => '{display_name} | {site_name}',
'profile_desc' => '{display_name} is on {site_name}. Join {site_name} to view {display_name}\'s profile',
- 'admin_email' => get_bloginfo('admin_email'),
- 'mail_from' => get_bloginfo('name'),
- 'mail_from_addr' => get_bloginfo('admin_email'),
+ 'admin_email' => get_bloginfo( 'admin_email' ),
+ 'mail_from' => get_bloginfo( 'name' ),
+ 'mail_from_addr' => get_bloginfo( 'admin_email' ),
'email_html' => 1,
'image_orientation_by_exif' => 0,
'image_compression' => 60,
@@ -576,6 +575,11 @@ if ( ! class_exists( 'um\Config' ) ) {
'profile_show_html_bio' => 0,
'profile_noindex' => 0,
'activation_link_expiry_time' => '',
+ 'lock_register_forms' => false,
+ 'display_login_form_notice' => false,
+ 'banned_capabilities' => array( 'manage_options', 'promote_users', 'level_10' ),
+ 'secure_notify_admins_banned_accounts' => false,
+ 'secure_notify_admins_banned_accounts__interval' => 'instant',
);
add_filter( 'um_get_tabs_from_config', '__return_true' );
diff --git a/includes/class-functions.php b/includes/class-functions.php
index 0a1aed80..dabefd4e 100644
--- a/includes/class-functions.php
+++ b/includes/class-functions.php
@@ -568,6 +568,9 @@ if ( ! class_exists( 'UM_Functions' ) ) {
'charoff' => true,
'valign' => true,
),
+ 'a' => array(
+ 'onclick' => array(),
+ ),
);
break;
case 'templates':
diff --git a/includes/class-init.php b/includes/class-init.php
index 7a3a0c27..a5a7d43f 100644
--- a/includes/class-init.php
+++ b/includes/class-init.php
@@ -573,6 +573,7 @@ if ( ! class_exists( 'UM' ) ) {
$this->admin_enqueue();
$this->metabox();
$this->admin()->notices();
+ $this->admin()->secure();
$this->users();
$this->dragdrop();
$this->admin_gdpr();
diff --git a/includes/core/class-options.php b/includes/core/class-options.php
index 9c97bb7b..a453e1f9 100644
--- a/includes/core/class-options.php
+++ b/includes/core/class-options.php
@@ -114,12 +114,13 @@ if ( ! class_exists( 'um\core\Options' ) ) {
* @use UM()->config()
*
* @param $option_id
- * @return bool
+ * @return mixed
*/
function get_default( $option_id ) {
$settings_defaults = UM()->config()->settings_defaults;
- if ( ! isset( $settings_defaults[ $option_id ] ) )
+ if ( ! isset( $settings_defaults[ $option_id ] ) ) {
return false;
+ }
return $settings_defaults[ $option_id ];
}
diff --git a/includes/core/class-secure.php b/includes/core/class-secure.php
index 3b860170..55f107f5 100644
--- a/includes/core/class-secure.php
+++ b/includes/core/class-secure.php
@@ -1,6 +1,7 @@
- * @example
- *
- */
- $this->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',
- )
- );
-
- /**
- * Add blocked status in the User column list.
- */
- add_filter( 'manage_users_custom_column', array( $this, 'manage_users_custom_column' ), 10, 3 );
-
/**
* WP Schedule Events for Notification
*/
add_action( 'wp', array( $this, 'schedule_events' ) );
-
- /**
- * Init
- */
- add_action( 'init', array( $this, 'init' ) );
-
- /**
- * Admin Notice
- */
- add_action( 'admin_notices', array( $this, 'admin_notice' ) );
-
}
/**
- * Init
+ * Adds handlers on form submissions.
*
* @since 2.6.8
*/
public function init() {
-
/**
* Checks the integrity of Current User's Capabilities
*/
- add_action( 'um_after_save_registration_details', array( $this, 'secure_user_capabilities' ), 10 );
+ 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 );
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 );
add_action( 'um_after_user_account_updated', array( $this, 'secure_user_capabilities' ), 1 );
}
-
- }
-
- /**
- * Admin Init
- *
- * @since 2.6.8
- */
- public function admin_init() {
-
- if ( isset( $_REQUEST['um_secure_expire_all_sessions'] ) && ( ! wp_doing_ajax() ) ) {
- if ( ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'um-secure-expire-session-nonce' ) || ! current_user_can( 'manage_options' ) ) {
- // This nonce is not valid or current logged-in user has no administrativerights.
- wp_die( esc_html_e( 'Security check', 'ultimate-member' ) );
- }
-
- // Get an instance of WP_User_Meta_Session_Tokens
- $sessions_manager = \WP_Session_Tokens::get_instance( null );
- // Remove all the session data for all users.
- $sessions_manager->drop_sessions();
- wp_safe_redirect( admin_url() );
- exit;
- }
-
- if ( isset( $_REQUEST['um_secure_restore_account'] ) && isset( $_REQUEST['user_id'] ) && ( ! wp_doing_ajax() ) ) {
- $user_id = $_REQUEST['user_id'];
- if ( ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'um-security-restore-account-nonce-' . $user_id ) || ! current_user_can( 'manage_options' ) ) {
- // This nonce is not valid or current logged-in user has no administrativerights.
- wp_die( esc_html_e( 'Security check', 'ultimate-member' ) );
- }
-
- $user = new WP_User( $user_id );
- $metadata = $user->get( 'um_user_blocked__metadata' );
-
- // Restore Roles.
- if ( isset( $metadata['roles'] ) ) {
- foreach ( $metadata['roles'] as $role ) {
- $user->add_role( $role );
- }
- }
- // Restore Account Status.
- if ( isset( $metadata['account_status'] ) ) {
- update_user_meta( $user_id, 'account_status', $metadata['account_status'] );
- }
- // Clear Cache
- UM()->user()->remove_cache( $user_id );
-
- set_transient( 'um_secure_restore_account_notice_success', 1, 5 );
-
- wp_safe_redirect( wp_get_referer() );
- exit;
-
- }
-
- if ( isset( $_REQUEST['um_dismiss_security_first_time_notice'] ) ) {
- set_transient( 'um_secure_first_time_admin_notice', 1, 0 );
- }
-
}
/**
* Add Login notice for Reset Password
*
- * @param array $args
* @since 2.6.8
*/
- public function reset_password_notice( $args ) {
-
+ public function reset_password_notice() {
if ( ! UM()->options()->get( 'display_login_form_notice' ) ) {
return;
}
@@ -247,28 +96,22 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
/**
* Add Login notice for Under Maintance
*
- * @param array $args
* @since 2.6.8
*/
- public function under_maintanance_notice( $args ) {
-
+ public function under_maintenance_notice() {
if ( ! UM()->options()->get( 'lock_register_forms' ) ) {
return;
}
// phpcs:disable WordPress.Security.NonceVerification
- if ( ! isset( $_REQUEST['notice'] ) || 'maintanance' !== $_REQUEST['notice'] ) {
+ if ( ! isset( $_GET['notice'] ) || 'maintenance' !== $_GET['notice'] ) {
return;
}
// phpcs:enable WordPress.Security.NonceVerification
echo "";
echo wp_kses(
- sprintf(
- // translators: One-time change requires you to reset your password
- __( 'Important: This site is currently under maintenance. Please check back soon.', 'ultimate-member' ),
- um_get_core_page( 'password-reset' )
- ),
+ __( 'Important: This site is currently under maintenance. Please log in or check back soon.', 'ultimate-member' ),
array(
'strong' => array(),
'a' => array(
@@ -279,83 +122,6 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
echo '
';
}
- /**
- * Register Secure Settings
- *
- * @param array $settings
- * @since 2.6.8
- */
- public function add_settings( $settings ) {
- $nonce = wp_create_nonce( 'um-secure-expire-session-nonce' );
- $count_users = count_users();
- $settings['secure']['title'] = __( 'Secure', 'ultimate-member' );
-
- $banned_admin_capabilities_options = array();
- $default_locked_cap_options = array( 'manage_options', 'promote_users', 'level_10' );
- foreach ( $this->banned_admin_capabilities as $i => $cap ) {
- $banned_admin_capabilities_options[ $cap ] = $cap;
- }
-
- $settings['secure']['sections'] =
- array(
- '' =>
- array(
- 'title' => __( 'Secure Ultimate Member', 'ultimate-member' ),
- 'fields' => array(
- array(
- 'id' => 'lock_register_forms',
- 'type' => 'checkbox',
- 'label' => __( 'Lock All Register Forms', 'ultimate-member' ),
- 'description' => __( 'This prevents all users from registering with Ultimate Member on your site.', 'ultimate-member' ),
- ),
- array(
- 'id' => 'display_login_form_notice',
- 'type' => 'checkbox',
- 'label' => __( 'Display Login form notice to reset passwords', 'ultimate-member' ),
- 'description' => __( 'Enforces users to reset their passwords( one-time ) and prevent from entering old password.', 'ultimate-member' ),
- ),
- array(
- 'id' => 'force_reset_passwords',
- 'type' => 'info_text',
- 'label' => __( 'Expire All Users Sessions', 'ultimate-member' ),
- '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_disabled' => $default_locked_cap_options,
- 'options' => $banned_admin_capabilities_options,
- 'value' => UM()->options()->get( 'banned_capabilities' ) ? array_keys( UM()->options()->get( 'banned_capabilities' ) ) : array_keys( $default_locked_cap_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 Account, Profile & Register forms 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 ),
- ),
- ),
- ),
- );
-
- return $settings;
- }
-
/**
* Block all UM Register form submissions.
*
@@ -364,7 +130,7 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
*/
public function block_register_forms( $args ) {
if ( UM()->options()->get( 'lock_register_forms' ) ) {
- $login_url = add_query_arg( 'notice', 'maintanance', um_get_core_page( 'login' ) );
+ $login_url = add_query_arg( 'notice', 'maintenance', um_get_core_page( 'login' ) );
nocache_headers();
wp_safe_redirect( $login_url );
exit;
@@ -374,14 +140,12 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
/**
* Validate when user has expired password
*
- * @param array $submitted_data
* @since 2.6.8
*/
- public function login_validate_expired_pass( $submitted_data ) {
-
+ public function login_validate_expired_pass() {
if ( UM()->options()->get( 'display_login_form_notice' ) ) {
- $has_expired = get_user_meta( um_user( 'ID' ), 'um_secure_has_reset_password', true );
- if ( ! $has_expired ) {
+ $expired_password_reset = get_user_meta( um_user( 'ID' ), 'um_secure_has_reset_password', true );
+ if ( ! $expired_password_reset ) {
$login_url = add_query_arg( 'notice', 'expired_password', um_get_core_page( 'login' ) );
wp_safe_redirect( $login_url );
exit;
@@ -390,36 +154,48 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
}
/**
- * Prevent users from using Old Passwords on Password Reset form
+ * Prevent users from using Old Passwords on UM Password Reset form.
+ *
+ * @param WP_Error $errors
+ * @param WP_User|WP_Error $user
*
- * @param object $errors
- * @param object $user
* @since 2.6.8
*/
public function avoid_old_password( $errors, $user ) {
- $wp_hasher = new \PasswordHash( 8, true );
+ if ( empty( $_POST['_um_password_change'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
+ return;
+ }
+
+ if ( ! UM()->options()->get( 'display_login_form_notice' ) ) {
+ return;
+ }
if ( isset( $_REQUEST['user_password'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
- $new_user_pass = $_REQUEST['user_password']; // phpcs:ignore WordPress.Security.NonceVerification
- if ( $wp_hasher->CheckPassword( $new_user_pass, $user->data->user_pass ) ) {
+ $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 ) ) {
UM()->form()->add_error( 'user_password', __( 'Your new password cannot be same as old password.', 'ultimate-member' ) );
- $errors->add( 'block_old_password', __( 'Your new password cannot be same as old password.', 'ultimate-member' ) );
+ $errors->add( 'um_block_old_password', __( 'Your new password cannot be same as old password.', 'ultimate-member' ) );
} else {
- update_user_meta( $user->data->ID, 'um_secure_has_reset_password', true );
- update_user_meta( $user->data->ID, 'um_secure_has_reset_password__timestamp', current_time( 'mysql' ) );
+ 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' ) );
}
}
}
/**
- * Secure user capabilities and revoke administrative ones
+ * Secure user capabilities and revoke administrative ones.
+ *
* @since 2.6.8
*/
public function secure_user_capabilities( $user_id ) {
global $wpdb;
+ $user = get_userdata( $user_id );
+ if ( empty( $user ) ) {
+ return;
+ }
+
// Fetch the WP_User object of our user.
um_fetch_user( $user_id );
- $user = new WP_User( $user_id );
$has_admin_cap = false;
$arr_banned_caps = array();
@@ -427,43 +203,40 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
$arr_banned_caps = array_keys( UM()->options()->get( 'banned_capabilities' ) );
}
- // Add locked administratrive capabilities
- $arr_banned_caps[] = 'manage_options';
- $arr_banned_caps[] = 'promote_users';
- $arr_banned_caps[] = 'level_10';
+ // Add locked administrative capabilities.
+ $arr_banned_caps = array_merge( $arr_banned_caps, UM()->options()->get_default( 'banned_capabilities' ) );
- foreach ( $arr_banned_caps as $i => $cap ) {
+ foreach ( $arr_banned_caps as $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;
}
}
- /**
- * 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 ) ) {
- if ( 10 === $user_level ) {
- $this->revoke_caps( $user );
+ 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 ) ) {
$has_admin_cap = true;
}
}
if ( $has_admin_cap ) {
+ $this->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->get( 'ID' ) ) );
+ $this->send_email( array( $user_id ) );
}
}
@@ -477,39 +250,53 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
wp_safe_redirect( $login_url );
exit;
}
+ } 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' ) );
+ }
+ }
+ }
- return true;
-
+ /**
+ * 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;
}
- return false;
+ 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' ) );
+ }
}
/**
* Revoke Caps & Mark rejected as suspicious
*
- * @param string $cap Capability slug
* @param object $user \WP_User
*
* @since 2.6.8
*/
public function revoke_caps( $user ) {
-
- if ( ! class_exists( '\Browser' ) ) {
- require_once um_path . 'includes/lib/browser.php';
+ $user_agent = '';
+ if ( isset( $_SERVER['HTTP_USER_AGENT'] ) ) {
+ $user_agent = sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) );
}
-
- // Detect browser.
- $browser = new \Browser();
// Capture details.
$captured = array(
'capabilities' => $user->allcaps,
'submitted' => UM()->form()->post_form,
'roles' => $user->roles,
- 'user_browser' => $browser,
- 'account_status' => get_user_meta( $user->get( 'ID' ), 'account_status', true ),
+ 'user_agent' => $user_agent,
+ 'account_status' => get_user_meta( $user->ID, 'account_status', true ),
);
- update_user_meta( $user->get( 'ID' ), 'um_user_blocked__metadata', $captured );
+ update_user_meta( $user->ID, 'um_user_blocked__metadata', $captured );
$user->remove_all_caps();
if ( is_user_logged_in() ) {
@@ -517,58 +304,26 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
} 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' ) );
+ update_user_meta( $user->ID, 'um_user_blocked', 'suspicious_activity' );
+ update_user_meta( $user->ID, 'um_user_blocked__datetime', current_time( 'mysql' ) );
}
/**
- * Append blocked status to the `account_status` column rows
- *
- * @param string $val Default column row value
- * @param string $column_name Current column name
- * @param integer $user_id User ID in loop
- *
- * @since 2.6.8
- *
- * @return string $val
- */
- public function manage_users_custom_column( $val, $column_name, $user_id ) {
- if ( 'account_status' === $column_name ) {
- um_fetch_user( $user_id );
- $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' );
- $val = $val . '' . __( '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 );
- $action = ' · ' . __( 'Restore Account', 'ultimate-member' ) . ' ';
- if ( ! empty( $datetime ) ) {
- $val = $val . '' . human_time_diff( strtotime( $datetime ), strtotime( current_time( 'mysql' ) ) ) . ' ' . __( 'ago', 'ultimate-member' ) . ' ' . $action . '
';
- }
- }
- um_reset_user();
- }
- return $val;
- }
-
- /**
- * Register Events
+ * Add callbacks to Schedule 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_hourly' ) ) {
- wp_schedule_event( current_time( 'mysql' ), 'hourly', 'um_secure_notify_administrator_hourly' );
+ $notification_interval = UM()->options()->get( 'secure_notify_admins_banned_accounts__interval' );
+ if ( 'instant' === $notification_interval ) {
+ return;
}
- if ( ! wp_next_scheduled( 'um_secure_notify_administrator_daily' ) ) {
- wp_schedule_event( current_time( 'mysql' ), 'daily', 'um_secure_notify_administrator_daily' );
+ 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' ) );
}
}
}
@@ -579,27 +334,22 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
* @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',
- ),
+ $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() ) );
- }
+ $users = new WP_User_Query( $args );
+ $this->send_email( array_values( $users->get_results() ) );
}
/**
@@ -608,35 +358,29 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
* @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',
- ),
+ $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() ) );
-
- }
+ $users = new WP_User_Query( $args );
+ $this->send_email( array_values( $users->get_results() ) );
}
/**
@@ -713,40 +457,5 @@ if ( ! class_exists( 'um\core\Secure' ) ) {
wp_mail( $multiple_recipients, $subject, $body );
}
-
- public function admin_notice() {
- if ( get_transient( 'um_secure_restore_account_notice_success' ) ) { ?>
-
-
-
-
Date: Fri, 7 Jul 2023 00:34:11 +0300
Subject: [PATCH 25/45] - review for secure functionality;
---
includes/admin/assets/js/um-admin-secure.js | 79 ++++
includes/admin/class-admin.php | 16 +-
includes/admin/{core => }/class-secure.php | 160 ++++----
includes/admin/core/class-admin-notices.php | 63 ++-
includes/ajax/class-init.php | 38 ++
includes/ajax/class-secure.php | 410 +++++++++++++++++++
includes/class-config.php | 13 +-
includes/class-init.php | 63 +--
includes/common/class-cpt.php | 92 +++++
includes/common/class-init.php | 64 +++
includes/common/class-screen.php | 44 ++
includes/common/class-secure.php | 235 +++++++++++
includes/core/class-common.php | 118 ------
includes/frontend/class-init.php | 38 ++
includes/{core => frontend}/class-secure.php | 216 +---------
templates/email/suspicious-activity.php | 36 ++
16 files changed, 1247 insertions(+), 438 deletions(-)
create mode 100644 includes/admin/assets/js/um-admin-secure.js
rename includes/admin/{core => }/class-secure.php (71%)
create mode 100644 includes/ajax/class-init.php
create mode 100644 includes/ajax/class-secure.php
create mode 100644 includes/common/class-cpt.php
create mode 100644 includes/common/class-init.php
create mode 100644 includes/common/class-screen.php
create mode 100644 includes/common/class-secure.php
delete mode 100644 includes/core/class-common.php
create mode 100644 includes/frontend/class-init.php
rename includes/{core => frontend}/class-secure.php (56%)
create mode 100644 templates/email/suspicious-activity.php
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 = '' . esc_html__( 'Scan Now', 'ultimate-member' ) . ' ';
+ $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 @@
+
+
+
+
+
+
+
+
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.
+
+
+
+
From fc6182c026f6f142fe6e43c252eb5de52e883907 Mon Sep 17 00:00:00 2001
From: Mykyta Synelnikov
Date: Fri, 7 Jul 2023 00:45:12 +0300
Subject: [PATCH 26/45] - additional changes from latest Champ commit;
---
includes/admin/class-secure.php | 9 ++++++---
includes/common/class-secure.php | 4 +++-
2 files changed, 9 insertions(+), 4 deletions(-)
diff --git a/includes/admin/class-secure.php b/includes/admin/class-secure.php
index 6f03f854..ef282792 100644
--- a/includes/admin/class-secure.php
+++ b/includes/admin/class-secure.php
@@ -129,6 +129,7 @@ if ( ! class_exists( 'um\admin\Secure' ) ) {
}
$metadata = get_user_meta( $user_id, 'um_user_blocked__metadata', true );
+ $user->update_user_level_from_caps();
// Restore Roles.
if ( isset( $metadata['roles'] ) ) {
@@ -147,8 +148,10 @@ if ( ! class_exists( 'um\admin\Secure' ) ) {
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 );
- update_user_meta( $user_id, 'um_secure_has_reset_password__timestamp', current_time( 'mysql' ) );
+ 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' ) );
+ }
// Clear Cache.
UM()->user()->remove_cache( $user_id );
@@ -251,7 +254,7 @@ if ( ! class_exists( 'um\admin\Secure' ) ) {
'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' ),
+ 'description' => __( 'When enabled, All administrators will be notified when someone has suspicious activities in the Account, Profile & Register forms.', 'ultimate-member' ),
'conditional' => array( 'secure_ban_admins_accounts', '=', 1 ),
),
array(
diff --git a/includes/common/class-secure.php b/includes/common/class-secure.php
index a316c89c..85f9c4bf 100644
--- a/includes/common/class-secure.php
+++ b/includes/common/class-secure.php
@@ -206,7 +206,9 @@ if ( ! class_exists( 'um\common\Secure' ) ) {
*/
public function revoke_caps( $user ) {
$user_agent = '';
- if ( isset( $_SERVER['HTTP_USER_AGENT'] ) ) {
+ if ( isset( $_REQUEST['nonce'], $_REQUEST['action'] ) && 'um_secure_scan_affected_users' === $_REQUEST['action'] && wp_verify_nonce( $_REQUEST['nonce'], 'um-admin-nonce' ) && current_user_can( 'manage_options' ) ) {
+ $user_agent = __( 'Ultimate Member Scanner', 'ultimate-member' );
+ } elseif ( isset( $_SERVER['HTTP_USER_AGENT'] ) ) {
$user_agent = sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) );
}
// Capture details.
From e23387684cdb2ce52caa6db7de07efe1e3c57ced Mon Sep 17 00:00:00 2001
From: Mykyta Synelnikov
Date: Fri, 7 Jul 2023 01:46:11 +0300
Subject: [PATCH 27/45] - fixed user status and Users List table column;
---
includes/admin/class-secure.php | 4 +++-
includes/common/class-secure.php | 7 +++++--
2 files changed, 8 insertions(+), 3 deletions(-)
diff --git a/includes/admin/class-secure.php b/includes/admin/class-secure.php
index ef282792..9d8eaa02 100644
--- a/includes/admin/class-secure.php
+++ b/includes/admin/class-secure.php
@@ -43,7 +43,7 @@ if ( ! class_exists( 'um\admin\Secure' ) ) {
public function __construct() {
add_action( 'admin_init', array( $this, 'admin_init' ) );
add_filter( 'um_settings_structure', array( $this, 'add_settings' ) );
- add_filter( 'manage_users_custom_column', array( $this, 'add_restore_account' ), 10, 3 );
+ add_filter( 'manage_users_custom_column', array( $this, 'add_restore_account' ), 9999, 3 );
add_action( 'um_settings_before_save', array( $this, 'check_secure_changes' ) );
add_action( 'um_settings_save', array( $this, 'on_settings_save' ) );
@@ -128,6 +128,7 @@ if ( ! class_exists( 'um\admin\Secure' ) ) {
wp_die( esc_html__( 'Invalid user.', 'ultimate-member' ) );
}
+ um_fetch_user( $user_id );
$metadata = get_user_meta( $user_id, 'um_user_blocked__metadata', true );
$user->update_user_level_from_caps();
@@ -155,6 +156,7 @@ if ( ! class_exists( 'um\admin\Secure' ) ) {
// Clear Cache.
UM()->user()->remove_cache( $user_id );
+ um_reset_user();
wp_safe_redirect( add_query_arg( 'update', 'um_secure_restore', wp_get_referer() ) );
exit;
}
diff --git a/includes/common/class-secure.php b/includes/common/class-secure.php
index 85f9c4bf..4577d574 100644
--- a/includes/common/class-secure.php
+++ b/includes/common/class-secure.php
@@ -211,19 +211,22 @@ if ( ! class_exists( 'um\common\Secure' ) ) {
} elseif ( isset( $_SERVER['HTTP_USER_AGENT'] ) ) {
$user_agent = sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) );
}
+
+ um_fetch_user( $user->ID );
+
// 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 ),
+ 'account_status' => um_user( 'status' ),
);
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 {
From b69e930373e3e10450a3e557b7f1109eb0dff9c6 Mon Sep 17 00:00:00 2001
From: Mykyta Synelnikov
Date: Fri, 7 Jul 2023 02:06:29 +0300
Subject: [PATCH 28/45] - fixed reset password meta set after update profile;
---
includes/frontend/class-secure.php | 5 -----
1 file changed, 5 deletions(-)
diff --git a/includes/frontend/class-secure.php b/includes/frontend/class-secure.php
index e2877e46..e7083a4d 100644
--- a/includes/frontend/class-secure.php
+++ b/includes/frontend/class-secure.php
@@ -249,11 +249,6 @@ if ( ! class_exists( 'um\frontend\Secure' ) ) {
wp_safe_redirect( $login_url );
exit;
}
- } 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' ) );
- }
}
}
From 13a3ca50a1cee0141b7c02c255690f2a251d9239 Mon Sep 17 00:00:00 2001
From: Mykyta Synelnikov
Date: Fri, 7 Jul 2023 02:32:37 +0300
Subject: [PATCH 29/45] - fixed banned profile;
---
includes/common/class-secure.php | 2 +-
includes/frontend/class-secure.php | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/includes/common/class-secure.php b/includes/common/class-secure.php
index 4577d574..c3bb3ab2 100644
--- a/includes/common/class-secure.php
+++ b/includes/common/class-secure.php
@@ -118,7 +118,7 @@ if ( ! class_exists( 'um\common\Secure' ) ) {
array(
'admin' => true,
'tags' => array(
- 'banned_profile_links',
+ '{banned_profile_links}',
),
'tags_replace' => array(
$banned_profile_links,
diff --git a/includes/frontend/class-secure.php b/includes/frontend/class-secure.php
index e7083a4d..bbc5f401 100644
--- a/includes/frontend/class-secure.php
+++ b/includes/frontend/class-secure.php
@@ -199,7 +199,7 @@ if ( ! class_exists( 'um\frontend\Secure' ) ) {
$arr_banned_caps = array();
if ( UM()->options()->get( 'banned_capabilities' ) ) {
- $arr_banned_caps = array_keys( UM()->options()->get( 'banned_capabilities' ) );
+ $arr_banned_caps = UM()->options()->get( 'banned_capabilities' );
}
// Add locked administrative capabilities.
From 56d57c539637500737d52f9375705b3df299daab Mon Sep 17 00:00:00 2001
From: Champ Camba
Date: Fri, 7 Jul 2023 13:47:53 +0800
Subject: [PATCH 30/45] Remove script to require settings save
---
includes/admin/assets/js/um-admin-secure.js | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/includes/admin/assets/js/um-admin-secure.js b/includes/admin/assets/js/um-admin-secure.js
index fe13e8eb..d58681a9 100644
--- a/includes/admin/assets/js/um-admin-secure.js
+++ b/includes/admin/assets/js/um-admin-secure.js
@@ -29,11 +29,7 @@ jQuery(document).on("ready", function(){
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 = [];
From 1672373729574350f625c0a70335faef79ef5201 Mon Sep 17 00:00:00 2001
From: Champ Camba
Date: Fri, 7 Jul 2023 13:48:16 +0800
Subject: [PATCH 31/45] Fix issue when saving & displaying with multi checkbox
value
---
includes/admin/class-secure.php | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/includes/admin/class-secure.php b/includes/admin/class-secure.php
index 9d8eaa02..b6071233 100644
--- a/includes/admin/class-secure.php
+++ b/includes/admin/class-secure.php
@@ -72,7 +72,7 @@ if ( ! class_exists( 'um\admin\Secure' ) ) {
public function admin_init() {
// Dismiss admin notice after the first visit to Secure settings page.
if ( isset( $_REQUEST['page'] ) && isset( $_REQUEST['tab'] ) &&
- 'um_options' === sanitize_key( $_REQUEST['page'] ) && 'secure' === sanitize_key( $_REQUEST['tab'] ) ) {
+ 'um_options' === sanitize_key( $_REQUEST['page'] ) && 'secure' === sanitize_key( $_REQUEST['tab'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
UM()->admin()->notices()->dismiss( 'secure_settings' );
}
@@ -183,6 +183,8 @@ if ( ! class_exists( 'um\admin\Secure' ) ) {
$disabled_capabilities = UM()->options()->get_default( 'banned_capabilities' );
$disabled_capabilities_text = '' . implode( ' , ', $disabled_capabilities ) . ' ';
+ $saved_options = UM()->options()->get( 'banned_capabilities' );
+
$scanner_content = '' . esc_html__( 'Scan Now', 'ultimate-member' ) . ' ';
$scanner_content .= '';
$scanner_content .= esc_html__( 'Last scan:', 'ultimate-member' ) . ' ';
@@ -203,9 +205,11 @@ if ( ! class_exists( 'um\admin\Secure' ) ) {
'id' => 'banned_capabilities',
'type' => 'multi_checkbox',
'multi' => true,
+ 'checkbox_key' => true,
'columns' => 2,
'options_disabled' => $disabled_capabilities,
'options' => $banned_capabilities,
+ 'value' => ! empty( $saved_options ) ? array_keys( $saved_options ) : $disabled_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 ),
@@ -316,13 +320,13 @@ if ( ! class_exists( 'um\admin\Secure' ) ) {
*
*/
public function check_secure_changes() {
- if ( isset( $_POST['um_options']['display_login_form_notice'] ) ) {
+ if ( isset( $_POST['um_options']['display_login_form_notice'] ) ) { //phpcs:ignore WordPress.Security.NonceVerification
$current_option_value = UM()->options()->get( 'display_login_form_notice' );
if ( empty( $current_option_value ) ) {
return;
}
- if ( empty( $_POST['um_options']['display_login_form_notice'] ) ) {
+ if ( empty( $_POST['um_options']['display_login_form_notice'] ) ) { //phpcs:ignore WordPress.Security.NonceVerification
$this->need_flush_meta = true;
}
}
@@ -332,7 +336,7 @@ if ( ! class_exists( 'um\admin\Secure' ) ) {
*
*/
public function on_settings_save() {
- if ( isset( $_POST['um_options']['display_login_form_notice'] ) && ! empty( $this->need_flush_meta ) ) {
+ if ( isset( $_POST['um_options']['display_login_form_notice'] ) && ! empty( $this->need_flush_meta ) ) { //phpcs:ignore WordPress.Security.NonceVerification
global $wpdb;
$wpdb->query(
"DELETE FROM {$wpdb->usermeta} WHERE meta_key = 'um_secure_has_reset_password' OR meta_key = 'um_secure_has_reset_password__timestamp'"
From c53d1b07b11559d1a8a4b5f700817bf703e60b08 Mon Sep 17 00:00:00 2001
From: Champ Camba
Date: Fri, 7 Jul 2023 13:48:43 +0800
Subject: [PATCH 32/45] Remove unused variables + add SSL check
---
includes/ajax/class-secure.php | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/includes/ajax/class-secure.php b/includes/ajax/class-secure.php
index 43b3cf5c..690c5b42 100644
--- a/includes/ajax/class-secure.php
+++ b/includes/ajax/class-secure.php
@@ -173,7 +173,7 @@ class Secure {
array(
'key' => 'submitted',
'value' => sprintf( '.*%s";', '_user_level' ),
- 'compare' => 'LIKE',
+ 'compare' => 'REGEXP',
),
),
)
@@ -186,9 +186,8 @@ class Secure {
* Disable and Kickout Suspicious accounts.
*/
if ( $suspicious_accounts_count > 0 ) {
- $arr_might_lookout_accounts = array();
- $arr_dates_registered = array();
- $arr_suspected_accounts = array();
+ $arr_dates_registered = array();
+ $arr_suspected_accounts = array();
if ( ! empty( $susp_accounts ) ) {
foreach ( $susp_accounts as $user ) {
@@ -207,7 +206,6 @@ class Secure {
}
}
- $date_query = array();
$oldest_date = min( $arr_dates_registered );
$newest_date = max( $arr_dates_registered );
@@ -337,6 +335,8 @@ class Secure {
$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.';
+ } else {
+ $content .= $br . $check . 'Your site provides a secure connection with SSL.';
}
$content .= $br . $br . 'Install Challenge-Response plugin to Login & Register Forms ';
From 3783496120944c51f1ad5abfcf81f3f87fcfd67b Mon Sep 17 00:00:00 2001
From: Champ Camba
Date: Fri, 7 Jul 2023 13:51:38 +0800
Subject: [PATCH 33/45] Add reset password
---
includes/ajax/class-secure.php | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/includes/ajax/class-secure.php b/includes/ajax/class-secure.php
index 690c5b42..7b9c2716 100644
--- a/includes/ajax/class-secure.php
+++ b/includes/ajax/class-secure.php
@@ -341,9 +341,7 @@ class Secure {
$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.';
- }
+ $content .= $br . $flag . 'We recommend that you install and enable ReCaptcha to your Reset Password, Login & Register forms.';
} else {
if ( in_array( 'um-recaptcha/um-recaptcha.php', $active_plugins, true ) ) {
$content .= $br . $check . 'Ultimate Member ReCaptcha is actived.';
From 7b83fc7838b04c0704d8d647027f167cf5c284f0 Mon Sep 17 00:00:00 2001
From: Champ Camba
Date: Fri, 7 Jul 2023 14:13:42 +0800
Subject: [PATCH 34/45] Add Site Health total issues with link to page
---
includes/ajax/class-secure.php | 45 ++++++++++++++++++++++++++++------
1 file changed, 38 insertions(+), 7 deletions(-)
diff --git a/includes/ajax/class-secure.php b/includes/ajax/class-secure.php
index 7b9c2716..9aa7bbec 100644
--- a/includes/ajax/class-secure.php
+++ b/includes/ajax/class-secure.php
@@ -220,7 +220,28 @@ class Secure {
);
}
- $content .= '' . ( $suspicious_accounts_count > 0 ? $warning : $check ) . __( 'Scan Complete.', 'ultimate-member' ) . ' ';
+ /**
+ * Get Site Health's Total issues to resolve
+ */
+ $get_issues = get_transient( 'health-check-site-status-result' );
+
+ $issue_counts = array();
+
+ if ( false !== $get_issues ) {
+ $issue_counts = json_decode( $get_issues, true );
+ }
+
+ if ( ! is_array( $issue_counts ) || ! $issue_counts ) {
+ $issue_counts = array(
+ 'good' => 0,
+ 'recommended' => 0,
+ 'critical' => 0,
+ );
+ }
+
+ $site_health_issues_total = $issue_counts['recommended'] + $issue_counts['critical'];
+
+ $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'];
@@ -276,14 +297,24 @@ class Secure {
$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;
+ $content .= '👇 MORE RECOMMENDATIONS BELOW.';
}
+ $content .= $br . $br . 'Review & Resolve Issues with Site Health Check tool ';
+ $content .= $br . __( 'Site Health is a tool in WordPress that helps you monitor how your site is doing. It shows critical information about your WordPress configuration and items that require your attention.', 'ultimate-member' );
+ if ( $site_health_issues_total > 0 ) {
+ $content .= $br . $flag . sprintf( /* translators: %d issue in the Site Health status */ _n( 'There\s %d issue in the Site Health status', 'There are %d issues in the Site Health status', $site_health_issues_total ), $site_health_issues_total );
+ $content .= ': Review Site Health Status ';
+ } else {
+ $content .= $br . $check . __( 'There are no issues found in the Site Health status', 'ultimate-member' );
+ }
+
+ $content .= $br . $br . 'Default WP Register Form ';
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 . $flag . '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;
+ } else {
+ $content .= $br . $check . 'The default WordPress Register form is disabled.' . $br;
}
$content .= $br . 'Block Disposable Email Addresses/Domains ';
@@ -295,7 +326,7 @@ class Secure {
$content .= $br;
}
- $content .= $br . 'Manage User Roles & Capabilities ';
+ $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'];
@@ -321,7 +352,7 @@ class Secure {
$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 . $check . 'Roles & Capabilities are all secured. No users are using the same capabilities as your administrators.';
}
$content .= $br . $br . 'Require Strong Passwords ';
From 39859b5026cd60be19dbc99002654d20598d27e3 Mon Sep 17 00:00:00 2001
From: Champ Camba
Date: Fri, 7 Jul 2023 14:48:50 +0800
Subject: [PATCH 35/45] Refactor destroying all sessions except one
---
includes/admin/class-secure.php | 40 +++++++++++----------------------
1 file changed, 13 insertions(+), 27 deletions(-)
diff --git a/includes/admin/class-secure.php b/includes/admin/class-secure.php
index b6071233..d40bddd1 100644
--- a/includes/admin/class-secure.php
+++ b/includes/admin/class-secure.php
@@ -70,6 +70,7 @@ if ( ! class_exists( 'um\admin\Secure' ) ) {
* @since 2.6.8
*/
public function admin_init() {
+ global $wpdb;
// Dismiss admin notice after the first visit to Secure settings page.
if ( isset( $_REQUEST['page'] ) && isset( $_REQUEST['tab'] ) &&
'um_options' === sanitize_key( $_REQUEST['page'] ) && 'secure' === sanitize_key( $_REQUEST['tab'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
@@ -82,34 +83,19 @@ if ( ! class_exists( 'um\admin\Secure' ) ) {
wp_die( esc_html__( 'Security check', 'ultimate-member' ) );
}
- $users = get_users(
- array(
- 'fields' => 'ids',
- )
- );
+ /**
+ * Destroy all user sessions except the current logged-in user.
+ */
+ $wpdb->query( $wpdb->prepare( 'DELETE FROM ' . $wpdb->usermeta . ' WHERE meta_key="session_tokens" AND user_id != %d', get_current_user_id() ) );
- $users = array_values( array_diff( $users, array( get_current_user_id() ) ) );
-
- 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 );
- // Remove all the session for instance user.
- $sessions_manager->destroy_all();
-
- // Remove all the session data for all users.
- //$sessions_manager::drop_sessions();
- }
-
- if ( UM()->options()->get( 'display_login_form_notice' ) ) {
- global $wpdb;
- $wpdb->query(
- $wpdb->prepare(
- "DELETE FROM {$wpdb->usermeta} WHERE user_id != %d AND ( meta_key = 'um_secure_has_reset_password' OR meta_key = 'um_secure_has_reset_password__timestamp' )",
- get_current_user_id()
- )
- );
- }
+ if ( UM()->options()->get( 'display_login_form_notice' ) ) {
+ global $wpdb;
+ $wpdb->query(
+ $wpdb->prepare(
+ "DELETE FROM {$wpdb->usermeta} WHERE user_id != %d AND ( meta_key = 'um_secure_has_reset_password' OR meta_key = 'um_secure_has_reset_password__timestamp' )",
+ get_current_user_id()
+ )
+ );
}
wp_safe_redirect( add_query_arg( 'update', 'um_secure_expire_sessions', wp_get_referer() ) );
From 3d902a2bbbe3fd0fbd72d90a7215a58b3d6573d8 Mon Sep 17 00:00:00 2001
From: Champ Camba
Date: Fri, 7 Jul 2023 15:44:28 +0800
Subject: [PATCH 36/45] Enable/Disable Security email template on settings save
---
includes/admin/class-secure.php | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/includes/admin/class-secure.php b/includes/admin/class-secure.php
index d40bddd1..d042a7e2 100644
--- a/includes/admin/class-secure.php
+++ b/includes/admin/class-secure.php
@@ -322,12 +322,21 @@ if ( ! class_exists( 'um\admin\Secure' ) ) {
*
*/
public function on_settings_save() {
+
if ( isset( $_POST['um_options']['display_login_form_notice'] ) && ! empty( $this->need_flush_meta ) ) { //phpcs:ignore WordPress.Security.NonceVerification
global $wpdb;
$wpdb->query(
"DELETE FROM {$wpdb->usermeta} WHERE meta_key = 'um_secure_has_reset_password' OR meta_key = 'um_secure_has_reset_password__timestamp'"
);
}
+
+ if ( isset( $_POST['um_options']['secure_notify_admins_banned_accounts'] ) ) { //phpcs:ignore WordPress.Security.NonceVerification
+ if ( ! empty( $_POST['um_options']['secure_notify_admins_banned_accounts'] ) ) { //phpcs:ignore WordPress.Security.NonceVerification
+ UM()->options()->update( 'suspicious-activity_on', 1 );
+ } else {
+ UM()->options()->update( 'suspicious-activity_on', 0 );
+ }
+ }
}
}
}
From bd14db9e680e29a4324efbdefe69e1fc89bc724c Mon Sep 17 00:00:00 2001
From: Champ Camba
Date: Fri, 7 Jul 2023 17:28:06 +0800
Subject: [PATCH 37/45] Add link to filter registered date
This filters users by date range who might be created when the suspicious accounts were created
---
includes/admin/class-secure.php | 35 ++++++++++++++++++++++++++++++
includes/ajax/class-secure.php | 38 ++++++++++++++++++++++-----------
2 files changed, 61 insertions(+), 12 deletions(-)
diff --git a/includes/admin/class-secure.php b/includes/admin/class-secure.php
index d042a7e2..1062d20f 100644
--- a/includes/admin/class-secure.php
+++ b/includes/admin/class-secure.php
@@ -44,6 +44,7 @@ if ( ! class_exists( 'um\admin\Secure' ) ) {
add_action( 'admin_init', array( $this, 'admin_init' ) );
add_filter( 'um_settings_structure', array( $this, 'add_settings' ) );
add_filter( 'manage_users_custom_column', array( $this, 'add_restore_account' ), 9999, 3 );
+ add_filter( 'pre_get_users', array( $this, 'filter_users_by_date_registered' ) );
add_action( 'um_settings_before_save', array( $this, 'check_secure_changes' ) );
add_action( 'um_settings_save', array( $this, 'on_settings_save' ) );
@@ -64,6 +65,40 @@ if ( ! class_exists( 'um\admin\Secure' ) ) {
wp_enqueue_script( 'um_admin_secure' );
}
+ /**
+ * Filter users by Register Date
+ *
+ * @since 2.6.8
+ * @param object $query WP query `pre_get_users`
+ */
+ public function filter_users_by_date_registered( $query ) {
+ global $pagenow;
+ if ( is_admin() && 'users.php' === $pagenow ) {
+ // phpcs:disable WordPress.Security.NonceVerification
+ $date_from = isset( $_GET['um_secure_date_from'] ) ? $_GET['um_secure_date_from'] : null;
+ $date_to = isset( $_GET['um_secure_date_to'] ) ? $_GET['um_secure_date_to'] : null;
+ // phpcs:enable WordPress.Security.NonceVerification
+ if ( ! $date_to ) {
+ $query->set(
+ 'date_query',
+ array(
+ 'after' => human_time_diff( $date_from, strtotime( current_time( 'mysql' ) ) ) . ' ago',
+ )
+ );
+ } elseif ( $date_from && $date_to ) {
+ $query->set(
+ 'date_query',
+ array(
+ 'after' => human_time_diff( $date_from, strtotime( current_time( 'mysql' ) ) ) . ' ago',
+ 'before' => human_time_diff( $date_to, strtotime( current_time( 'mysql' ) ) ) . ' ago',
+ )
+ );
+ }
+ }
+
+ return $query;
+ }
+
/**
* Handle secure actions.
*
diff --git a/includes/ajax/class-secure.php b/includes/ajax/class-secure.php
index 9aa7bbec..8a884f22 100644
--- a/includes/ajax/class-secure.php
+++ b/includes/ajax/class-secure.php
@@ -181,18 +181,18 @@ class Secure {
$suspicious_accounts_count = $suspicious_accounts->get_total();
$susp_accounts = $suspicious_accounts->get_results();
+ $arr_dates_registered = array();
+ $arr_suspected_accounts = array();
/**
* Disable and Kickout Suspicious accounts.
*/
if ( $suspicious_accounts_count > 0 ) {
- $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 );
+ $arr_dates_registered[] = $user->user_registered;
if ( $user->__get( 'um_user_blocked' ) ) {
continue;
@@ -206,15 +206,21 @@ class Secure {
}
}
- $oldest_date = min( $arr_dates_registered );
- $newest_date = max( $arr_dates_registered );
+ $arr_dates_in_timestamp = array_map( 'strtotime', $arr_dates_registered );
+
+ $oldest_date = min( $arr_dates_in_timestamp );
+ $newest_date = max( $arr_dates_in_timestamp );
+
+ $content .= gmdate( 'F d, Y', $newest_date );
$might_affected_users = new WP_User_Query(
array(
'number' => -1,
- 'relation' => 'AND',
+ 'exclude' => $arr_suspected_accounts,
'date_query' => array(
- 'after' => human_time_diff( $oldest_date, strtotime( current_time( 'mysql' ) ) ) . ' ago',
+ 'after' => gmdate( 'F d, Y', $oldest_date ),
+ 'before' => gmdate( 'F d, Y', $newest_date ),
+ 'inclusive' => true,
),
)
);
@@ -233,7 +239,6 @@ class Secure {
if ( ! is_array( $issue_counts ) || ! $issue_counts ) {
$issue_counts = array(
- 'good' => 0,
'recommended' => 0,
'critical' => 0,
);
@@ -253,15 +258,14 @@ class Secure {
$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 );
+ $od = gmdate( 'F d, Y h:iA', $oldest_date );
+ $nd = gmdate( 'F d, Y h:iA', $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 ';
@@ -277,12 +281,22 @@ class Secure {
$content .= $br . $br;
$suspicious_accounts_url = admin_url( 'users.php?um_status=inactive' );
+ if ( $might_affected_users->get_total() > 0 ) {
+ $od = gmdate( 'F d, Y', $oldest_date );
+ $nd = gmdate( 'F d, Y', $newest_date );
+ if ( $od !== $nd ) {
+ $suspicious_accounts_url = admin_url( 'users.php?um_secure_date_from=' . $oldest_date . '&um_secure_date_to=' . $newest_date );
+ } else {
+ $suspicious_accounts_url = admin_url( 'users.php?um_secure_date_from=' . $oldest_date );
+ }
+ }
+
$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 .= '4. 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 .';
From 3ca3d0c0e8c1b6a9d994e60e01faf9ac04f7ccb9 Mon Sep 17 00:00:00 2001
From: Champ Camba
Date: Fri, 7 Jul 2023 17:37:43 +0800
Subject: [PATCH 38/45] Fix date range to filter suspcicious accounts and
possible affected users
---
includes/admin/class-secure.php | 8 +++++---
includes/ajax/class-secure.php | 6 +++---
2 files changed, 8 insertions(+), 6 deletions(-)
diff --git a/includes/admin/class-secure.php b/includes/admin/class-secure.php
index 1062d20f..262d4ee2 100644
--- a/includes/admin/class-secure.php
+++ b/includes/admin/class-secure.php
@@ -82,15 +82,17 @@ if ( ! class_exists( 'um\admin\Secure' ) ) {
$query->set(
'date_query',
array(
- 'after' => human_time_diff( $date_from, strtotime( current_time( 'mysql' ) ) ) . ' ago',
+ 'after' => human_time_diff( $date_from, strtotime( current_time( 'mysql' ) ) ) . ' ago',
+ 'inclusive' => true,
)
);
} elseif ( $date_from && $date_to ) {
$query->set(
'date_query',
array(
- 'after' => human_time_diff( $date_from, strtotime( current_time( 'mysql' ) ) ) . ' ago',
- 'before' => human_time_diff( $date_to, strtotime( current_time( 'mysql' ) ) ) . ' ago',
+ 'after' => human_time_diff( $date_from, strtotime( current_time( 'mysql' ) ) ) . ' ago',
+ 'before' => human_time_diff( $date_to, strtotime( current_time( 'mysql' ) ) ) . ' ago',
+ 'inclusive' => true,
)
);
}
diff --git a/includes/ajax/class-secure.php b/includes/ajax/class-secure.php
index 8a884f22..8a5c7cc9 100644
--- a/includes/ajax/class-secure.php
+++ b/includes/ajax/class-secure.php
@@ -218,12 +218,12 @@ class Secure {
'number' => -1,
'exclude' => $arr_suspected_accounts,
'date_query' => array(
- 'after' => gmdate( 'F d, Y', $oldest_date ),
- 'before' => gmdate( 'F d, Y', $newest_date ),
- 'inclusive' => true,
+ 'after' => gmdate( 'F d, Y', strtotime( '-1 day', $oldest_date ) ),
+ 'before' => gmdate( 'F d, Y', strtotime( '+1 day', $newest_date ) ),
),
)
);
+
}
/**
From fda882d35c387e70b88c20053a6f75fa50ecb133 Mon Sep 17 00:00:00 2001
From: Champ Camba
Date: Fri, 7 Jul 2023 17:39:02 +0800
Subject: [PATCH 39/45] Remove debug text
---
includes/ajax/class-secure.php | 2 --
1 file changed, 2 deletions(-)
diff --git a/includes/ajax/class-secure.php b/includes/ajax/class-secure.php
index 8a5c7cc9..b92df089 100644
--- a/includes/ajax/class-secure.php
+++ b/includes/ajax/class-secure.php
@@ -211,8 +211,6 @@ class Secure {
$oldest_date = min( $arr_dates_in_timestamp );
$newest_date = max( $arr_dates_in_timestamp );
- $content .= gmdate( 'F d, Y', $newest_date );
-
$might_affected_users = new WP_User_Query(
array(
'number' => -1,
From 251753bbbfe3124fa0d5299786ababcb32c3c1a6 Mon Sep 17 00:00:00 2001
From: Champ Camba
Date: Fri, 7 Jul 2023 17:39:49 +0800
Subject: [PATCH 40/45] Remove hours from the detection
---
includes/ajax/class-secure.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/includes/ajax/class-secure.php b/includes/ajax/class-secure.php
index b92df089..86fc1642 100644
--- a/includes/ajax/class-secure.php
+++ b/includes/ajax/class-secure.php
@@ -256,8 +256,8 @@ class Secure {
$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 d, Y h:iA', $oldest_date );
- $nd = gmdate( 'F d, Y h:iA', $newest_date );
+ $od = gmdate( 'F d, Y', $oldest_date );
+ $nd = gmdate( 'F d, Y', $newest_date );
if ( $od !== $nd ) {
$date_registered = $od . ' to ' . $nd;
} else {
From cb3897c48bb3d0f43f3f1bef5b3ac41a944c7f47 Mon Sep 17 00:00:00 2001
From: Mykyta Synelnikov
Date: Fri, 7 Jul 2023 13:34:55 +0300
Subject: [PATCH 41/45] - UM cron class review; - schedule events on the first
install; - make an event's starting time base on recurrence;
---
includes/class-init.php | 2 +
includes/core/class-cron.php | 92 +++++++++++++++++++-----------------
2 files changed, 50 insertions(+), 44 deletions(-)
diff --git a/includes/class-init.php b/includes/class-init.php
index 2e40ea66..2cf9fdf6 100644
--- a/includes/class-init.php
+++ b/includes/class-init.php
@@ -529,6 +529,8 @@ if ( ! class_exists( 'UM' ) ) {
//run setup
$this->common()->cpt()->create_post_types();
$this->setup()->run_setup();
+
+ $this->cron()->schedule_events();
}
diff --git a/includes/core/class-cron.php b/includes/core/class-cron.php
index 93a1083f..63b0ad34 100644
--- a/includes/core/class-cron.php
+++ b/includes/core/class-cron.php
@@ -1,122 +1,126 @@
- */
- $um_cron = apply_filters( 'um_cron_disable', false );
- if ( $um_cron ) {
- return;
- }
-
add_filter( 'cron_schedules', array( $this, 'add_schedules' ) );
- add_action( 'wp', array( $this, 'schedule_Events' ) );
+ add_action( 'wp', array( $this, 'schedule_events' ) );
}
+ /**
+ * @return bool
+ */
+ private function cron_disabled() {
+ /**
+ * Filters variable for disable Ultimate Member WP Cron actions.
+ *
+ * @since 2.0
+ * @hook um_cron_disable
+ *
+ * @param {bool} $is_disabled Shortcode arguments.
+ *
+ * @return {bool} Do Cron actions are disabled? True for disable.
+ *
+ * @example Disable all Ultimate Member WP Cron actions.
+ * add_filter( 'um_cron_disable', '__return_true' );
+ */
+ return apply_filters( 'um_cron_disable', false );
+ }
/**
+ * Adds once weekly to the existing schedules.
+ *
* @param array $schedules
*
* @return array
*/
public function add_schedules( $schedules = array() ) {
+ if ( $this->cron_disabled() ) {
+ return $schedules;
+ }
- // Adds once weekly to the existing schedules.
$schedules['weekly'] = array(
'interval' => 604800,
- 'display' => __( 'Once Weekly', 'ultimate-member' )
+ 'display' => __( 'Once Weekly', 'ultimate-member' ),
);
return $schedules;
}
-
/**
*
*/
- public function schedule_Events() {
+ public function schedule_events() {
+ if ( $this->cron_disabled() ) {
+ return;
+ }
+
$this->weekly_events();
$this->daily_events();
$this->twicedaily_events();
$this->hourly_events();
}
-
/**
*
*/
private function weekly_events() {
+ $sunday_start = wp_date( 'w' );
+ $week_start = $sunday_start - absint( get_option( 'start_of_week' ) );
+ $week_start_day = strtotime( '-' . $week_start . ' days' );
+ $time = mktime( 0, 0, 0, wp_date( 'm', $week_start_day ), wp_date( 'd', $week_start_day ), wp_date( 'Y', $week_start_day ) );
if ( ! wp_next_scheduled( 'um_weekly_scheduled_events' ) ) {
- wp_schedule_event( current_time( 'timestamp' ), 'weekly', 'um_weekly_scheduled_events' );
+ wp_schedule_event( $time, 'weekly', 'um_weekly_scheduled_events' );
}
}
-
/**
*
*/
private function daily_events() {
if ( ! wp_next_scheduled( 'um_daily_scheduled_events' ) ) {
- wp_schedule_event( current_time( 'timestamp' ), 'daily', 'um_daily_scheduled_events' );
+ $time = mktime( 0, 0, 0, wp_date( 'm' ), wp_date( 'd' ), wp_date( 'Y' ) );
+ wp_schedule_event( $time, 'daily', 'um_daily_scheduled_events' );
}
}
-
/**
*
*/
private function twicedaily_events() {
if ( ! wp_next_scheduled( 'um_twicedaily_scheduled_events' ) ) {
- wp_schedule_event( current_time( 'timestamp' ), 'twicedaily', 'um_twicedaily_scheduled_events' );
+ $time = mktime( 0, 0, 0, wp_date( 'm' ), wp_date( 'd' ), wp_date( 'Y' ) );
+ wp_schedule_event( $time, 'twicedaily', 'um_twicedaily_scheduled_events' );
}
}
-
/**
*
*/
private function hourly_events() {
if ( ! wp_next_scheduled( 'um_hourly_scheduled_events' ) ) {
- wp_schedule_event( current_time( 'timestamp' ), 'hourly', 'um_hourly_scheduled_events' );
+ $time = mktime( wp_date( 'H' ), 0, 0, wp_date( 'm' ), wp_date( 'd' ), wp_date( 'Y' ) );
+ wp_schedule_event( $time, 'hourly', 'um_hourly_scheduled_events' );
}
}
-
+ /**
+ * Breaks all Ultimate Member registered schedule events.
+ */
public function unschedule_events() {
wp_clear_scheduled_hook( 'um_weekly_scheduled_events' );
wp_clear_scheduled_hook( 'um_daily_scheduled_events' );
From fe3bea640baf40d2e127baad54f75db02b6410dd Mon Sep 17 00:00:00 2001
From: Champ Camba
Date: Fri, 7 Jul 2023 19:13:10 +0800
Subject: [PATCH 42/45] Fix issue with saved capabilities
---
includes/frontend/class-secure.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/includes/frontend/class-secure.php b/includes/frontend/class-secure.php
index bbc5f401..e7083a4d 100644
--- a/includes/frontend/class-secure.php
+++ b/includes/frontend/class-secure.php
@@ -199,7 +199,7 @@ if ( ! class_exists( 'um\frontend\Secure' ) ) {
$arr_banned_caps = array();
if ( UM()->options()->get( 'banned_capabilities' ) ) {
- $arr_banned_caps = UM()->options()->get( 'banned_capabilities' );
+ $arr_banned_caps = array_keys( UM()->options()->get( 'banned_capabilities' ) );
}
// Add locked administrative capabilities.
From c7dd1a80f3f6ce451e7d6b66b767e1f82cb09a4e Mon Sep 17 00:00:00 2001
From: Champ Camba
Date: Fri, 7 Jul 2023 19:29:45 +0800
Subject: [PATCH 43/45] Revert the notice to save settings when banned
capabilities are modified
---
includes/admin/assets/js/um-admin-secure.js | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/includes/admin/assets/js/um-admin-secure.js b/includes/admin/assets/js/um-admin-secure.js
index d58681a9..c4bc45d8 100644
--- a/includes/admin/assets/js/um-admin-secure.js
+++ b/includes/admin/assets/js/um-admin-secure.js
@@ -29,7 +29,10 @@ jQuery(document).on("ready", function(){
UM_Secure.ajax('');
});
-
+ scan_capabilities.on("change", function(){
+ scan_button_elem.after( ' ' + wp.i18n.__( 'You can start the scan now but you must save the settings to apply the selected capabilities after the scan is complete.', 'ultimate-member' ) + ' ' );
+ scan_capabilities.off("change");
+ })
},
ajax: function( last_capability ) {
let checkedCaps = [];
From 0bb73218bad1a349517156830daacc959c62648f Mon Sep 17 00:00:00 2001
From: Mykyta Synelnikov
Date: Fri, 7 Jul 2023 15:06:08 +0300
Subject: [PATCH 44/45] - fixed conflict with saving capabilities;
---
includes/admin/class-admin.php | 10 ++++++-
includes/admin/class-secure.php | 5 +---
.../admin/core/class-admin-forms-settings.php | 2 +-
includes/admin/core/class-admin-forms.php | 15 ++++++++---
includes/admin/core/class-admin-settings.php | 2 +-
includes/common/class-secure.php | 17 ++++++++++++
includes/frontend/class-secure.php | 27 ++++++++-----------
7 files changed, 51 insertions(+), 27 deletions(-)
diff --git a/includes/admin/class-admin.php b/includes/admin/class-admin.php
index d2ca2d03..91ff1609 100644
--- a/includes/admin/class-admin.php
+++ b/includes/admin/class-admin.php
@@ -1081,7 +1081,6 @@ if ( ! class_exists( 'um\admin\Admin' ) ) {
return $value;
}
-
/**
* @param $value
*
@@ -1092,6 +1091,15 @@ if ( ! class_exists( 'um\admin\Admin' ) ) {
return $value;
}
+ /**
+ * @param $value
+ *
+ * @return array
+ */
+ public function sanitize_wp_capabilities_assoc( $value ) {
+ $value = array_map( 'sanitize_key', array_filter( $value ) );
+ return $value;
+ }
/**
* Sanitize role meta fields when wp-admin form has been submitted
diff --git a/includes/admin/class-secure.php b/includes/admin/class-secure.php
index 262d4ee2..5bd1289d 100644
--- a/includes/admin/class-secure.php
+++ b/includes/admin/class-secure.php
@@ -206,8 +206,6 @@ if ( ! class_exists( 'um\admin\Secure' ) ) {
$disabled_capabilities = UM()->options()->get_default( 'banned_capabilities' );
$disabled_capabilities_text = '' . implode( ' , ', $disabled_capabilities ) . ' ';
- $saved_options = UM()->options()->get( 'banned_capabilities' );
-
$scanner_content = '' . esc_html__( 'Scan Now', 'ultimate-member' ) . ' ';
$scanner_content .= '';
$scanner_content .= esc_html__( 'Last scan:', 'ultimate-member' ) . ' ';
@@ -228,11 +226,11 @@ if ( ! class_exists( 'um\admin\Secure' ) ) {
'id' => 'banned_capabilities',
'type' => 'multi_checkbox',
'multi' => true,
+ 'assoc' => true,
'checkbox_key' => true,
'columns' => 2,
'options_disabled' => $disabled_capabilities,
'options' => $banned_capabilities,
- 'value' => ! empty( $saved_options ) ? array_keys( $saved_options ) : $disabled_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 ),
@@ -359,7 +357,6 @@ if ( ! class_exists( 'um\admin\Secure' ) ) {
*
*/
public function on_settings_save() {
-
if ( isset( $_POST['um_options']['display_login_form_notice'] ) && ! empty( $this->need_flush_meta ) ) { //phpcs:ignore WordPress.Security.NonceVerification
global $wpdb;
$wpdb->query(
diff --git a/includes/admin/core/class-admin-forms-settings.php b/includes/admin/core/class-admin-forms-settings.php
index fb115a66..ba52b817 100644
--- a/includes/admin/core/class-admin-forms-settings.php
+++ b/includes/admin/core/class-admin-forms-settings.php
@@ -54,4 +54,4 @@ if ( ! class_exists( 'um\admin\core\Admin_Forms_Settings' ) ) {
}
}
-}
\ No newline at end of file
+}
diff --git a/includes/admin/core/class-admin-forms.php b/includes/admin/core/class-admin-forms.php
index 4b1a7b51..21bb22d9 100644
--- a/includes/admin/core/class-admin-forms.php
+++ b/includes/admin/core/class-admin-forms.php
@@ -1214,9 +1214,16 @@ if ( ! class_exists( 'um\admin\core\Admin_Forms' ) ) {
$html .= '';
foreach ( $section_fields_per_page as $k => $title ) {
- $id_attr = ' id="' . esc_attr( $id . '_' . $k ) . '" ';
- $for_attr = ' for="' . esc_attr( $id . '_' . $k ) . '" ';
- $name_attr = ' name="' . $name . '[' . $k . ']" ';
+ $id_attr = ' id="' . esc_attr( $id . '_' . $k ) . '" ';
+ $for_attr = ' for="' . esc_attr( $id . '_' . $k ) . '" ';
+
+ if ( ! empty( $field_data['assoc'] ) ) {
+ $name_attr = ' name="' . esc_attr( $name ) . '[]" ';
+ $value_attr = ' value="' . esc_attr( $k ) . '" ';
+ } else {
+ $name_attr = ' name="' . esc_attr( $name ) . '[' . esc_attr( $k ) . ']" ';
+ $value_attr = ' value="1" ';
+ }
$disabed_attr = '';
$data = array(
@@ -1240,7 +1247,7 @@ if ( ! class_exists( 'um\admin\core\Admin_Forms' ) ) {
}
$html .= "
-
+
$title
";
}
diff --git a/includes/admin/core/class-admin-settings.php b/includes/admin/core/class-admin-settings.php
index ef8a95c9..33a9d833 100644
--- a/includes/admin/core/class-admin-settings.php
+++ b/includes/admin/core/class-admin-settings.php
@@ -954,7 +954,7 @@ if ( ! class_exists( 'um\admin\core\Admin_Settings' ) ) {
'sanitize' => 'bool',
),
'banned_capabilities' => array(
- 'sanitize' => array( UM()->admin(), 'sanitize_wp_capabilities' ),
+ 'sanitize' => array( UM()->admin(), 'sanitize_wp_capabilities_assoc' ),
),
'secure_notify_admins_banned_accounts' => array(
'sanitize' => 'bool',
diff --git a/includes/common/class-secure.php b/includes/common/class-secure.php
index c3bb3ab2..73e57a23 100644
--- a/includes/common/class-secure.php
+++ b/includes/common/class-secure.php
@@ -20,6 +20,7 @@ if ( ! class_exists( 'um\common\Secure' ) ) {
public function hooks() {
add_action( 'wp', array( $this, 'schedule_events' ) );
+ add_filter( 'um_get_option_filter__banned_capabilities', array( $this, 'add_default_capabilities' ) );
}
/**
@@ -236,5 +237,21 @@ if ( ! class_exists( 'um\common\Secure' ) ) {
update_user_meta( $user->ID, 'um_user_blocked', 'suspicious_activity' );
update_user_meta( $user->ID, 'um_user_blocked__timestamp', current_time( 'mysql' ) );
}
+
+ /**
+ * Always add default banned capabilities.
+ *
+ * @param mixed $option_value
+ *
+ * @return mixed
+ *
+ * @since 2.6.8
+ */
+ public function add_default_capabilities( $option_value ) {
+ if ( is_array( $option_value ) ) {
+ $option_value = array_merge( $option_value, UM()->options()->get_default( 'banned_capabilities' ) );
+ }
+ return $option_value;
+ }
}
}
diff --git a/includes/frontend/class-secure.php b/includes/frontend/class-secure.php
index bbc5f401..dfcb51de 100644
--- a/includes/frontend/class-secure.php
+++ b/includes/frontend/class-secure.php
@@ -196,23 +196,18 @@ if ( ! class_exists( 'um\frontend\Secure' ) ) {
// Fetch the WP_User object of our user.
um_fetch_user( $user_id );
$has_admin_cap = false;
- $arr_banned_caps = array();
+ $arr_banned_caps = UM()->options()->get( 'banned_capabilities' );
- if ( UM()->options()->get( 'banned_capabilities' ) ) {
- $arr_banned_caps = UM()->options()->get( 'banned_capabilities' );
- }
-
- // Add locked administrative capabilities.
- $arr_banned_caps = array_merge( $arr_banned_caps, UM()->options()->get_default( 'banned_capabilities' ) );
-
- foreach ( $arr_banned_caps as $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;
- break;
+ if ( is_array( $arr_banned_caps ) ) {
+ foreach ( $arr_banned_caps as $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;
+ break;
+ }
}
}
From 479c2b03e1abbaec7525544f7fe54a9a018f40de Mon Sep 17 00:00:00 2001
From: Champ Camba
Date: Sat, 8 Jul 2023 01:53:21 +0800
Subject: [PATCH 45/45] Fix blocked email recommendation
---
includes/ajax/class-secure.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/includes/ajax/class-secure.php b/includes/ajax/class-secure.php
index 86fc1642..30576a3d 100644
--- a/includes/ajax/class-secure.php
+++ b/includes/ajax/class-secure.php
@@ -334,7 +334,7 @@ class Secure {
$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 . $check . 'Blocked Emails option is already set.';
$content .= $br;
}