400 ) ); } // Check if name already exists. if ( static::application_name_exists_for_user( $user_id, $args['name'] ) ) { return new \WP_Error( 'application_password_exists', __( 'Each application name should be unique.', 'mainwp' ), array( 'status' => 409 ) ); } $new_password = wp_generate_password( static::PW_LENGTH, false ); $hashed_password = wp_hash_password( $new_password ); $new_item = array( 'uuid' => wp_generate_uuid4(), 'app_id' => empty( $args['app_id'] ) ? '' : $args['app_id'], 'name' => $args['name'], 'password' => $hashed_password, 'created' => time(), 'last_used' => null, 'last_ip' => null, ); $passwords = static::get_user_application_passwords( $user_id ); $passwords[] = $new_item; $saved = static::set_user_application_passwords( $user_id, $passwords ); if ( ! $saved ) { return new \WP_Error( 'db_error', __( 'Could not save application password.', 'mainwp' ) ); } /** * Fires when an application password is created. * * @param int $user_id The user ID. * @param array $new_item The details about the created password. * @param string $new_password The generated application password in plain text. * @param array $args Arguments used to create the application password. */ do_action( 'mainwp_create_application_password', $user_id, $new_item, $new_password, $args ); return array( $new_password, $new_item ); } /** * Gets a user's application passwords. * * @param int $user_id User ID. * @return array The list of application passwords. */ public static function get_user_application_passwords( $user_id ) { $passwords = get_user_meta( $user_id, static::USERMETA_KEY_APPLICATION_PASSWORDS, true ); if ( ! is_array( $passwords ) ) { return array(); } $save = false; foreach ( $passwords as $i => $password ) { if ( ! isset( $password['uuid'] ) ) { $passwords[ $i ]['uuid'] = wp_generate_uuid4(); $save = true; } } if ( $save ) { static::set_user_application_passwords( $user_id, $passwords ); } return $passwords; } /** * Gets a user's application password with the given UUID. * * @param int $user_id User ID. * @param string $uuid The password's UUID. * @return array|null The application password if found, null otherwise. */ public static function get_user_application_password( $user_id, $uuid ) { $passwords = static::get_user_application_passwords( $user_id ); foreach ( $passwords as $password ) { if ( $password['uuid'] === $uuid ) { return $password; } } return null; } /** * Checks if an application password with the given name exists for this user. * * @param int $user_id User ID. * @param string $name Application name. * @return bool Whether the provided application name exists. */ public static function application_name_exists_for_user( $user_id, $name ) { $passwords = static::get_user_application_passwords( $user_id ); foreach ( $passwords as $password ) { if ( strtolower( $password['name'] ) === strtolower( $name ) ) { return true; } } return false; } /** * Updates the application passwords list for a user. * * @param int $user_id User ID. * @param array $passwords Application passwords. * @return bool Whether the update was successful. */ protected static function set_user_application_passwords( $user_id, $passwords ) { return update_user_meta( $user_id, static::USERMETA_KEY_APPLICATION_PASSWORDS, $passwords ); } /** * Updates an application password. * * @param int $user_id User ID. * @param string $uuid The password's UUID. * @param array $update Information about the application password to update. * @return true|\WP_Error True if successful, otherwise a WP_Error instance is returned on error. */ public static function update_application_password( $user_id, $uuid, $update = array() ) { // phpcs:ignore -- NOSONAR - complex. $passwords = get_user_meta( $user_id, static::USERMETA_KEY_APPLICATION_PASSWORDS, true ); if ( ! is_array( $passwords ) ) { return new \WP_Error( 'application_password_not_found', static::get_error_message() ); } foreach ( $passwords as &$item ) { if ( $item['uuid'] !== $uuid ) { continue; } if ( ! empty( $update['name'] ) ) { $update['name'] = sanitize_text_field( $update['name'] ); } $save = false; if ( ! empty( $update['name'] ) && $item['name'] !== $update['name'] ) { $item['name'] = $update['name']; $save = true; } if ( isset( $update['last_used'] ) ) { $item['last_used'] = $update['last_used']; $save = true; } if ( isset( $update['last_ip'] ) ) { $item['last_ip'] = $update['last_ip']; $save = true; } if ( $save ) { $saved = static::set_user_application_passwords( $user_id, $passwords ); if ( ! $saved ) { return new \WP_Error( 'db_error', __( 'Could not save application password.', 'mainwp' ) ); } /** * Fires when an application password is updated. * * @param int $user_id The user ID. * @param array $item The updated application password details. * @param array $update The information to update. */ do_action( 'mainwp_update_application_password', $user_id, $item, $update ); } return true; } return new \WP_Error( 'application_password_not_found', static::get_error_message() ); } /** * Records that an application password has been used. * * @param int $user_id User ID. * @param string $uuid The password's UUID. * @return true|\WP_Error True if successful, otherwise a WP_Error instance is returned on error. */ public static function record_application_password_usage( $user_id, $uuid ) { $remote_add = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : null; $update = array( 'last_used' => time(), 'last_ip' => $remote_add, ); return static::update_application_password( $user_id, $uuid, $update ); } /** * Deletes an application password. * * @param int $user_id User ID. * @param string $uuid The password's UUID. * @return true|\WP_Error Whether the password was successfully found and deleted, a WP_Error otherwise. */ public static function delete_application_password( $user_id, $uuid ) { // phpcs:ignore -- NOSONAR - complex. $passwords = get_user_meta( $user_id, static::USERMETA_KEY_APPLICATION_PASSWORDS, true ); if ( ! is_array( $passwords ) ) { return new \WP_Error( 'application_password_not_found', static::get_error_message() ); } foreach ( $passwords as $key => $item ) { if ( $item['uuid'] === $uuid ) { unset( $passwords[ $key ] ); $saved = static::set_user_application_passwords( $user_id, $passwords ); if ( ! $saved ) { return new \WP_Error( 'db_error', __( 'Could not delete application password.', 'mainwp' ) ); } /** * Fires when an application password is deleted. * * @param int $user_id The user ID. * @param array $item The application password that was deleted. */ do_action( 'mainwp_delete_application_password', $user_id, $item ); return true; } } return new \WP_Error( 'application_password_not_found', static::get_error_message() ); } /** * Sanitizes and then splits a password into smaller chunks. * * @param string $raw_password The raw application password. * @return string The chunked password. */ public static function chunk_password( $raw_password ) { $raw_password = preg_replace( '/[^a-z\d]/i', '', $raw_password ); return trim( chunk_split( $raw_password, 4, ' ' ) ); } /** * Deletes all application passwords for the given user. * * @param int $user_id User ID. * @return int| \WP_Error The number of passwords that were deleted or a WP_Error on failure. */ public static function delete_all_application_passwords( $user_id ) { $passwords = static::get_user_application_passwords( $user_id ); $count = count( $passwords ); if ( $count > 0 ) { $saved = delete_user_meta( $user_id, static::USERMETA_KEY_APPLICATION_PASSWORDS ); if ( ! $saved ) { return new \WP_Error( 'db_error', __( 'Could not delete application passwords.', 'mainwp' ) ); } /** * Fires when all application passwords for a user are deleted. * * @param int $user_id The user ID. * @param int $count The number of passwords that were deleted. */ do_action( 'mainwp_delete_all_application_passwords', $user_id, $count ); } return $count; } /** * Get error message. * * @return string Error message. */ public static function get_error_message() { return esc_html__( 'Could not find an application password with that id.', 'mainwp' ); } /** * Determine if user can view Application Passwords. * * @return bool */ public static function can_access_application_passwords() { // phpcs:ignore -- used outside. if ( static::can_view_manage_application_passwords() || static::can_view_all_application_passwords() || static::can_create_application_passwords() || static::can_delete_application_passwords() || static::can_edit_application_passwords() ) { return true; } return false; } /** * Determine if user can manage Application Passwords (create/delete). * * @return bool */ public static function can_manage_application_passwords() { return static::can_view_manage_application_passwords(); } /** * Determine if user can add Application Passwords. * * @return bool */ public static function can_create_application_passwords() { if ( static::is_special_admin_user() ) { return true; } return \mainwp_current_user_can( 'rest_api', 'create_application_passwords' ); } /** * Check if the current user can editApplication Passwords. * * @return bool */ public static function can_edit_application_passwords() { if ( static::is_special_admin_user() ) { return true; } return \mainwp_current_user_can( 'rest_api', 'edit_application_passwords' ); } /** * Determine if user can revoke Application Passwords. * * @return bool */ public static function can_delete_application_passwords() { if ( static::is_special_admin_user() ) { return true; } return \mainwp_current_user_can( 'rest_api', 'delete_application_passwords' ); } /** * Check if the current user can view all application passwords. * * @return bool */ public static function can_view_all_application_passwords() { // phpcs:ignore -- NOSONAR - complex. // 1. Special admin has full view-all. if ( static::is_special_admin_user() ) { return true; } // 2. If user is an administrator but NOT special admin, explicitly deny view-all. if ( static::is_admin_user_role() ) { return false; } // 3. Non-admins: only allow if Team Control explicitly grants. $explicit_grant = apply_filters( 'mainwp_currentusercan', false, 'rest_api', 'view_all_application_passwords' ); return (bool) $explicit_grant; } /** * Check if the current user can view and manage application passwords. * * @return bool */ public static function can_view_manage_application_passwords() { // 1. Special admin can manage. if ( static::is_special_admin_user() ) { return true; } // 2. If user is an administrator but NOT special admin, explicitly deny manage-all. if ( static::is_admin_user_role() ) { return false; } // 3. Non-admins: only allow if Team Control explicitly grants. $explicit_grant = apply_filters( 'mainwp_currentusercan', false, 'rest_api', 'manage_application_passwords' ); return (bool) $explicit_grant; } /** * Check if current user is the special admin (administrator role AND ID=1 or email==admin_email). * * @return bool */ protected static function is_special_admin_user() { // phpcs:ignore -- NOSONAR $user = wp_get_current_user(); if ( ! ( $user instanceof \WP_User ) || 0 === (int) $user->ID ) { return false; } if ( ! static::is_admin_user_role() ) { return false; } if ( 1 === (int) $user->ID ) { return true; } $admin_email = get_option( 'admin_email' ); if ( ! empty( $admin_email ) && is_string( $admin_email ) ) { return strtolower( (string) $user->user_email ) === strtolower( $admin_email ); } return false; } /** * Check if current user has administrator role. * * @return bool */ protected static function is_admin_user_role() { $user = wp_get_current_user(); if ( ! ( $user instanceof \WP_User ) ) { return false; } return is_array( $user->roles ) && in_array( 'administrator', $user->roles, true ); } }