Files
laipower/wp-content/plugins/two-factor/providers/class-two-factor-email.php

466 lines
12 KiB
PHP

<?php
/**
* Class for creating an email provider.
*
* @package Two_Factor
*/
/**
* Class for creating an email provider.
*
* @since 0.1-dev
*
* @package Two_Factor
*/
class Two_Factor_Email extends Two_Factor_Provider {
/**
* The user meta token key.
*
* @var string
*/
const TOKEN_META_KEY = '_two_factor_email_token';
/**
* Store the timestamp when the token was generated.
*
* @var string
*/
const TOKEN_META_KEY_TIMESTAMP = '_two_factor_email_token_timestamp';
/**
* Name of the input field used for code resend.
*
* @var string
*/
const INPUT_NAME_RESEND_CODE = 'two-factor-email-code-resend';
/**
* Class constructor.
*
* @since 0.1-dev
*
* @codeCoverageIgnore
*/
protected function __construct() {
add_action( 'two_factor_user_options_' . __CLASS__, array( $this, 'user_options' ) );
parent::__construct();
}
/**
* Returns the name of the provider.
*
* @since 0.1-dev
*/
public function get_label() {
return _x( 'Email', 'Provider Label', 'two-factor' );
}
/**
* Returns the "continue with" text provider for the login screen.
*
* @since 0.9.0
*/
public function get_alternative_provider_label() {
return __( 'Send a code to your email', 'two-factor' );
}
/**
* Get the email token length.
*
* @since 0.11.0
*
* @return int Email token string length.
*/
private function get_token_length() {
/**
* Filters the number of characters in the email token.
*
* @since 0.11.0
*
* @param int $token_length Number of characters in the email token. Default 8.
*/
$token_length = (int) apply_filters( 'two_factor_email_token_length', 8 );
return $token_length;
}
/**
* Generate the user token.
*
* @since 0.1-dev
*
* @param int $user_id User ID.
* @return string
*/
public function generate_token( $user_id ) {
$token = $this->get_code( $this->get_token_length() );
update_user_meta( $user_id, self::TOKEN_META_KEY_TIMESTAMP, time() );
update_user_meta( $user_id, self::TOKEN_META_KEY, wp_hash( $token ) );
return $token;
}
/**
* Check if user has a valid token already.
*
* @since 0.2.0
*
* @param int $user_id User ID.
* @return boolean If user has a valid email token.
*/
public function user_has_token( $user_id ) {
$hashed_token = $this->get_user_token( $user_id );
if ( ! empty( $hashed_token ) ) {
return true;
}
return false;
}
/**
* Has the user token validity timestamp expired.
*
* @since 0.6.0
*
* @param integer $user_id User ID.
*
* @return boolean
*/
public function user_token_has_expired( $user_id ) {
$token_lifetime = $this->user_token_lifetime( $user_id );
$token_ttl = $this->user_token_ttl( $user_id );
// Invalid token lifetime is considered an expired token.
if ( is_int( $token_lifetime ) && $token_lifetime <= $token_ttl ) {
return false;
}
return true;
}
/**
* Get the lifetime of a user token in seconds.
*
* @since 0.6.0
*
* @param integer $user_id User ID.
*
* @return integer|null Return `null` if the lifetime can't be measured.
*/
public function user_token_lifetime( $user_id ) {
$timestamp = intval( get_user_meta( $user_id, self::TOKEN_META_KEY_TIMESTAMP, true ) );
if ( ! empty( $timestamp ) ) {
return time() - $timestamp;
}
return null;
}
/**
* Return the token time-to-live for a user.
*
* @since 0.6.0
*
* @param integer $user_id User ID.
*
* @return integer
*/
public function user_token_ttl( $user_id ) {
$token_ttl = 15 * MINUTE_IN_SECONDS;
/**
* Filters the number of seconds the email token is considered valid after generation.
*
* @since 0.6.0
* @deprecated 0.11.0 Use {@see 'two_factor_email_token_ttl'} instead.
*
* @param int $token_ttl Token time-to-live in seconds.
* @param int $user_id User ID.
*/
$token_ttl = (int) apply_filters_deprecated( 'two_factor_token_ttl', array( $token_ttl, $user_id ), '0.11.0', 'two_factor_email_token_ttl' );
/**
* Filters the number of seconds the email token is considered valid after generation.
*
* @since 0.11.0
*
* @param int $token_ttl Token time-to-live in seconds.
* @param int $user_id User ID.
*/
return (int) apply_filters( 'two_factor_email_token_ttl', $token_ttl, $user_id );
}
/**
* Get the authentication token for the user.
*
* @since 0.2.0
*
* @param int $user_id User ID.
*
* @return string|boolean User token or `false` if no token found.
*/
public function get_user_token( $user_id ) {
$hashed_token = get_user_meta( $user_id, self::TOKEN_META_KEY, true );
if ( ! empty( $hashed_token ) && is_string( $hashed_token ) ) {
return $hashed_token;
}
return false;
}
/**
* Validate the user token.
*
* @since 0.1-dev
*
* @param int $user_id User ID.
* @param string $token User token.
* @return boolean
*/
public function validate_token( $user_id, $token ) {
$hashed_token = $this->get_user_token( $user_id );
// Bail if token is empty or it doesn't match.
if ( empty( $hashed_token ) || ! hash_equals( wp_hash( $token ), $hashed_token ) ) {
return false;
}
if ( $this->user_token_has_expired( $user_id ) ) {
return false;
}
// Ensure the token can be used only once.
$this->delete_token( $user_id );
return true;
}
/**
* Delete the user token.
*
* @since 0.1-dev
*
* @param int $user_id User ID.
*/
public function delete_token( $user_id ) {
delete_user_meta( $user_id, self::TOKEN_META_KEY );
}
/**
* Get the client IP address for the current request.
*
* @since 0.15.0
*
* Note that the IP address is used only for information purposes
* and is expected to be configured correctly, if behind proxy.
*
* @return string|null
*/
private function get_client_ip() {
if ( ! empty( $_SERVER['REMOTE_ADDR'] ) ) { // phpcs:ignore WordPressVIPMinimum.Variables.ServerVariables.UserControlledHeaders -- don't have more reliable option for now.
return preg_replace( '/[^0-9a-fA-F:., ]/', '', $_SERVER['REMOTE_ADDR'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPressVIPMinimum.Variables.ServerVariables.UserControlledHeaders, WordPressVIPMinimum.Variables.RestrictedVariables.cache_constraints___SERVER__REMOTE_ADDR__ -- we're limit the allowed characters.
}
return null;
}
/**
* Generate and email the user token.
*
* @since 0.1-dev
*
* @param WP_User $user WP_User object of the logged-in user.
* @return bool Whether the email contents were sent successfully.
*/
public function generate_and_email_token( $user ) {
$token = $this->generate_token( $user->ID );
$remote_ip = $this->get_client_ip();
$ttl_minutes = (int) ceil( $this->user_token_ttl( $user->ID ) / MINUTE_IN_SECONDS );
$subject = wp_strip_all_tags(
sprintf(
/* translators: %s: site name */
__( '[%s] Login confirmation code', 'two-factor' ),
wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES )
)
);
$message_parts = array(
__( 'Please complete the login by entering the verification code below:', 'two-factor' ),
$token,
sprintf(
/* translators: %d: number of minutes */
__( 'This code will expire in %d minutes.', 'two-factor' ),
$ttl_minutes
),
sprintf(
/* translators: %1$s: IP address of user, %2$s: user login */
__( 'A user from IP address %1$s has successfully authenticated as %2$s. If this wasn\'t you, please change your password.', 'two-factor' ),
$remote_ip,
$user->user_login
),
);
$message = wp_strip_all_tags( implode( "\n\n", $message_parts ) );
/**
* Filters the token email subject.
*
* @since 0.5.2
*
* @param string $subject The email subject line.
* @param int $user_id The ID of the user.
*/
$subject = apply_filters( 'two_factor_token_email_subject', $subject, $user->ID );
/**
* Filters the token email message.
*
* @since 0.5.2
*
* @param string $message The email message.
* @param string $token The token.
* @param int $user_id The ID of the user.
*/
$message = apply_filters( 'two_factor_token_email_message', $message, $token, $user->ID );
return wp_mail( $user->user_email, $subject, $message ); // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.wp_mail_wp_mail
}
/**
* Prints the form that prompts the user to authenticate.
*
* @since 0.1-dev
*
* @param WP_User $user WP_User object of the logged-in user.
*/
public function authentication_page( $user ) {
if ( ! $user ) {
return;
}
if ( ! $this->user_has_token( $user->ID ) || $this->user_token_has_expired( $user->ID ) ) {
$this->generate_and_email_token( $user );
}
$token_length = $this->get_token_length();
$token_placeholder = str_repeat( 'X', $token_length );
require_once ABSPATH . '/wp-admin/includes/template.php';
?>
<?php
/** This action is documented in providers/class-two-factor-backup-codes.php */
do_action( 'two_factor_before_authentication_prompt', $this );
?>
<p class="two-factor-prompt"><?php esc_html_e( 'A verification code has been sent to the email address associated with your account.', 'two-factor' ); ?></p>
<?php
/** This action is documented in providers/class-two-factor-backup-codes.php */
do_action( 'two_factor_after_authentication_prompt', $this );
?>
<p>
<label for="authcode"><?php esc_html_e( 'Verification Code:', 'two-factor' ); ?></label>
<input type="text" inputmode="numeric" name="two-factor-email-code" id="authcode" class="input authcode" value="" size="20" pattern="[0-9 ]*" autocomplete="one-time-code" placeholder="<?php echo esc_attr( $token_placeholder ); ?>" data-digits="<?php echo esc_attr( $token_length ); ?>" />
</p>
<?php
/** This action is documented in providers/class-two-factor-backup-codes.php */
do_action( 'two_factor_after_authentication_input', $this );
?>
<?php submit_button( __( 'Verify', 'two-factor' ) ); ?>
<p class="two-factor-email-resend">
<input type="submit" class="button" name="<?php echo esc_attr( self::INPUT_NAME_RESEND_CODE ); ?>" value="<?php esc_attr_e( 'Resend Code', 'two-factor' ); ?>" />
</p>
<?php wp_enqueue_script( 'two-factor-login' ); ?>
<?php
}
/**
* Send the email code if missing or requested. Stop the authentication
* validation if a new token has been generated and sent.
*
* @since 0.2.0
*
* @param WP_User $user WP_User object of the logged-in user.
* @return boolean
*/
public function pre_process_authentication( $user ) {
if ( isset( $user->ID ) && isset( $_REQUEST[ self::INPUT_NAME_RESEND_CODE ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- non-distructive option that relies on user state.
$this->generate_and_email_token( $user );
return true;
}
return false;
}
/**
* Validates the users input token.
*
* @since 0.1-dev
*
* @param WP_User $user WP_User object of the logged-in user.
* @return boolean
*/
public function validate_authentication( $user ) {
$code = $this->sanitize_code_from_request( 'two-factor-email-code' );
if ( ! isset( $user->ID ) || ! $code ) {
return false;
}
return $this->validate_token( $user->ID, $code );
}
/**
* Whether this Two Factor provider is configured and available for the user specified.
*
* @since 0.1-dev
*
* @param WP_User $user WP_User object of the logged-in user.
* @return boolean
*/
public function is_available_for_user( $user ) {
return true;
}
/**
* Inserts markup at the end of the user profile field for this provider.
*
* @since 0.1-dev
*
* @param WP_User $user WP_User object of the logged-in user.
*/
public function user_options( $user ) {
$email = $user->user_email;
?>
<p>
<?php
echo esc_html(
sprintf(
/* translators: %s: email address */
__( 'Authentication codes will be sent to %s.', 'two-factor' ),
$email
)
);
?>
</p>
<?php
}
/**
* Return user meta keys to delete during plugin uninstall.
*
* @since 0.10.0
*
* @return array
*/
public static function uninstall_user_meta_keys() {
return array(
self::TOKEN_META_KEY,
self::TOKEN_META_KEY_TIMESTAMP,
);
}
}