2022-10-27 11:23:01 +00:00
< ? php
/**
* Class for creating a Time Based One - Time Password provider .
*
* @ package Two_Factor
*/
/**
* Class Two_Factor_Totp
*/
class Two_Factor_Totp extends Two_Factor_Provider {
/**
2023-03-29 18:20:25 +00:00
* The user meta key for the TOTP Secret key .
2022-10-27 11:23:01 +00:00
*
* @ var string
*/
const SECRET_META_KEY = '_two_factor_totp_key' ;
/**
2023-03-29 18:20:25 +00:00
* The user meta key for the last successful TOTP token timestamp logged in with .
2022-10-27 11:23:01 +00:00
*
* @ var string
*/
2023-03-29 18:20:25 +00:00
const LAST_SUCCESSFUL_LOGIN_META_KEY = '_two_factor_totp_last_successful_login' ;
2022-10-27 11:23:01 +00:00
const DEFAULT_KEY_BIT_SIZE = 160 ;
const DEFAULT_CRYPTO = 'sha1' ;
const DEFAULT_DIGIT_COUNT = 6 ;
const DEFAULT_TIME_STEP_SEC = 30 ;
const DEFAULT_TIME_STEP_ALLOWANCE = 4 ;
/**
* Characters used in base32 encoding .
*
* @ var string
*/
private static $base_32_chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567' ;
2023-03-29 18:20:25 +00:00
/**
* Ensures only one instance of this class exists in memory at any one time .
*
* @ codeCoverageIgnore
*/
public static function get_instance () {
static $instance ;
if ( ! isset ( $instance ) ) {
$instance = new self ();
}
return $instance ;
}
2022-10-27 11:23:01 +00:00
/**
* Class constructor . Sets up hooks , etc .
*
* @ codeCoverageIgnore
*/
protected function __construct () {
2023-03-29 18:20:25 +00:00
add_action ( 'rest_api_init' , array ( $this , 'register_rest_routes' ) );
add_action ( 'admin_enqueue_scripts' , array ( $this , 'enqueue_assets' ) );
add_action ( 'wp_enqueue_scripts' , array ( $this , 'enqueue_assets' ) );
2022-10-27 11:23:01 +00:00
add_action ( 'two_factor_user_options_' . __CLASS__ , array ( $this , 'user_two_factor_options' ) );
return parent :: __construct ();
}
/**
2023-03-29 18:20:25 +00:00
* Register the rest - api endpoints required for this provider .
2022-10-27 11:23:01 +00:00
*
* @ codeCoverageIgnore
*/
2023-03-29 18:20:25 +00:00
public function register_rest_routes () {
register_rest_route (
Two_Factor_Core :: REST_NAMESPACE ,
'/totp' ,
array (
array (
'methods' => WP_REST_Server :: DELETABLE ,
'callback' => array ( $this , 'rest_delete_totp' ),
'permission_callback' => function ( $request ) {
return current_user_can ( 'edit_user' , $request [ 'user_id' ] );
},
'args' => array (
'user_id' => array (
'required' => true ,
2023-10-22 22:21:19 +00:00
'type' => 'integer' ,
2023-03-29 18:20:25 +00:00
),
),
),
array (
'methods' => WP_REST_Server :: CREATABLE ,
'callback' => array ( $this , 'rest_setup_totp' ),
'permission_callback' => function ( $request ) {
return current_user_can ( 'edit_user' , $request [ 'user_id' ] );
},
'args' => array (
'user_id' => array (
'required' => true ,
2023-10-22 22:21:19 +00:00
'type' => 'integer' ,
2023-03-29 18:20:25 +00:00
),
'key' => array (
'type' => 'string' ,
'default' => '' ,
'validate_callback' => null , // Note: validation handled in ::rest_setup_totp().
),
'code' => array (
'type' => 'string' ,
'default' => '' ,
'validate_callback' => null , // Note: validation handled in ::rest_setup_totp().
),
'enable_provider' => array (
'required' => false ,
'type' => 'boolean' ,
'default' => false ,
),
),
),
)
);
2022-10-27 11:23:01 +00:00
}
/**
* Returns the name of the provider .
*/
public function get_label () {
return _x ( 'Time Based One-Time Password (TOTP)' , 'Provider Label' , 'two-factor' );
}
/**
2023-03-29 18:20:25 +00:00
* Enqueue scripts
2022-10-27 11:23:01 +00:00
*
2023-03-29 18:20:25 +00:00
* @ codeCoverageIgnore
*/
public function enqueue_assets ( $hook_suffix ) {
$environment_prefix = file_exists ( TWO_FACTOR_DIR . '/dist' ) ? '/dist' : '' ;
wp_register_script (
'two-factor-qr-code-generator' ,
plugins_url ( $environment_prefix . '/includes/qrcode-generator/qrcode.js' , __DIR__ ),
array (),
TWO_FACTOR_VERSION ,
true
);
}
/**
* Rest API endpoint for handling deactivation of TOTP .
2022-10-27 11:23:01 +00:00
*
2023-03-29 18:20:25 +00:00
* @ param WP_Rest_Request $request The Rest Request object .
* @ return array Success array .
*/
public function rest_delete_totp ( $request ) {
$user_id = $request [ 'user_id' ];
$user = get_user_by ( 'id' , $user_id );
$this -> delete_user_totp_key ( $user_id );
ob_start ();
$this -> user_two_factor_options ( $user );
$html = ob_get_clean ();
return [
'success' => true ,
'html' => $html ,
];
}
/**
* REST API endpoint for setting up TOTP .
2022-10-27 11:23:01 +00:00
*
2023-03-29 18:20:25 +00:00
* @ param WP_Rest_Request $request The Rest Request object .
* @ return WP_Error | array Array of data on success , WP_Error on error .
2022-10-27 11:23:01 +00:00
*/
2023-03-29 18:20:25 +00:00
public function rest_setup_totp ( $request ) {
$user_id = $request [ 'user_id' ];
$user = get_user_by ( 'id' , $user_id );
$key = $request [ 'key' ];
$code = preg_replace ( '/\s+/' , '' , $request [ 'code' ] );
if ( ! $this -> is_valid_key ( $key ) ) {
return new WP_Error ( 'invalid_key' , __ ( 'Invalid Two Factor Authentication secret key.' , 'two-factor' ), array ( 'status' => 400 ) );
}
if ( ! $this -> is_valid_authcode ( $key , $code ) ) {
return new WP_Error ( 'invalid_key_code' , __ ( 'Invalid Two Factor Authentication code.' , 'two-factor' ), array ( 'status' => 400 ) );
}
if ( ! $this -> set_user_totp_key ( $user_id , $key ) ) {
return new WP_Error ( 'db_error' , __ ( 'Unable to save Two Factor Authentication code. Please re-scan the QR code and enter the code provided by your application.' , 'two-factor' ), array ( 'status' => 500 ) );
}
if ( $request -> get_param ( 'enable_provider' ) && ! Two_Factor_Core :: enable_provider_for_user ( $user_id , 'Two_Factor_Totp' ) ) {
return new WP_Error ( 'db_error' , __ ( 'Unable to enable TOTP provider for this user.' , 'two-factor' ), array ( 'status' => 500 ) );
2022-10-27 11:23:01 +00:00
}
2023-03-29 18:20:25 +00:00
ob_start ();
$this -> user_two_factor_options ( $user );
$html = ob_get_clean ();
return [
'success' => true ,
'html' => $html ,
];
2022-10-27 11:23:01 +00:00
}
/**
2023-03-29 18:20:25 +00:00
* Generates a URL that can be used to create a QR code .
2022-10-27 11:23:01 +00:00
*
2023-03-29 18:20:25 +00:00
* @ param WP_User $user The user to generate a URL for .
* @ param string $key The secret key .
2022-10-27 11:23:01 +00:00
*
* @ return string
*/
2023-03-29 18:20:25 +00:00
public static function generate_qr_code_url ( $user , $secret_key ) {
$issuer = get_bloginfo ( 'name' , 'display' );
/**
* Filter the Issuer for the TOTP .
*
* Must follow the TOTP format for a " issuer " . Do not URL Encode .
*
* @ see https :// github . com / google / google - authenticator / wiki / Key - Uri - Format #issuer
* @ param string $issuer The issuer for TOTP .
*/
$issuer = apply_filters ( 'two_factor_totp_issuer' , $issuer );
/**
* Filter the Label for the TOTP .
2023-10-22 22:21:19 +00:00
*
2023-03-29 18:20:25 +00:00
* Must follow the TOTP format for a " label " . Do not URL Encode .
*
* @ see https :// github . com / google / google - authenticator / wiki / Key - Uri - Format #label
* @ param string $totp_title The label for the TOTP .
* @ param WP_User $user The User object .
* @ param string $issuer The issuer of the TOTP . This should be the prefix of the result .
*/
$totp_title = apply_filters ( 'two_factor_totp_title' , $issuer . ':' . $user -> user_login , $user , $issuer );
$totp_url = add_query_arg (
array (
'secret' => rawurlencode ( $secret_key ),
'issuer' => rawurlencode ( $issuer ),
),
'otpauth://totp/' . rawurlencode ( $totp_title )
);
/**
* Filter the TOTP generated URL .
*
* Must follow the TOTP format . Do not URL Encode .
*
* @ see https :// github . com / google / google - authenticator / wiki / Key - Uri - Format
* @ param string $totp_url The TOTP URL .
* @ param WP_User $user The user object .
*/
$totp_url = apply_filters ( 'two_factor_totp_url' , $totp_url , $user );
$totp_url = esc_url ( $totp_url , array ( 'otpauth' ) );
return $totp_url ;
2022-10-27 11:23:01 +00:00
}
/**
* Display TOTP options on the user settings page .
*
* @ param WP_User $user The current user being edited .
* @ return false
*
* @ codeCoverageIgnore
*/
public function user_two_factor_options ( $user ) {
if ( ! isset ( $user -> ID ) ) {
return false ;
}
$key = $this -> get_user_totp_key ( $user -> ID );
2023-03-29 18:20:25 +00:00
wp_enqueue_script ( 'two-factor-qr-code-generator' );
2023-10-22 22:21:19 +00:00
wp_enqueue_script ( 'wp-api-request' );
wp_enqueue_script ( 'jquery' );
2022-10-27 11:23:01 +00:00
?>
< div id = " two-factor-totp-options " >
< ? php
if ( empty ( $key ) ) :
2023-03-29 18:20:25 +00:00
$key = $this -> generate_key ();
$totp_url = $this -> generate_qr_code_url ( $user , $key );
2022-10-27 11:23:01 +00:00
?>
2023-03-29 18:20:25 +00:00
2022-10-27 11:23:01 +00:00
< p >
< ? php esc_html_e ( 'Please scan the QR code or manually enter the key, then enter an authentication code from your app in order to complete setup.' , 'two-factor' ); ?>
</ p >
2023-03-29 18:20:25 +00:00
< p id = " two-factor-qr-code " >
< a href = " <?php echo $totp_url ; ?> " >
Loading ...
< img src = " <?php echo esc_url( admin_url( 'images/spinner.gif' ) ); ?> " alt = " " />
</ a >
2022-10-27 11:23:01 +00:00
</ p >
2023-03-29 18:20:25 +00:00
< style >
#two-factor-qr-code {
/* The size of the image will change based on the length of the URL inside it. */
min - width : 205 px ;
min - height : 205 px ;
}
</ style >
< script >
( function (){
var qr_generator = function () {
/*
* 0 = Automatically select the version , to avoid going over the limit of URL
* length .
* L = Least amount of error correction , because it ' s not needed when scanning
* on a monitor , and it lowers the image size .
*/
var qr = qrcode ( 0 , 'L' );
qr . addData ( < ? php echo wp_json_encode ( $totp_url ); ?> );
qr . make ();
document . querySelector ( '#two-factor-qr-code a' ) . innerHTML = qr . createSvgTag ( 5 );
};
// Run now if the document is loaded, otherwise on DOMContentLoaded.
if ( document . readyState === 'complete' ) {
qr_generator ();
} else {
window . addEventListener ( 'DOMContentLoaded' , qr_generator );
}
})();
</ script >
2022-10-27 11:23:01 +00:00
< p >
< code >< ? php echo esc_html ( $key ); ?> </code>
</ p >
< p >
2023-03-29 18:20:25 +00:00
< input type = " hidden " id = " two-factor-totp-key " name = " two-factor-totp-key " value = " <?php echo esc_attr( $key ); ?> " />
2022-10-27 11:23:01 +00:00
< label for = " two-factor-totp-authcode " >
< ? php esc_html_e ( 'Authentication Code:' , 'two-factor' ); ?>
2023-03-29 18:20:25 +00:00
< ? php
/* translators: Example auth code. */
$placeholder = sprintf ( __ ( 'eg. %s' , 'two-factor' ), '123456' );
?>
< input type = " tel " name = " two-factor-totp-authcode " id = " two-factor-totp-authcode " class = " input " value = " " size = " 20 " pattern = " [0-9 ]* " placeholder = " <?php echo esc_attr( $placeholder ); ?> " />
2022-10-27 11:23:01 +00:00
</ label >
2023-03-29 18:20:25 +00:00
< input type = " submit " class = " button totp-submit " name = " two-factor-totp-submit " value = " <?php esc_attr_e( 'Submit', 'two-factor' ); ?> " />
2022-10-27 11:23:01 +00:00
</ p >
2023-03-29 18:20:25 +00:00
< script >
( function ( $ ){
$ ( '.totp-submit' ) . click ( function ( e ) {
e . preventDefault ();
var key = $ ( '#two-factor-totp-key' ) . val (),
code = $ ( '#two-factor-totp-authcode' ) . val ();
wp . apiRequest ( {
method : 'POST' ,
path : < ? php echo wp_json_encode ( Two_Factor_Core :: REST_NAMESPACE . '/totp' ); ?> ,
data : {
user_id : < ? php echo wp_json_encode ( $user -> ID ); ?> ,
key : key ,
code : code ,
}
} ) . fail ( function ( response , status ) {
var errorMessage = response . responseJSON . message || status ,
$error = $ ( '#totp-setup-error' );
if ( ! $error . length ) {
$error = $ ( '<div class="error" id="totp-setup-error"><p></p></div>' ) . insertAfter ( $ ( '.totp-submit' ) );
}
$error . find ( 'p' ) . text ( errorMessage );
$ ( '#two-factor-totp-authcode' ) . val ( '' );
} ) . then ( function ( response ) {
$ ( '#two-factor-totp-options' ) . html ( response . html );
} );
} );
})( jQuery );
</ script >
2022-10-27 11:23:01 +00:00
< ? php else : ?>
< p class = " success " >
< ? php esc_html_e ( 'Secret key is configured and registered. It is not possible to view it again for security reasons.' , 'two-factor' ); ?>
</ p >
< p >
2023-03-29 18:20:25 +00:00
< a class = " button reset-totp-key " href = " # " >< ? php esc_html_e ( 'Reset Key' , 'two-factor' ); ?> </a>
2022-10-27 11:23:01 +00:00
< em class = " description " >
< ? php esc_html_e ( 'You will have to re-scan the QR code on all devices as the previous codes will stop working.' , 'two-factor' ); ?>
</ em >
2023-03-29 18:20:25 +00:00
< script >
( function ( $ ) {
$ ( 'a.reset-totp-key' ) . click ( function ( e ) {
e . preventDefault ();
wp . apiRequest ( {
method : 'DELETE' ,
path : < ? php echo wp_json_encode ( Two_Factor_Core :: REST_NAMESPACE . '/totp' ); ?> ,
data : {
user_id : < ? php echo wp_json_encode ( $user -> ID ); ?> ,
}
} ) . then ( function ( response ) {
$ ( '#two-factor-totp-options' ) . html ( response . html );
} );
} );
} )( jQuery );
</ script >
2022-10-27 11:23:01 +00:00
</ p >
< ? php endif ; ?>
</ div >
< ? php
}
/**
* Get the TOTP secret key for a user .
*
* @ param int $user_id User ID .
*
* @ return string
*/
public function get_user_totp_key ( $user_id ) {
return ( string ) get_user_meta ( $user_id , self :: SECRET_META_KEY , true );
}
/**
* Set the TOTP secret key for a user .
*
* @ param int $user_id User ID .
* @ param string $key TOTP secret key .
*
* @ return boolean If the key was stored successfully .
*/
public function set_user_totp_key ( $user_id , $key ) {
return update_user_meta ( $user_id , self :: SECRET_META_KEY , $key );
}
/**
* Delete the TOTP secret key for a user .
*
* @ param int $user_id User ID .
*
* @ return boolean If the key was deleted successfully .
*/
public function delete_user_totp_key ( $user_id ) {
2023-03-29 18:20:25 +00:00
delete_user_meta ( $user_id , self :: LAST_SUCCESSFUL_LOGIN_META_KEY );
2022-10-27 11:23:01 +00:00
return delete_user_meta ( $user_id , self :: SECRET_META_KEY );
}
/**
* Check if the TOTP secret key has a proper format .
*
* @ param string $key TOTP secret key .
*
* @ return boolean
*/
public function is_valid_key ( $key ) {
$check = sprintf ( '/^[%s]+$/' , self :: $base_32_chars );
if ( 1 === preg_match ( $check , $key ) ) {
return true ;
}
return false ;
}
/**
2023-03-29 18:20:25 +00:00
* Validates authentication .
2022-10-27 11:23:01 +00:00
*
2023-03-29 18:20:25 +00:00
* @ param WP_User $user WP_User object of the logged - in user .
2022-10-27 11:23:01 +00:00
*
2023-03-29 18:20:25 +00:00
* @ return bool Whether the user gave a valid code
2022-10-27 11:23:01 +00:00
*/
2023-03-29 18:20:25 +00:00
public function validate_authentication ( $user ) {
$code = $this -> sanitize_code_from_request ( 'authcode' , self :: DEFAULT_DIGIT_COUNT );
if ( ! $code ) {
return false ;
2022-10-27 11:23:01 +00:00
}
2023-03-29 18:20:25 +00:00
return $this -> validate_code_for_user ( $user , $code );
2022-10-27 11:23:01 +00:00
}
/**
2023-03-29 18:20:25 +00:00
* Validates an authentication code for a given user , preventing re - use and older TOTP keys .
2022-10-27 11:23:01 +00:00
*
* @ param WP_User $user WP_User object of the logged - in user .
2023-03-29 18:20:25 +00:00
* @ param int $code The TOTP token to validate .
2022-10-27 11:23:01 +00:00
*
2023-03-29 18:20:25 +00:00
* @ return bool Whether the code is valid for the user and a newer code has not been used .
2022-10-27 11:23:01 +00:00
*/
2023-03-29 18:20:25 +00:00
public function validate_code_for_user ( $user , $code ) {
$valid_timestamp = $this -> get_authcode_valid_ticktime (
$this -> get_user_totp_key ( $user -> ID ),
$code
);
if ( ! $valid_timestamp ) {
return false ;
2022-10-27 11:23:01 +00:00
}
2023-03-29 18:20:25 +00:00
$last_totp_login = ( int ) get_user_meta ( $user -> ID , self :: LAST_SUCCESSFUL_LOGIN_META_KEY , true );
// The TOTP authentication is not valid, if we've seen the same or newer code.
if ( $last_totp_login && $last_totp_login >= $valid_timestamp ) {
return false ;
}
update_user_meta ( $user -> ID , self :: LAST_SUCCESSFUL_LOGIN_META_KEY , $valid_timestamp );
return true ;
2022-10-27 11:23:01 +00:00
}
2023-03-29 18:20:25 +00:00
2022-10-27 11:23:01 +00:00
/**
2023-03-29 18:20:25 +00:00
* Checks if a given code is valid for a given key , allowing for a certain amount of time drift .
2022-10-27 11:23:01 +00:00
*
* @ param string $key The share secret key to use .
* @ param string $authcode The code to test .
*
2023-03-29 18:20:25 +00:00
* @ return bool Whether the code is valid within the time frame .
2022-10-27 11:23:01 +00:00
*/
public static function is_valid_authcode ( $key , $authcode ) {
2023-03-29 18:20:25 +00:00
return ( bool ) self :: get_authcode_valid_ticktime ( $key , $authcode );
}
/**
* Checks if a given code is valid for a given key , allowing for a certain amount of time drift .
*
* @ param string $key The share secret key to use .
* @ param string $authcode The code to test .
*
* @ return false | int Returns the timestamp of the authcode on success , False otherwise .
*/
public static function get_authcode_valid_ticktime ( $key , $authcode ) {
2022-10-27 11:23:01 +00:00
/**
* Filter the maximum ticks to allow when checking valid codes .
*
* Ticks are the allowed offset from the correct time in 30 second increments ,
* so the default of 4 allows codes that are two minutes to either side of server time
*
* @ deprecated 0.7 . 0 Use { @ see 'two_factor_totp_time_step_allowance' } instead .
* @ param int $max_ticks Max ticks of time correction to allow . Default 4.
*/
$max_ticks = apply_filters_deprecated ( 'two-factor-totp-time-step-allowance' , array ( self :: DEFAULT_TIME_STEP_ALLOWANCE ), '0.7.0' , 'two_factor_totp_time_step_allowance' );
$max_ticks = apply_filters ( 'two_factor_totp_time_step_allowance' , self :: DEFAULT_TIME_STEP_ALLOWANCE );
// Array of all ticks to allow, sorted using absolute value to test closest match first.
$ticks = range ( - $max_ticks , $max_ticks );
usort ( $ticks , array ( __CLASS__ , 'abssort' ) );
2023-03-29 18:20:25 +00:00
$time = floor ( time () / self :: DEFAULT_TIME_STEP_SEC );
2022-10-27 11:23:01 +00:00
foreach ( $ticks as $offset ) {
$log_time = $time + $offset ;
2023-03-29 18:20:25 +00:00
if ( hash_equals ( self :: calc_totp ( $key , $log_time ), $authcode ) ) {
// Return the tick timestamp.
return $log_time * self :: DEFAULT_TIME_STEP_SEC ;
2022-10-27 11:23:01 +00:00
}
}
2023-03-29 18:20:25 +00:00
2022-10-27 11:23:01 +00:00
return false ;
}
/**
* Generates key
*
* @ param int $bitsize Nume of bits to use for key .
*
* @ return string $bitsize long string composed of available base32 chars .
*/
public static function generate_key ( $bitsize = self :: DEFAULT_KEY_BIT_SIZE ) {
$bytes = ceil ( $bitsize / 8 );
$secret = wp_generate_password ( $bytes , true , true );
return self :: base32_encode ( $secret );
}
/**
* Pack stuff
*
* @ param string $value The value to be packed .
*
* @ return string Binary packed string .
*/
public static function pack64 ( $value ) {
// 64bit mode (PHP_INT_SIZE == 8).
if ( PHP_INT_SIZE >= 8 ) {
// If we're on PHP 5.6.3+ we can use the new 64bit pack functionality.
if ( version_compare ( PHP_VERSION , '5.6.3' , '>=' ) && PHP_INT_SIZE >= 8 ) {
return pack ( 'J' , $value ); // phpcs:ignore PHPCompatibility.ParameterValues.NewPackFormat.NewFormatFound
}
$highmap = 0xffffffff << 32 ;
$higher = ( $value & $highmap ) >> 32 ;
} else {
/*
* 32 bit PHP can ' t shift 32 bits like that , so we have to assume 0 for the higher
* and not pack anything beyond it ' s limits .
*/
$higher = 0 ;
}
$lowmap = 0xffffffff ;
$lower = $value & $lowmap ;
return pack ( 'NN' , $higher , $lower );
}
/**
* Calculate a valid code given the shared secret key
*
* @ param string $key The shared secret key to use for calculating code .
* @ param mixed $step_count The time step used to calculate the code , which is the floor of time () divided by step size .
* @ param int $digits The number of digits in the returned code .
* @ param string $hash The hash used to calculate the code .
* @ param int $time_step The size of the time step .
*
* @ return string The totp code
*/
public static function calc_totp ( $key , $step_count = false , $digits = self :: DEFAULT_DIGIT_COUNT , $hash = self :: DEFAULT_CRYPTO , $time_step = self :: DEFAULT_TIME_STEP_SEC ) {
$secret = self :: base32_decode ( $key );
if ( false === $step_count ) {
$step_count = floor ( time () / $time_step );
}
$timestamp = self :: pack64 ( $step_count );
$hash = hash_hmac ( $hash , $timestamp , $secret , true );
$offset = ord ( $hash [ 19 ] ) & 0xf ;
$code = (
( ( ord ( $hash [ $offset + 0 ] ) & 0x7f ) << 24 ) |
( ( ord ( $hash [ $offset + 1 ] ) & 0xff ) << 16 ) |
( ( ord ( $hash [ $offset + 2 ] ) & 0xff ) << 8 ) |
( ord ( $hash [ $offset + 3 ] ) & 0xff )
) % pow ( 10 , $digits );
return str_pad ( $code , $digits , '0' , STR_PAD_LEFT );
}
/**
* Whether this Two Factor provider is configured and available for the user specified .
*
* @ param WP_User $user WP_User object of the logged - in user .
*
* @ return boolean
*/
public function is_available_for_user ( $user ) {
// Only available if the secret key has been saved for the user.
$key = $this -> get_user_totp_key ( $user -> ID );
return ! empty ( $key );
}
/**
* Prints the form that prompts the user to authenticate .
*
* @ param WP_User $user WP_User object of the logged - in user .
*
* @ codeCoverageIgnore
*/
public function authentication_page ( $user ) {
require_once ABSPATH . '/wp-admin/includes/template.php' ;
?>
2023-03-29 18:20:25 +00:00
< p class = " two-factor-prompt " >
2022-10-27 11:23:01 +00:00
< ? php esc_html_e ( 'Please enter the code generated by your authenticator app.' , 'two-factor' ); ?>
</ p >
< p >
< label for = " authcode " >< ? php esc_html_e ( 'Authentication Code:' , 'two-factor' ); ?> </label>
2023-03-29 18:20:25 +00:00
< input type = " text " inputmode = " numeric " autocomplete = " one-time-code " name = " authcode " id = " authcode " class = " input authcode " value = " " size = " 20 " pattern = " [0-9 ]* " placeholder = " 123 456 " data - digits = " <?php echo esc_attr( self::DEFAULT_DIGIT_COUNT ); ?> " />
2022-10-27 11:23:01 +00:00
</ p >
< script type = " text/javascript " >
setTimeout ( function (){
var d ;
try {
d = document . getElementById ( 'authcode' );
d . focus ();
} catch ( e ){}
}, 200 );
</ script >
< ? php
submit_button ( __ ( 'Authenticate' , 'two-factor' ) );
}
/**
* Returns a base32 encoded string .
*
* @ param string $string String to be encoded using base32 .
*
* @ return string base32 encoded string without padding .
*/
public static function base32_encode ( $string ) {
if ( empty ( $string ) ) {
return '' ;
}
$binary_string = '' ;
foreach ( str_split ( $string ) as $character ) {
$binary_string .= str_pad ( base_convert ( ord ( $character ), 10 , 2 ), 8 , '0' , STR_PAD_LEFT );
}
$five_bit_sections = str_split ( $binary_string , 5 );
$base32_string = '' ;
foreach ( $five_bit_sections as $five_bit_section ) {
$base32_string .= self :: $base_32_chars [ base_convert ( str_pad ( $five_bit_section , 5 , '0' ), 2 , 10 ) ];
}
return $base32_string ;
}
/**
* Decode a base32 string and return a binary representation
*
* @ param string $base32_string The base 32 string to decode .
*
* @ throws Exception If string contains non - base32 characters .
*
* @ return string Binary representation of decoded string
*/
public static function base32_decode ( $base32_string ) {
$base32_string = strtoupper ( $base32_string );
if ( ! preg_match ( '/^[' . self :: $base_32_chars . ']+$/' , $base32_string , $match ) ) {
throw new Exception ( 'Invalid characters in the base32 string.' );
}
$l = strlen ( $base32_string );
$n = 0 ;
$j = 0 ;
$binary = '' ;
for ( $i = 0 ; $i < $l ; $i ++ ) {
$n = $n << 5 ; // Move buffer left by 5 to make room.
$n = $n + strpos ( self :: $base_32_chars , $base32_string [ $i ] ); // Add value into buffer.
$j += 5 ; // Keep track of number of bits in buffer.
if ( $j >= 8 ) {
$j -= 8 ;
$binary .= chr ( ( $n & ( 0xFF << $j ) ) >> $j );
}
}
return $binary ;
}
/**
* Used with usort to sort an array by distance from 0
*
* @ param int $a First array element .
* @ param int $b Second array element .
*
* @ return int - 1 , 0 , or 1 as needed by usort
*/
private static function abssort ( $a , $b ) {
$a = abs ( $a );
$b = abs ( $b );
if ( $a === $b ) {
return 0 ;
}
return ( $a < $b ) ? - 1 : 1 ;
}
}