From a2a56c8af76a7f6f8f521fe76d848649496be67f Mon Sep 17 00:00:00 2001 From: Champ Camba Date: Sat, 1 Jul 2023 17:48:45 +0800 Subject: [PATCH] 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 + } + + } + + +}