diff --git a/wp-content/plugins/two-factor/class-two-factor-compat.php b/wp-content/plugins/two-factor/class-two-factor-compat.php index 47e6ebfba3670f0e9655e7d248474c126673b592..b578c84762c3ac721a9506e5d675afaba448c0df 100644 --- a/wp-content/plugins/two-factor/class-two-factor-compat.php +++ b/wp-content/plugins/two-factor/class-two-factor-compat.php @@ -1,4 +1,9 @@ <?php +/** + * A compatibility layer for some of the most popular plugins. + * + * @package Two_Factor + */ /** * A compatibility layer for some of the most popular plugins. @@ -30,7 +35,9 @@ class Two_Factor_Compat { * @return boolean */ public function jetpack_rememberme( $rememberme ) { - if ( isset( $_GET['action'] ) && 'jetpack-sso' === $_GET['action'] && $this->jetpack_is_sso_active() ) { + $action = filter_input( INPUT_GET, 'action', FILTER_SANITIZE_STRING ); + + if ( 'jetpack-sso' === $action && $this->jetpack_is_sso_active() ) { return true; } diff --git a/wp-content/plugins/two-factor/class-two-factor-core.php b/wp-content/plugins/two-factor/class-two-factor-core.php index 923cdfc830ce0568b3e6cf444b9d617cfa2f2801..e319cf6212cc9ff20f934d7dabe48c08614f1f47 100644 --- a/wp-content/plugins/two-factor/class-two-factor-core.php +++ b/wp-content/plugins/two-factor/class-two-factor-core.php @@ -1,4 +1,10 @@ <?php +/** + * Two Factore Core Class. + * + * @package Two_Factor + */ + /** * Class for creating two factor authorization. * @@ -54,6 +60,8 @@ class Two_Factor_Core { /** * Set up filters and actions. * + * @param object $compat A compaitbility later for plugins. + * * @since 0.1-dev */ public static function add_hooks( $compat ) { @@ -106,11 +114,11 @@ class Two_Factor_Core { */ public static function get_providers() { $providers = array( - 'Two_Factor_Email' => TWO_FACTOR_DIR . 'providers/class.two-factor-email.php', - 'Two_Factor_Totp' => TWO_FACTOR_DIR . 'providers/class.two-factor-totp.php', - 'Two_Factor_FIDO_U2F' => TWO_FACTOR_DIR . 'providers/class.two-factor-fido-u2f.php', - 'Two_Factor_Backup_Codes' => TWO_FACTOR_DIR . 'providers/class.two-factor-backup-codes.php', - 'Two_Factor_Dummy' => TWO_FACTOR_DIR . 'providers/class.two-factor-dummy.php', + 'Two_Factor_Email' => TWO_FACTOR_DIR . 'providers/class-two-factor-email.php', + 'Two_Factor_Totp' => TWO_FACTOR_DIR . 'providers/class-two-factor-totp.php', + 'Two_Factor_FIDO_U2F' => TWO_FACTOR_DIR . 'providers/class-two-factor-fido-u2f.php', + 'Two_Factor_Backup_Codes' => TWO_FACTOR_DIR . 'providers/class-two-factor-backup-codes.php', + 'Two_Factor_Dummy' => TWO_FACTOR_DIR . 'providers/class-two-factor-dummy.php', ); /** @@ -127,18 +135,20 @@ class Two_Factor_Core { // FIDO U2F is PHP 5.3+ only. if ( isset( $providers['Two_Factor_FIDO_U2F'] ) && version_compare( PHP_VERSION, '5.3.0', '<' ) ) { unset( $providers['Two_Factor_FIDO_U2F'] ); - trigger_error( sprintf( // WPCS: XSS OK. + trigger_error( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error + sprintf( /* translators: %s: version number */ - __( 'FIDO U2F is not available because you are using PHP %s. (Requires 5.3 or greater)', 'two-factor' ), - PHP_VERSION - ) ); + __( 'FIDO U2F is not available because you are using PHP %s. (Requires 5.3 or greater)', 'two-factor' ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + PHP_VERSION + ) + ); } /** * For each filtered provider, */ foreach ( $providers as $class => $path ) { - include_once( $path ); + include_once $path; /** * Confirm that it's been successfully included before instantiating. @@ -267,7 +277,7 @@ class Two_Factor_Core { * @return void */ public static function trigger_user_settings_action() { - $action = filter_input( INPUT_GET, self::USER_SETTINGS_ACTION_QUERY_VAR, FILTER_SANITIZE_STRING ); + $action = filter_input( INPUT_GET, self::USER_SETTINGS_ACTION_QUERY_VAR, FILTER_SANITIZE_STRING ); $user_id = self::current_user_being_edited(); if ( ! empty( $action ) && self::is_valid_user_action( $user_id, $action ) ) { @@ -341,7 +351,7 @@ class Two_Factor_Core { $configured_providers = array(); foreach ( $providers as $classname => $provider ) { - if ( in_array( $classname, $enabled_providers ) && $provider->is_available_for_user( $user ) ) { + if ( in_array( $classname, $enabled_providers, true ) && $provider->is_available_for_user( $user ) ) { $configured_providers[ $classname ] = $provider; } } @@ -526,29 +536,33 @@ class Two_Factor_Core { * @since 0.1-dev */ public static function backup_2fa() { - if ( ! isset( $_GET['wp-auth-id'], $_GET['wp-auth-nonce'], $_GET['provider'] ) ) { + $wp_auth_id = filter_input( INPUT_GET, 'wp-auth-id', FILTER_SANITIZE_NUMBER_INT ); + $nonce = filter_input( INPUT_GET, 'wp-auth-nonce', FILTER_SANITIZE_STRING ); + $provider = filter_input( INPUT_GET, 'provider', FILTER_SANITIZE_STRING ); + + if ( ! $wp_auth_id || ! $nonce || ! $provider ) { return; } - $user = get_userdata( $_GET['wp-auth-id'] ); + $user = get_userdata( $wp_auth_id ); if ( ! $user ) { return; } - $nonce = $_GET['wp-auth-nonce']; if ( true !== self::verify_login_nonce( $user->ID, $nonce ) ) { wp_safe_redirect( get_bloginfo( 'url' ) ); exit; } $providers = self::get_available_providers_for_user( $user ); - if ( isset( $providers[ $_GET['provider'] ] ) ) { - $provider = $providers[ $_GET['provider'] ]; + if ( isset( $providers[ $provider ] ) ) { + $provider = $providers[ $provider ]; } else { wp_die( esc_html__( 'Cheatin’ uh?', 'two-factor' ), 403 ); } - self::login_html( $user, $_GET['wp-auth-nonce'], $_GET['redirect_to'], '', $provider ); + $redirect_to = filter_input( INPUT_GET, 'redirect_to', FILTER_SANITIZE_URL ); + self::login_html( $user, $nonce, $redirect_to, '', $provider ); exit; } @@ -574,14 +588,14 @@ class Two_Factor_Core { $provider_class = get_class( $provider ); $available_providers = self::get_available_providers_for_user( $user ); - $backup_providers = array_diff_key( $available_providers, array( $provider_class => null ) ); - $interim_login = isset( $_REQUEST['interim-login'] ); // WPCS: CSRF ok. + $backup_providers = array_diff_key( $available_providers, array( $provider_class => null ) ); + $interim_login = isset( $_REQUEST['interim-login'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended $rememberme = intval( self::rememberme() ); if ( ! function_exists( 'login_header' ) ) { // We really should migrate login_header() out of `wp-login.php` so it can be called from an includes file. - include_once( TWO_FACTOR_DIR . 'includes/function.login-header.php' ); + include_once TWO_FACTOR_DIR . 'includes/function.login-header.php'; } login_header(); @@ -595,11 +609,11 @@ class Two_Factor_Core { <input type="hidden" name="provider" id="provider" value="<?php echo esc_attr( $provider_class ); ?>" /> <input type="hidden" name="wp-auth-id" id="wp-auth-id" value="<?php echo esc_attr( $user->ID ); ?>" /> <input type="hidden" name="wp-auth-nonce" id="wp-auth-nonce" value="<?php echo esc_attr( $login_nonce ); ?>" /> - <?php if ( $interim_login ) { ?> + <?php if ( $interim_login ) { ?> <input type="hidden" name="interim-login" value="1" /> - <?php } else { ?> + <?php } else { ?> <input type="hidden" name="redirect_to" value="<?php echo esc_attr( $redirect_to ); ?>" /> - <?php } ?> + <?php } ?> <input type="hidden" name="rememberme" id="rememberme" value="<?php echo esc_attr( $rememberme ); ?>" /> <?php $provider->authentication_page( $user ); ?> @@ -608,8 +622,8 @@ class Two_Factor_Core { <?php if ( 1 === count( $backup_providers ) ) : $backup_classname = key( $backup_providers ); - $backup_provider = $backup_providers[ $backup_classname ]; - $login_url = self::login_url( + $backup_provider = $backup_providers[ $backup_classname ]; + $login_url = self::login_url( array( 'action' => 'backup_2fa', 'provider' => $backup_classname, @@ -703,7 +717,8 @@ class Two_Factor_Core { <?php /** This action is documented in wp-login.php */ - do_action( 'login_footer' ); ?> + do_action( 'login_footer' ); + ?> <div class="clear"></div> </body> </html> @@ -737,11 +752,11 @@ class Two_Factor_Core { * @return array */ public static function create_login_nonce( $user_id ) { - $login_nonce = array(); + $login_nonce = array(); try { $login_nonce['key'] = bin2hex( random_bytes( 32 ) ); - } catch (Exception $ex) { - $login_nonce['key'] = wp_hash( $user_id . mt_rand() . microtime(), 'nonce' ); + } catch ( Exception $ex ) { + $login_nonce['key'] = wp_hash( $user_id . wp_rand() . microtime(), 'nonce' ); } $login_nonce['expiration'] = time() + HOUR_IN_SECONDS; @@ -793,25 +808,28 @@ class Two_Factor_Core { * @since 0.1-dev */ public static function login_form_validate_2fa() { - if ( ! isset( $_POST['wp-auth-id'], $_POST['wp-auth-nonce'] ) ) { + $wp_auth_id = filter_input( INPUT_POST, 'wp-auth-id', FILTER_SANITIZE_NUMBER_INT ); + $nonce = filter_input( INPUT_POST, 'wp-auth-nonce', FILTER_SANITIZE_STRING ); + + if ( ! $wp_auth_id || ! $nonce ) { return; } - $user = get_userdata( $_POST['wp-auth-id'] ); + $user = get_userdata( $wp_auth_id ); if ( ! $user ) { return; } - $nonce = $_POST['wp-auth-nonce']; if ( true !== self::verify_login_nonce( $user->ID, $nonce ) ) { wp_safe_redirect( get_bloginfo( 'url' ) ); exit; } - if ( isset( $_POST['provider'] ) ) { + $provider = filter_input( INPUT_POST, 'provider', FILTER_SANITIZE_STRING ); + if ( $provider ) { $providers = self::get_available_providers_for_user( $user ); - if ( isset( $providers[ $_POST['provider'] ] ) ) { - $provider = $providers[ $_POST['provider'] ]; + if ( isset( $providers[ $provider ] ) ) { + $provider = $providers[ $provider ]; } else { wp_die( esc_html__( 'Cheatin’ uh?', 'two-factor' ), 403 ); } @@ -856,7 +874,7 @@ class Two_Factor_Core { // Must be global because that's how login_header() uses it. global $interim_login; - $interim_login = isset( $_REQUEST['interim-login'] ); // WPCS: override ok. + $interim_login = isset( $_REQUEST['interim-login'] ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited,WordPress.Security.NonceVerification.Recommended if ( $interim_login ) { $customize_login = isset( $_REQUEST['customize-login'] ); @@ -864,14 +882,16 @@ class Two_Factor_Core { wp_enqueue_script( 'customize-base' ); } $message = '<p class="message">' . __( 'You have logged in successfully.', 'two-factor' ) . '</p>'; - $interim_login = 'success'; // WPCS: override ok. - login_header( '', $message ); ?> + $interim_login = 'success'; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited + login_header( '', $message ); + ?> </div> <?php /** This action is documented in wp-login.php */ - do_action( 'login_footer' ); ?> + do_action( 'login_footer' ); + ?> <?php if ( $customize_login ) : ?> - <script type="text/javascript">setTimeout( function(){ new wp.customize.Messenger({ url: '<?php echo wp_customize_url(); /* WPCS: XSS OK. */ ?>', channel: 'login' }).send('login') }, 1000 );</script> + <script type="text/javascript">setTimeout( function(){ new wp.customize.Messenger({ url: '<?php echo esc_url( wp_customize_url() ); ?>', channel: 'login' }).send('login') }, 1000 );</script> <?php endif; ?> </body></html> <?php @@ -927,10 +947,10 @@ class Two_Factor_Core { * @param WP_User $user WP_User object of the logged-in user. */ public static function user_two_factor_options( $user ) { - wp_enqueue_style( 'user-edit-2fa', plugins_url( 'user-edit.css', __FILE__ ) ); + wp_enqueue_style( 'user-edit-2fa', plugins_url( 'user-edit.css', __FILE__ ), array(), TWO_FACTOR_VERSION ); $enabled_providers = array_keys( self::get_available_providers_for_user( $user ) ); - $primary_provider = self::get_primary_provider_for_user( $user->ID ); + $primary_provider = self::get_primary_provider_for_user( $user->ID ); if ( ! empty( $primary_provider ) && is_object( $primary_provider ) ) { $primary_provider_key = get_class( $primary_provider ); @@ -959,11 +979,24 @@ class Two_Factor_Core { <tbody> <?php foreach ( self::get_providers() as $class => $object ) : ?> <tr> - <th scope="row"><input type="checkbox" name="<?php echo esc_attr( self::ENABLED_PROVIDERS_USER_META_KEY ); ?>[]" value="<?php echo esc_attr( $class ); ?>" <?php checked( in_array( $class, $enabled_providers ) ); ?> /></th> + <th scope="row"><input type="checkbox" name="<?php echo esc_attr( self::ENABLED_PROVIDERS_USER_META_KEY ); ?>[]" value="<?php echo esc_attr( $class ); ?>" <?php checked( in_array( $class, $enabled_providers, true ) ); ?> /></th> <th scope="row"><input type="radio" name="<?php echo esc_attr( self::PROVIDER_USER_META_KEY ); ?>" value="<?php echo esc_attr( $class ); ?>" <?php checked( $class, $primary_provider_key ); ?> /></th> <td> - <?php $object->print_label(); ?> - <?php do_action( 'two-factor-user-options-' . $class, $user ); ?> + <?php + $object->print_label(); + + /** + * Fires after user options are shown. + * + * Use the {@see 'two_factor_user_options_' . $class } hook instead. + * + * @deprecated 0.7.0 + * + * @param WP_User $user The user. + */ + do_action_deprecated( 'two-factor-user-options-' . $class, array( $user ), '0.7.0', 'two_factor_user_options_' . $class ); + do_action( 'two_factor_user_options_' . $class, $user ); + ?> </td> </tr> <?php endforeach; ?> diff --git a/wp-content/plugins/two-factor/docker-compose.yml b/wp-content/plugins/two-factor/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..9a92e6cecc3d5016dfc079eaffda14fee13dab52 --- /dev/null +++ b/wp-content/plugins/two-factor/docker-compose.yml @@ -0,0 +1,11 @@ +version: '3.6' + +services: + + wpdevlib: + build: ./tests/docker/wp-dev-lib + working_dir: /var/www/html + volumes: + - .:/var/www/html + environment: + CHECK_SCOPE: all diff --git a/wp-content/plugins/two-factor/providers/class-two-factor-backup-codes.php b/wp-content/plugins/two-factor/providers/class-two-factor-backup-codes.php new file mode 100644 index 0000000000000000000000000000000000000000..f22675383910ddb88ed4a65a3f3c2058d7c68a4e --- /dev/null +++ b/wp-content/plugins/two-factor/providers/class-two-factor-backup-codes.php @@ -0,0 +1,355 @@ +<?php +/** + * Class for creating a backup codes provider. + * + * @package Two_Factor + */ + +/** + * Class for creating a backup codes provider. + * + * @since 0.1-dev + * + * @package Two_Factor + */ +class Two_Factor_Backup_Codes extends Two_Factor_Provider { + + /** + * The user meta backup codes key. + * + * @type string + */ + const BACKUP_CODES_META_KEY = '_two_factor_backup_codes'; + + /** + * The number backup codes. + * + * @type int + */ + const NUMBER_OF_CODES = 10; + + /** + * Ensures only one instance of this class exists in memory at any one time. + * + * @since 0.1-dev + */ + public static function get_instance() { + static $instance; + $class = __CLASS__; + if ( ! is_a( $instance, $class ) ) { + $instance = new $class(); + } + return $instance; + } + + /** + * Class constructor. + * + * @since 0.1-dev + */ + protected function __construct() { + add_action( 'two_factor_user_options_' . __CLASS__, array( $this, 'user_options' ) ); + add_action( 'admin_notices', array( $this, 'admin_notices' ) ); + add_action( 'wp_ajax_two_factor_backup_codes_generate', array( $this, 'ajax_generate_json' ) ); + + return parent::__construct(); + } + + /** + * Displays an admin notice when backup codes have run out. + * + * @since 0.1-dev + */ + public function admin_notices() { + $user = wp_get_current_user(); + + // Return if the provider is not enabled. + if ( ! in_array( __CLASS__, Two_Factor_Core::get_enabled_providers_for_user( $user->ID ), true ) ) { + return; + } + + // Return if we are not out of codes. + if ( $this->is_available_for_user( $user ) ) { + return; + } + ?> + <div class="error"> + <p> + <span> + <?php + wp_kses( + sprintf( + /* translators: %s: URL for code regeneration */ + __( 'Two-Factor: You are out of backup codes and need to <a href="%s">regenerate!</a>', 'two-factor' ), + esc_url( get_edit_user_link( $user->ID ) . '#two-factor-backup-codes' ) + ), + array( 'a' => array( 'href' => true ) ) + ); + ?> + <span> + </p> + </div> + <?php + } + + /** + * Returns the name of the provider. + * + * @since 0.1-dev + */ + public function get_label() { + return _x( 'Backup Verification Codes (Single Use)', 'Provider Label', 'two-factor' ); + } + + /** + * Whether this Two Factor provider is configured and codes are 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 ) { + // Does this user have available codes? + if ( 0 < self::codes_remaining_for_user( $user ) ) { + return true; + } + return false; + } + + /** + * 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 ) { + $ajax_nonce = wp_create_nonce( 'two-factor-backup-codes-generate-json-' . $user->ID ); + $count = self::codes_remaining_for_user( $user ); + ?> + <p id="two-factor-backup-codes"> + <button type="button" class="button button-two-factor-backup-codes-generate button-secondary hide-if-no-js"> + <?php esc_html_e( 'Generate Verification Codes', 'two-factor' ); ?> + </button> + <span class="two-factor-backup-codes-count"> + <?php + echo esc_html( + sprintf( + /* translators: %s: count */ + _n( '%s unused code remaining.', '%s unused codes remaining.', $count, 'two-factor' ), + $count + ) + ); + ?> + </span> + </p> + <div class="two-factor-backup-codes-wrapper" style="display:none;"> + <ol class="two-factor-backup-codes-unused-codes"></ol> + <p class="description"><?php esc_html_e( 'Write these down! Once you navigate away from this page, you will not be able to view these codes again.', 'two-factor' ); ?></p> + <p> + <a class="button button-two-factor-backup-codes-download button-secondary hide-if-no-js" href="javascript:void(0);" id="two-factor-backup-codes-download-link" download="two-factor-backup-codes.txt"><?php esc_html_e( 'Download Codes', 'two-factor' ); ?></a> + <p> + </div> + <script type="text/javascript"> + ( function( $ ) { + $( '.button-two-factor-backup-codes-generate' ).click( function() { + $.ajax( { + method: 'POST', + url: ajaxurl, + data: { + action: 'two_factor_backup_codes_generate', + user_id: '<?php echo esc_js( $user->ID ); ?>', + nonce: '<?php echo esc_js( $ajax_nonce ); ?>' + }, + dataType: 'JSON', + success: function( response ) { + var $codesList = $( '.two-factor-backup-codes-unused-codes' ); + + $( '.two-factor-backup-codes-wrapper' ).show(); + $codesList.html( '' ); + + // Append the codes. + for ( i = 0; i < response.data.codes.length; i++ ) { + $codesList.append( '<li>' + response.data.codes[ i ] + '</li>' ); + } + + // Update counter. + $( '.two-factor-backup-codes-count' ).html( response.data.i18n.count ); + + // Build the download link. + var txt_data = 'data:application/text;charset=utf-8,' + '\n'; + txt_data += response.data.i18n.title.replace( /%s/g, document.domain ) + '\n\n'; + + for ( i = 0; i < response.data.codes.length; i++ ) { + txt_data += i + 1 + '. ' + response.data.codes[ i ] + '\n'; + } + + $( '#two-factor-backup-codes-download-link' ).attr( 'href', encodeURI( txt_data ) ); + } + } ); + } ); + } )( jQuery ); + </script> + <?php + } + + /** + * Generates backup codes & updates the user meta. + * + * @since 0.1-dev + * + * @param WP_User $user WP_User object of the logged-in user. + * @param array $args Optional arguments for assigning new codes. + * @return array + */ + public function generate_codes( $user, $args = '' ) { + $codes = array(); + $codes_hashed = array(); + + // Check for arguments. + if ( isset( $args['number'] ) ) { + $num_codes = (int) $args['number']; + } else { + $num_codes = self::NUMBER_OF_CODES; + } + + // Append or replace (default). + if ( isset( $args['method'] ) && 'append' === $args['method'] ) { + $codes_hashed = (array) get_user_meta( $user->ID, self::BACKUP_CODES_META_KEY, true ); + } + + for ( $i = 0; $i < $num_codes; $i++ ) { + $code = $this->get_code(); + $codes_hashed[] = wp_hash_password( $code ); + $codes[] = $code; + unset( $code ); + } + + update_user_meta( $user->ID, self::BACKUP_CODES_META_KEY, $codes_hashed ); + + // Unhashed. + return $codes; + } + + /** + * Generates a JSON object of backup codes. + * + * @since 0.1-dev + */ + public function ajax_generate_json() { + $user = get_user_by( 'id', filter_input( INPUT_POST, 'user_id', FILTER_SANITIZE_NUMBER_INT ) ); + check_ajax_referer( 'two-factor-backup-codes-generate-json-' . $user->ID, 'nonce' ); + + // Setup the return data. + $codes = $this->generate_codes( $user ); + $count = self::codes_remaining_for_user( $user ); + $i18n = array( + /* translators: %s: count */ + 'count' => esc_html( sprintf( _n( '%s unused code remaining.', '%s unused codes remaining.', $count, 'two-factor' ), $count ) ), + /* translators: %s: the site's domain */ + 'title' => esc_html__( 'Two-Factor Backup Codes for %s', 'two-factor' ), + ); + + // Send the response. + wp_send_json_success( + array( + 'codes' => $codes, + 'i18n' => $i18n, + ) + ); + } + + /** + * Returns the number of unused codes for the specified user + * + * @param WP_User $user WP_User object of the logged-in user. + * @return int $int The number of unused codes remaining + */ + public static function codes_remaining_for_user( $user ) { + $backup_codes = get_user_meta( $user->ID, self::BACKUP_CODES_META_KEY, true ); + if ( is_array( $backup_codes ) && ! empty( $backup_codes ) ) { + return count( $backup_codes ); + } + return 0; + } + + /** + * 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 ) { + require_once ABSPATH . '/wp-admin/includes/template.php'; + ?> + <p><?php esc_html_e( 'Enter a backup verification code.', 'two-factor' ); ?></p><br/> + <p> + <label for="authcode"><?php esc_html_e( 'Verification Code:', 'two-factor' ); ?></label> + <input type="tel" name="two-factor-backup-code" id="authcode" class="input" value="" size="20" pattern="[0-9]*" /> + </p> + <?php + submit_button( __( 'Submit', 'two-factor' ) ); + } + + /** + * Validates the users input token. + * + * In this class we just return true. + * + * @since 0.1-dev + * + * @param WP_User $user WP_User object of the logged-in user. + * @return boolean + */ + public function validate_authentication( $user ) { + $backup_code = isset( $_POST['two-factor-backup-code'] ) ? sanitize_text_field( wp_unslash( $_POST['two-factor-backup-code'] ) ) : false; + return $this->validate_code( $user, filter_var( $backup_code, FILTER_SANITIZE_STRING ) ); + } + + /** + * Validates a backup code. + * + * Backup Codes are single use and are deleted upon a successful validation. + * + * @since 0.1-dev + * + * @param WP_User $user WP_User object of the logged-in user. + * @param int $code The backup code. + * @return boolean + */ + public function validate_code( $user, $code ) { + $backup_codes = get_user_meta( $user->ID, self::BACKUP_CODES_META_KEY, true ); + + if ( is_array( $backup_codes ) && ! empty( $backup_codes ) ) { + foreach ( $backup_codes as $code_index => $code_hashed ) { + if ( wp_check_password( $code, $code_hashed, $user->ID ) ) { + $this->delete_code( $user, $code_hashed ); + return true; + } + } + } + return false; + } + + /** + * Deletes a backup code. + * + * @since 0.1-dev + * + * @param WP_User $user WP_User object of the logged-in user. + * @param string $code_hashed The hashed the backup code. + */ + public function delete_code( $user, $code_hashed ) { + $backup_codes = get_user_meta( $user->ID, self::BACKUP_CODES_META_KEY, true ); + + // Delete the current code from the list since it's been used. + $backup_codes = array_flip( $backup_codes ); + unset( $backup_codes[ $code_hashed ] ); + $backup_codes = array_values( array_flip( $backup_codes ) ); + + // Update the backup code master list. + update_user_meta( $user->ID, self::BACKUP_CODES_META_KEY, $backup_codes ); + } +} diff --git a/wp-content/plugins/two-factor/providers/class-two-factor-dummy.php b/wp-content/plugins/two-factor/providers/class-two-factor-dummy.php new file mode 100644 index 0000000000000000000000000000000000000000..f1625dcea09e0d0e746243a9bb047157b52c89bb --- /dev/null +++ b/wp-content/plugins/two-factor/providers/class-two-factor-dummy.php @@ -0,0 +1,99 @@ +<?php +/** + * Class for creating a dummy provider. + * + * @package Two_Factor + */ + +/** + * Class for creating a dummy provider. + * + * @since 0.1-dev + * + * @package Two_Factor + */ +class Two_Factor_Dummy extends Two_Factor_Provider { + + /** + * Ensures only one instance of this class exists in memory at any one time. + * + * @since 0.1-dev + */ + public static function get_instance() { + static $instance; + $class = __CLASS__; + if ( ! is_a( $instance, $class ) ) { + $instance = new $class(); + } + return $instance; + } + + /** + * Class constructor. + * + * @since 0.1-dev + */ + protected function __construct() { + add_action( 'two_factor_user_options_' . __CLASS__, array( $this, 'user_options' ) ); + return parent::__construct(); + } + + /** + * Returns the name of the provider. + * + * @since 0.1-dev + */ + public function get_label() { + return _x( 'Dummy Method', 'Provider Label', 'two-factor' ); + } + + /** + * 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 ) { + require_once ABSPATH . '/wp-admin/includes/template.php'; + ?> + <p><?php esc_html_e( 'Are you really you?', 'two-factor' ); ?></p> + <?php + submit_button( __( 'Yup.', 'two-factor' ) ); + } + + /** + * Validates the users input token. + * + * In this class we just return true. + * + * @since 0.1-dev + * + * @param WP_User $user WP_User object of the logged-in user. + * @return boolean + */ + public function validate_authentication( $user ) { + return true; + } + + /** + * 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 ) {} +} diff --git a/wp-content/plugins/two-factor/providers/class-two-factor-email.php b/wp-content/plugins/two-factor/providers/class-two-factor-email.php new file mode 100644 index 0000000000000000000000000000000000000000..94fd8448272b007db2b66ea856304aaad7ec620b --- /dev/null +++ b/wp-content/plugins/two-factor/providers/class-two-factor-email.php @@ -0,0 +1,361 @@ +<?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'; + + /** + * Ensures only one instance of this class exists in memory at any one time. + * + * @since 0.1-dev + */ + public static function get_instance() { + static $instance; + $class = __CLASS__; + if ( ! is_a( $instance, $class ) ) { + $instance = new $class(); + } + return $instance; + } + + /** + * Class constructor. + * + * @since 0.1-dev + */ + protected function __construct() { + add_action( 'two_factor_user_options_' . __CLASS__, array( $this, 'user_options' ) ); + return parent::__construct(); + } + + /** + * Returns the name of the provider. + * + * @since 0.1-dev + */ + public function get_label() { + return _x( 'Email', 'Provider Label', 'two-factor' ); + } + + /** + * 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(); + + 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. + * + * @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. + * + * @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. + * + * @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. + * + * @param integer $user_id User ID. + * + * @return integer + */ + public function user_token_ttl( $user_id ) { + $token_ttl = 15 * MINUTE_IN_SECONDS; + + /** + * Number of seconds the token is considered valid + * after the generation. + * + * @param integer $token_ttl Token time-to-live in seconds. + * @param integer $user_id User ID. + */ + return (int) apply_filters( 'two_factor_token_ttl', $token_ttl, $user_id ); + } + + /** + * Get the authentication token for the user. + * + * @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 ) || ( 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 ); + } + + /** + * 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 ); + + /* translators: %s: site name */ + $subject = wp_strip_all_tags( sprintf( __( 'Your login confirmation code for %s', 'two-factor' ), get_bloginfo( 'name' ) ) ); + /* translators: %s: token */ + $message = wp_strip_all_tags( sprintf( __( 'Enter %s to log in.', 'two-factor' ), $token ) ); + + /** + * Filter the token email subject. + * + * @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 ); + + /** + * Filter the token email message. + * + * @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 ); + } + + /** + * 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 ); + } + + require_once ABSPATH . '/wp-admin/includes/template.php'; + ?> + <p><?php esc_html_e( 'A verification code has been sent to the email address associated with your account.', 'two-factor' ); ?></p> + <p> + <label for="authcode"><?php esc_html_e( 'Verification Code:', 'two-factor' ); ?></label> + <input type="tel" name="two-factor-email-code" id="authcode" class="input" value="" size="20" /> + <?php submit_button( __( 'Log In', 'two-factor' ) ); ?> + </p> + <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> + <script type="text/javascript"> + setTimeout( function(){ + var d; + try{ + d = document.getElementById('authcode'); + d.value = ''; + d.focus(); + } catch(e){} + }, 200); + </script> + <?php + } + + /** + * Send the email code if missing or requested. Stop the authentication + * validation if a new token has been generated and sent. + * + * @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 ] ) ) { + $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 ) { + if ( ! isset( $user->ID ) || ! isset( $_REQUEST['two-factor-email-code'] ) ) { + return false; + } + + // Ensure there are no spaces or line breaks around the code. + $code = trim( sanitize_text_field( $_REQUEST['two-factor-email-code'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, handled by the core method already. + + 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; + ?> + <div> + <?php + echo esc_html( + sprintf( + /* translators: %s: email address */ + __( 'Authentication codes will be sent to %s.', 'two-factor' ), + $email + ) + ); + ?> + </div> + <?php + } +} diff --git a/wp-content/plugins/two-factor/providers/class-two-factor-fido-u2f-admin-list-table.php b/wp-content/plugins/two-factor/providers/class-two-factor-fido-u2f-admin-list-table.php new file mode 100644 index 0000000000000000000000000000000000000000..deb220cf9729023bd5231fdeaf959dd4d0cb1105 --- /dev/null +++ b/wp-content/plugins/two-factor/providers/class-two-factor-fido-u2f-admin-list-table.php @@ -0,0 +1,160 @@ +<?php +/** + * Class for displaying the list of security key items. + * + * @package Two_Factor + */ + +// Load the parent class if it doesn't exist. +if ( ! class_exists( 'WP_List_Table' ) ) { + require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php'; +} + +/** + * Class for displaying the list of security key items. + * + * @since 0.1-dev + * @access private + * + * @package Two_Factor + */ +class Two_Factor_FIDO_U2F_Admin_List_Table extends WP_List_Table { + + /** + * Get a list of columns. + * + * @since 0.1-dev + * + * @return array + */ + public function get_columns() { + return array( + 'name' => wp_strip_all_tags( __( 'Name', 'two-factor' ) ), + 'added' => wp_strip_all_tags( __( 'Added', 'two-factor' ) ), + 'last_used' => wp_strip_all_tags( __( 'Last Used', 'two-factor' ) ), + ); + } + + /** + * Prepares the list of items for displaying. + * + * @since 0.1-dev + */ + public function prepare_items() { + $columns = $this->get_columns(); + $hidden = array(); + $sortable = array(); + $primary = 'name'; + $this->_column_headers = array( $columns, $hidden, $sortable, $primary ); + } + + /** + * Generates content for a single row of the table + * + * @since 0.1-dev + * @access protected + * + * @param object $item The current item. + * @param string $column_name The current column name. + * @return string + */ + protected function column_default( $item, $column_name ) { + switch ( $column_name ) { + case 'name': + $out = '<div class="hidden" id="inline_' . esc_attr( $item->keyHandle ) . '">'; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + $out .= '<div class="name">' . esc_html( $item->name ) . '</div>'; + $out .= '</div>'; + + $actions = array( + 'rename hide-if-no-js' => Two_Factor_FIDO_U2F_Admin::rename_link( $item ), + 'delete' => Two_Factor_FIDO_U2F_Admin::delete_link( $item ), + ); + + return esc_html( $item->name ) . $out . self::row_actions( $actions ); + case 'added': + return gmdate( get_option( 'date_format', 'r' ), $item->added ); + case 'last_used': + return gmdate( get_option( 'date_format', 'r' ), $item->last_used ); + default: + return 'WTF^^?'; + } + } + + /** + * Generates custom table navigation to prevent conflicting nonces. + * + * @since 0.1-dev + * @access protected + * + * @param string $which The location of the bulk actions: 'top' or 'bottom'. + */ + protected function display_tablenav( $which ) { + // Not used for the Security key list. + } + + /** + * Generates content for a single row of the table + * + * @since 0.1-dev + * @access public + * + * @param object $item The current item. + */ + public function single_row( $item ) { + ?> + <tr id="key-<?php echo esc_attr( $item->keyHandle ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase ?>"> + <?php $this->single_row_columns( $item ); ?> + </tr> + <?php + } + + /** + * Outputs the hidden row displayed when inline editing + * + * @since 0.1-dev + */ + public function inline_edit() { + ?> + <table style="display: none"> + <tbody id="inlineedit"> + <tr id="inline-edit" class="inline-edit-row" style="display: none"> + <td colspan="<?php echo esc_attr( $this->get_column_count() ); ?>" class="colspanchange"> + <fieldset> + <div class="inline-edit-col"> + <label> + <span class="title"><?php esc_html_e( 'Name', 'two-factor' ); ?></span> + <span class="input-text-wrap"><input type="text" name="name" class="ptitle" value="" /></span> + </label> + </div> + </fieldset> + <?php + $core_columns = array( + 'name' => true, + 'added' => true, + 'last_used' => true, + ); + list( $columns ) = $this->get_column_info(); + foreach ( $columns as $column_name => $column_display_name ) { + if ( isset( $core_columns[ $column_name ] ) ) { + continue; + } + + /** This action is documented in wp-admin/includes/class-wp-posts-list-table.php */ + do_action( 'quick_edit_custom_box', $column_name, 'edit-security-keys' ); + } + ?> + <p class="inline-edit-save submit"> + <a href="#inline-edit" class="cancel button-secondary alignleft"><?php esc_html_e( 'Cancel', 'two-factor' ); ?></a> + <a href="#inline-edit" class="save button-primary alignright"><?php esc_html_e( 'Update', 'two-factor' ); ?></a> + <span class="spinner"></span> + <span class="error" style="display:none;"></span> + <?php wp_nonce_field( 'keyinlineeditnonce', '_inline_edit', false ); ?> + <br class="clear" /> + </p> + </td> + </tr> + </tbody> + </table> + <?php + } +} diff --git a/wp-content/plugins/two-factor/providers/class-two-factor-fido-u2f-admin.php b/wp-content/plugins/two-factor/providers/class-two-factor-fido-u2f-admin.php new file mode 100644 index 0000000000000000000000000000000000000000..7fc297f7165a2dbe4849ab0ec63717732d9a7807 --- /dev/null +++ b/wp-content/plugins/two-factor/providers/class-two-factor-fido-u2f-admin.php @@ -0,0 +1,358 @@ +<?php +/** + * Class for registering & modifying FIDO U2F security keys. + * + * @package Two_Factor + */ + +/** + * Class for registering & modifying FIDO U2F security keys. + * + * @since 0.1-dev + * + * @package Two_Factor + */ +class Two_Factor_FIDO_U2F_Admin { + + /** + * The user meta register data. + * + * @type string + */ + const REGISTER_DATA_USER_META_KEY = '_two_factor_fido_u2f_register_request'; + + /** + * Add various hooks. + * + * @since 0.1-dev + * + * @access public + * @static + */ + public static function add_hooks() { + add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_assets' ) ); + add_action( 'show_user_security_settings', array( __CLASS__, 'show_user_profile' ) ); + add_action( 'personal_options_update', array( __CLASS__, 'catch_submission' ), 0 ); + add_action( 'edit_user_profile_update', array( __CLASS__, 'catch_submission' ), 0 ); + add_action( 'load-profile.php', array( __CLASS__, 'catch_delete_security_key' ) ); + add_action( 'load-user-edit.php', array( __CLASS__, 'catch_delete_security_key' ) ); + add_action( 'wp_ajax_inline-save-key', array( __CLASS__, 'wp_ajax_inline_save' ) ); + } + + /** + * Enqueue assets. + * + * @since 0.1-dev + * + * @access public + * @static + * + * @param string $hook Current page. + */ + public static function enqueue_assets( $hook ) { + if ( ! in_array( $hook, array( 'user-edit.php', 'profile.php' ), true ) ) { + return; + } + + $user_id = Two_Factor_Core::current_user_being_edited(); + if ( ! $user_id ) { + return; + } + + $security_keys = Two_Factor_FIDO_U2F::get_security_keys( $user_id ); + + // @todo Ensure that scripts don't fail because of missing u2fL10n. + try { + $data = Two_Factor_FIDO_U2F::$u2f->getRegisterData( $security_keys ); + list( $req,$sigs ) = $data; + + update_user_meta( $user_id, self::REGISTER_DATA_USER_META_KEY, $req ); + } catch ( Exception $e ) { + return false; + } + + wp_enqueue_style( + 'fido-u2f-admin', + plugins_url( 'css/fido-u2f-admin.css', __FILE__ ), + null, + self::asset_version() + ); + + wp_enqueue_script( + 'fido-u2f-admin', + plugins_url( 'js/fido-u2f-admin.js', __FILE__ ), + array( 'jquery', 'fido-u2f-api' ), + self::asset_version(), + true + ); + + /** + * Pass a U2F challenge and user data to our scripts + */ + + $translation_array = array( + 'user_id' => $user_id, + 'register' => array( + 'request' => $req, + 'sigs' => $sigs, + ), + 'text' => array( + 'insert' => esc_html__( 'Now insert (and tap) your Security Key.', 'two-factor' ), + 'error' => esc_html__( 'U2F request failed.', 'two-factor' ), + 'error_codes' => array( + // Map u2f.ErrorCodes to error messages. + 0 => esc_html__( 'Request OK.', 'two-factor' ), + 1 => esc_html__( 'Other U2F error.', 'two-factor' ), + 2 => esc_html__( 'Bad U2F request.', 'two-factor' ), + 3 => esc_html__( 'Unsupported U2F configuration.', 'two-factor' ), + 4 => esc_html__( 'U2F device ineligible.', 'two-factor' ), + 5 => esc_html__( 'U2F request timeout reached.', 'two-factor' ), + ), + 'u2f_not_supported' => esc_html__( 'FIDO U2F appears to be not supported by your web browser. Try using Google Chrome or Firefox.', 'two-factor' ), + ), + ); + + wp_localize_script( + 'fido-u2f-admin', + 'u2fL10n', + $translation_array + ); + + /** + * Script for admin UI + */ + + wp_enqueue_script( + 'inline-edit-key', + plugins_url( 'js/fido-u2f-admin-inline-edit.js', __FILE__ ), + array( 'jquery' ), + self::asset_version(), + true + ); + + wp_localize_script( + 'inline-edit-key', + 'inlineEditL10n', + array( + 'error' => esc_html__( 'Error while saving the changes.', 'two-factor' ), + ) + ); + } + + /** + * Return the current asset version number. + * + * Added as own helper to allow swapping the implementation once we inject + * it as a dependency. + * + * @return string + */ + protected static function asset_version() { + return Two_Factor_FIDO_U2F::asset_version(); + } + + /** + * Display the security key section in a users profile. + * + * This executes during the `show_user_security_settings` action. + * + * @since 0.1-dev + * + * @access public + * @static + * + * @param WP_User $user WP_User object of the logged-in user. + */ + public static function show_user_profile( $user ) { + wp_nonce_field( "user_security_keys-{$user->ID}", '_nonce_user_security_keys' ); + $new_key = false; + + $security_keys = Two_Factor_FIDO_U2F::get_security_keys( $user->ID ); + if ( $security_keys ) { + foreach ( $security_keys as &$security_key ) { + if ( property_exists( $security_key, 'new' ) ) { + $new_key = true; + unset( $security_key->new ); + + // If we've got a new one, update the db record to not save it there any longer. + Two_Factor_FIDO_U2F::update_security_key( $user->ID, $security_key ); + } + } + unset( $security_key ); + } + + ?> + <div class="security-keys" id="security-keys-section"> + <h3><?php esc_html_e( 'Security Keys', 'two-factor' ); ?></h3> + + <?php if ( ! is_ssl() ) : ?> + <p class="u2f-error-https"> + <em><?php esc_html_e( 'U2F requires an HTTPS connection. You won\'t be able to add new security keys over HTTP.', 'two-factor' ); ?></em> + </p> + <?php endif; ?> + + <div class="register-security-key"> + <input type="hidden" name="do_new_security_key" id="do_new_security_key" /> + <input type="hidden" name="u2f_response" id="u2f_response" /> + <button type="button" class="button button-secondary" id="register_security_key"><?php echo esc_html( _x( 'Register New Key', 'security key', 'two-factor' ) ); ?></button> + <span class="spinner"></span> + <span class="security-key-status"></span> + </div> + + <?php if ( $new_key ) : ?> + <div class="notice notice-success is-dismissible"> + <p class="new-security-key"><?php esc_html_e( 'Your new security key registered.', 'two-factor' ); ?></p> + </div> + <?php endif; ?> + + <p><a href="https://support.google.com/accounts/answer/6103523"><?php esc_html_e( 'You can find FIDO U2F Security Key devices for sale from here.', 'two-factor' ); ?></a></p> + + <?php + require TWO_FACTOR_DIR . 'providers/class-two-factor-fido-u2f-admin-list-table.php'; + $u2f_list_table = new Two_Factor_FIDO_U2F_Admin_List_Table(); + $u2f_list_table->items = $security_keys; + $u2f_list_table->prepare_items(); + $u2f_list_table->display(); + $u2f_list_table->inline_edit(); + ?> + </div> + <?php + } + + /** + * Catch the non-ajax submission from the new form. + * + * This executes during the `personal_options_update` & `edit_user_profile_update` actions. + * + * @since 0.1-dev + * + * @access public + * @static + * + * @param int $user_id User ID. + * @return false + */ + public static function catch_submission( $user_id ) { + if ( ! empty( $_REQUEST['do_new_security_key'] ) ) { + check_admin_referer( "user_security_keys-{$user_id}", '_nonce_user_security_keys' ); + + try { + $response = json_decode( stripslashes( $_POST['u2f_response'] ) ); + $reg = Two_Factor_FIDO_U2F::$u2f->doRegister( get_user_meta( $user_id, self::REGISTER_DATA_USER_META_KEY, true ), $response ); + $reg->new = true; + + Two_Factor_FIDO_U2F::add_security_key( $user_id, $reg ); + } catch ( Exception $e ) { + return false; + } + + delete_user_meta( $user_id, self::REGISTER_DATA_USER_META_KEY ); + + wp_safe_redirect( + add_query_arg( + array( + 'new_app_pass' => 1, + ), + wp_get_referer() + ) . '#security-keys-section' + ); + exit; + } + } + + /** + * Catch the delete security key request. + * + * This executes during the `load-profile.php` & `load-user-edit.php` actions. + * + * @since 0.1-dev + * + * @access public + * @static + */ + public static function catch_delete_security_key() { + $user_id = Two_Factor_Core::current_user_being_edited(); + + if ( ! empty( $user_id ) && ! empty( $_REQUEST['delete_security_key'] ) ) { + $slug = $_REQUEST['delete_security_key']; + + check_admin_referer( "delete_security_key-{$slug}", '_nonce_delete_security_key' ); + + Two_Factor_FIDO_U2F::delete_security_key( $user_id, $slug ); + + wp_safe_redirect( remove_query_arg( 'new_app_pass', wp_get_referer() ) . '#security-keys-section' ); + } + } + + /** + * Generate a link to rename a specified security key. + * + * @since 0.1-dev + * + * @access public + * @static + * + * @param array $item The current item. + * @return string + */ + public static function rename_link( $item ) { + return sprintf( '<a href="#" class="editinline">%s</a>', esc_html__( 'Rename', 'two-factor' ) ); + } + + /** + * Generate a link to delete a specified security key. + * + * @since 0.1-dev + * + * @access public + * @static + * + * @param array $item The current item. + * @return string + */ + public static function delete_link( $item ) { + $delete_link = add_query_arg( 'delete_security_key', $item->keyHandle ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + $delete_link = wp_nonce_url( $delete_link, "delete_security_key-{$item->keyHandle}", '_nonce_delete_security_key' ); + return sprintf( '<a href="%1$s">%2$s</a>', esc_url( $delete_link ), esc_html__( 'Delete', 'two-factor' ) ); + } + + /** + * Ajax handler for quick edit saving for a security key. + * + * @since 0.1-dev + * + * @access public + * @static + */ + public static function wp_ajax_inline_save() { + check_ajax_referer( 'keyinlineeditnonce', '_inline_edit' ); + + require TWO_FACTOR_DIR . 'providers/class-two-factor-fido-u2f-admin-list-table.php'; + $wp_list_table = new Two_Factor_FIDO_U2F_Admin_List_Table(); + + if ( ! isset( $_POST['keyHandle'] ) ) { + wp_die(); + } + + $user_id = Two_Factor_Core::current_user_being_edited(); + $security_keys = Two_Factor_FIDO_U2F::get_security_keys( $user_id ); + if ( ! $security_keys ) { + wp_die(); + } + + foreach ( $security_keys as &$key ) { + if ( $key->keyHandle === $_POST['keyHandle'] ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + break; + } + } + + $key->name = $_POST['name']; + + $updated = Two_Factor_FIDO_U2F::update_security_key( $user_id, $key ); + if ( ! $updated ) { + wp_die( esc_html__( 'Item not updated.', 'two-factor' ) ); + } + $wp_list_table->single_row( $key ); + wp_die(); + } +} diff --git a/wp-content/plugins/two-factor/providers/class-two-factor-fido-u2f.php b/wp-content/plugins/two-factor/providers/class-two-factor-fido-u2f.php new file mode 100644 index 0000000000000000000000000000000000000000..330451c2f9c4c107e0ded1196c079bca3903e2f9 --- /dev/null +++ b/wp-content/plugins/two-factor/providers/class-two-factor-fido-u2f.php @@ -0,0 +1,397 @@ +<?php +/** + * Class for creating a FIDO Universal 2nd Factor provider. + * + * @package Two_Factor + */ + +/** + * Class for creating a FIDO Universal 2nd Factor provider. + * + * @since 0.1-dev + * + * @package Two_Factor + */ +class Two_Factor_FIDO_U2F extends Two_Factor_Provider { + + /** + * U2F Library + * + * @var u2flib_server\U2F + */ + public static $u2f; + + /** + * The user meta registered key. + * + * @type string + */ + const REGISTERED_KEY_USER_META_KEY = '_two_factor_fido_u2f_registered_key'; + + /** + * The user meta authenticate data. + * + * @type string + */ + const AUTH_DATA_USER_META_KEY = '_two_factor_fido_u2f_login_request'; + + /** + * Version number for the bundled assets. + * + * @var string + */ + const U2F_ASSET_VERSION = '0.2.1'; + + /** + * Ensures only one instance of this class exists in memory at any one time. + * + * @return \Two_Factor_FIDO_U2F + */ + public static function get_instance() { + static $instance; + + if ( ! isset( $instance ) ) { + $instance = new self(); + } + + return $instance; + } + + /** + * Class constructor. + * + * @since 0.1-dev + */ + protected function __construct() { + if ( version_compare( PHP_VERSION, '5.3.0', '<' ) ) { + return; + } + + require_once TWO_FACTOR_DIR . 'includes/Yubico/U2F.php'; + self::$u2f = new u2flib_server\U2F( self::get_u2f_app_id() ); + + require_once TWO_FACTOR_DIR . 'providers/class-two-factor-fido-u2f-admin.php'; + Two_Factor_FIDO_U2F_Admin::add_hooks(); + + // Ensure the script dependencies have been registered before they're enqueued at a later priority. + add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_scripts' ), 5 ); + add_action( 'wp_enqueue_scripts', array( __CLASS__, 'enqueue_scripts' ), 5 ); + add_action( 'login_enqueue_scripts', array( __CLASS__, 'enqueue_scripts' ), 5 ); + + add_action( 'two_factor_user_options_' . __CLASS__, array( $this, 'user_options' ) ); + + return parent::__construct(); + } + + /** + * Get the asset version number. + * + * TODO: There should be a plugin-level helper for getting the current plugin version. + * + * @return string + */ + public static function asset_version() { + return self::U2F_ASSET_VERSION; + } + + /** + * Return the U2F AppId. U2F requires the AppID to use HTTPS + * and a top-level domain. + * + * @return string AppID URI + */ + public static function get_u2f_app_id() { + $url_parts = wp_parse_url( home_url() ); + + if ( ! empty( $url_parts['port'] ) ) { + return sprintf( 'https://%s:%d', $url_parts['host'], $url_parts['port'] ); + } else { + return sprintf( 'https://%s', $url_parts['host'] ); + } + } + + /** + * Returns the name of the provider. + * + * @since 0.1-dev + */ + public function get_label() { + return _x( 'FIDO U2F Security Keys', 'Provider Label', 'two-factor' ); + } + + /** + * Register script dependencies used during login and when + * registering keys in the WP admin. + * + * @return void + */ + public static function enqueue_scripts() { + wp_register_script( + 'fido-u2f-api', + plugins_url( 'includes/Google/u2f-api.js', dirname( __FILE__ ) ), + null, + self::asset_version(), + true + ); + + wp_register_script( + 'fido-u2f-login', + plugins_url( 'js/fido-u2f-login.js', __FILE__ ), + array( 'jquery', 'fido-u2f-api' ), + self::asset_version(), + true + ); + } + + /** + * 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. + * @return null + */ + public function authentication_page( $user ) { + require_once ABSPATH . '/wp-admin/includes/template.php'; + + // U2F doesn't work without HTTPS. + if ( ! is_ssl() ) { + ?> + <p><?php esc_html_e( 'U2F requires an HTTPS connection. Please use an alternative 2nd factor method.', 'two-factor' ); ?></p> + <?php + + return; + } + + try { + $keys = self::get_security_keys( $user->ID ); + $data = self::$u2f->getAuthenticateData( $keys ); + update_user_meta( $user->ID, self::AUTH_DATA_USER_META_KEY, $data ); + } catch ( Exception $e ) { + ?> + <p><?php esc_html_e( 'An error occurred while creating authentication data.', 'two-factor' ); ?></p> + <?php + return null; + } + + wp_localize_script( + 'fido-u2f-login', + 'u2fL10n', + array( + 'request' => $data, + ) + ); + + wp_enqueue_script( 'fido-u2f-login' ); + + ?> + <p><?php esc_html_e( 'Now insert (and tap) your Security Key.', 'two-factor' ); ?></p> + <input type="hidden" name="u2f_response" id="u2f_response" /> + <?php + } + + /** + * 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 ) { + $requests = get_user_meta( $user->ID, self::AUTH_DATA_USER_META_KEY, true ); + + $response = json_decode( stripslashes( $_REQUEST['u2f_response'] ) ); + + $keys = self::get_security_keys( $user->ID ); + + try { + $reg = self::$u2f->doAuthenticate( $requests, $keys, $response ); + + $reg->last_used = time(); + + self::update_security_key( $user->ID, $reg ); + + return true; + } catch ( Exception $e ) { + return false; + } + } + + /** + * 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 (bool) self::get_security_keys( $user->ID ); + } + + /** + * 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 ) { + ?> + <p> + <?php esc_html_e( 'Requires an HTTPS connection. Configure your security keys in the "Security Keys" section below.', 'two-factor' ); ?> + </p> + <?php + } + + /** + * Add registered security key to a user. + * + * @since 0.1-dev + * + * @param int $user_id User ID. + * @param object $register The data of registered security key. + * @return int|bool Meta ID on success, false on failure. + */ + public static function add_security_key( $user_id, $register ) { + if ( ! is_numeric( $user_id ) ) { + return false; + } + + if ( + ! is_object( $register ) + || ! property_exists( $register, 'keyHandle' ) || empty( $register->keyHandle ) // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + || ! property_exists( $register, 'publicKey' ) || empty( $register->publicKey ) // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + || ! property_exists( $register, 'certificate' ) || empty( $register->certificate ) + || ! property_exists( $register, 'counter' ) || ( -1 > $register->counter ) + ) { + return false; + } + + $register = array( + 'keyHandle' => $register->keyHandle, // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + 'publicKey' => $register->publicKey, // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + 'certificate' => $register->certificate, + 'counter' => $register->counter, + ); + + $register['name'] = __( 'New Security Key', 'two-factor' ); + $register['added'] = time(); + $register['last_used'] = $register['added']; + + return add_user_meta( $user_id, self::REGISTERED_KEY_USER_META_KEY, $register ); + } + + /** + * Retrieve registered security keys for a user. + * + * @since 0.1-dev + * + * @param int $user_id User ID. + * @return array|bool Array of keys on success, false on failure. + */ + public static function get_security_keys( $user_id ) { + if ( ! is_numeric( $user_id ) ) { + return false; + } + + $keys = get_user_meta( $user_id, self::REGISTERED_KEY_USER_META_KEY ); + if ( $keys ) { + foreach ( $keys as &$key ) { + $key = (object) $key; + } + unset( $key ); + } + + return $keys; + } + + /** + * Update registered security key. + * + * Use the $prev_value parameter to differentiate between meta fields with the + * same key and user ID. + * + * If the meta field for the user does not exist, it will be added. + * + * @since 0.1-dev + * + * @param int $user_id User ID. + * @param object $data The data of registered security key. + * @return int|bool Meta ID if the key didn't exist, true on successful update, false on failure. + */ + public static function update_security_key( $user_id, $data ) { + if ( ! is_numeric( $user_id ) ) { + return false; + } + + if ( + ! is_object( $data ) + || ! property_exists( $data, 'keyHandle' ) || empty( $data->keyHandle ) // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + || ! property_exists( $data, 'publicKey' ) || empty( $data->publicKey ) // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + || ! property_exists( $data, 'certificate' ) || empty( $data->certificate ) + || ! property_exists( $data, 'counter' ) || ( -1 > $data->counter ) + ) { + return false; + } + + $keys = self::get_security_keys( $user_id ); + if ( $keys ) { + foreach ( $keys as $key ) { + if ( $key->keyHandle === $data->keyHandle ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + return update_user_meta( $user_id, self::REGISTERED_KEY_USER_META_KEY, (array) $data, (array) $key ); + } + } + } + + return self::add_security_key( $user_id, $data ); + } + + /** + * Remove registered security key matching criteria from a user. + * + * @since 0.1-dev + * + * @param int $user_id User ID. + * @param string $keyHandle Optional. Key handle. + * @return bool True on success, false on failure. + */ + public static function delete_security_key( $user_id, $keyHandle = null ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase + global $wpdb; + + if ( ! is_numeric( $user_id ) ) { + return false; + } + + $user_id = absint( $user_id ); + if ( ! $user_id ) { + return false; + } + + $keyHandle = wp_unslash( $keyHandle ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase + $keyHandle = maybe_serialize( $keyHandle ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase + + $query = $wpdb->prepare( "SELECT umeta_id FROM {$wpdb->usermeta} WHERE meta_key = %s AND user_id = %d", self::REGISTERED_KEY_USER_META_KEY, $user_id ); + + if ( $keyHandle ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase + $key_handle_lookup = sprintf( ':"%s";s:', $keyHandle ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase + + $query .= $wpdb->prepare( + ' AND meta_value LIKE %s', + '%' . $wpdb->esc_like( $key_handle_lookup ) . '%' + ); + } + + $meta_ids = $wpdb->get_col( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + if ( ! count( $meta_ids ) ) { + return false; + } + + foreach ( $meta_ids as $meta_id ) { + delete_metadata_by_mid( 'user', $meta_id ); + } + + return true; + } +} diff --git a/wp-content/plugins/two-factor/providers/class-two-factor-provider.php b/wp-content/plugins/two-factor/providers/class-two-factor-provider.php new file mode 100644 index 0000000000000000000000000000000000000000..a2f9be062eb2cab7513833293f71dc4baa78ca59 --- /dev/null +++ b/wp-content/plugins/two-factor/providers/class-two-factor-provider.php @@ -0,0 +1,102 @@ +<?php +/** + * Abstract class for creating two factor authentication providers. + * + * @package Two_Factor + */ + +/** + * Abstract class for creating two factor authentication providers. + * + * @since 0.1-dev + * + * @package Two_Factor + */ +abstract class Two_Factor_Provider { + + /** + * Class constructor. + * + * @since 0.1-dev + */ + protected function __construct() { + return $this; + } + + /** + * Returns the name of the provider. + * + * @since 0.1-dev + * + * @return string + */ + abstract public function get_label(); + + /** + * Prints the name of the provider. + * + * @since 0.1-dev + */ + public function print_label() { + echo esc_html( $this->get_label() ); + } + + /** + * 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. + */ + abstract public function authentication_page( $user ); + + /** + * Allow providers to do extra processing before the authentication. + * Return `true` to prevent the authentication and render the + * authentication page. + * + * @param WP_User $user WP_User object of the logged-in user. + * @return boolean + */ + public function pre_process_authentication( $user ) { + 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 + */ + abstract public function validate_authentication( $user ); + + /** + * 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 + */ + abstract public function is_available_for_user( $user ); + + /** + * Generate a random eight-digit string to send out as an auth code. + * + * @since 0.1-dev + * + * @param int $length The code length. + * @param string|array $chars Valid auth code characters. + * @return string + */ + public function get_code( $length = 8, $chars = '1234567890' ) { + $code = ''; + if ( is_array( $chars ) ) { + $chars = implode( '', $chars ); + } + for ( $i = 0; $i < $length; $i++ ) { + $code .= substr( $chars, wp_rand( 0, strlen( $chars ) - 1 ), 1 ); + } + return $code; + } +} diff --git a/wp-content/plugins/two-factor/providers/class-two-factor-totp.php b/wp-content/plugins/two-factor/providers/class-two-factor-totp.php new file mode 100644 index 0000000000000000000000000000000000000000..11f71f6e1779e939259273c80fc263a4b637f310 --- /dev/null +++ b/wp-content/plugins/two-factor/providers/class-two-factor-totp.php @@ -0,0 +1,558 @@ +<?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 { + + /** + * The user meta token key. + * + * @var string + */ + const SECRET_META_KEY = '_two_factor_totp_key'; + + /** + * The user meta token key. + * + * @var string + */ + const NOTICES_META_KEY = '_two_factor_totp_notices'; + + /** + * Action name for resetting the secret token. + * + * @var string + */ + const ACTION_SECRET_DELETE = 'totp-delete'; + + 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; + + /** + * Chracters used in base32 encoding. + * + * @var string + */ + private static $base_32_chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; + + /** + * Class constructor. Sets up hooks, etc. + */ + protected function __construct() { + add_action( 'two_factor_user_options_' . __CLASS__, array( $this, 'user_two_factor_options' ) ); + add_action( 'personal_options_update', array( $this, 'user_two_factor_options_update' ) ); + add_action( 'edit_user_profile_update', array( $this, 'user_two_factor_options_update' ) ); + add_action( 'two_factor_user_settings_action', array( $this, 'user_settings_action' ), 10, 2 ); + + return parent::__construct(); + } + + /** + * Ensures only one instance of this class exists in memory at any one time. + */ + public static function get_instance() { + static $instance; + if ( ! isset( $instance ) ) { + $instance = new self(); + } + return $instance; + } + + /** + * Returns the name of the provider. + */ + public function get_label() { + return _x( 'Time Based One-Time Password (TOTP)', 'Provider Label', 'two-factor' ); + } + + /** + * Trigger our custom user settings actions. + * + * @param integer $user_id User ID. + * @param string $action Action ID. + * + * @return void + */ + public function user_settings_action( $user_id, $action ) { + if ( self::ACTION_SECRET_DELETE === $action ) { + $this->delete_user_totp_key( $user_id ); + } + } + + /** + * Get the URL for deleting the secret token. + * + * @param integer $user_id User ID. + * + * @return string + */ + protected function get_token_delete_url_for_user( $user_id ) { + return Two_Factor_Core::get_user_update_action_url( $user_id, self::ACTION_SECRET_DELETE ); + } + + /** + * Display TOTP options on the user settings page. + * + * @param WP_User $user The current user being edited. + * @return false + */ + public function user_two_factor_options( $user ) { + if ( ! isset( $user->ID ) ) { + return false; + } + + wp_nonce_field( 'user_two_factor_totp_options', '_nonce_user_two_factor_totp_options', false ); + + $key = $this->get_user_totp_key( $user->ID ); + $this->admin_notices( $user->ID ); + + ?> + <div id="two-factor-totp-options"> + <?php + if ( empty( $key ) ) : + $key = $this->generate_key(); + $site_name = get_bloginfo( 'name', 'display' ); + $totp_title = apply_filters( 'two_factor_totp_title', $site_name . ':' . $user->user_login, $user ); + ?> + <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> + <p> + <img src="<?php echo esc_url( $this->get_google_qr_code( $totp_title, $key, $site_name ) ); ?>" id="two-factor-totp-qrcode" /> + </p> + <p> + <code><?php echo esc_html( $key ); ?></code> + </p> + <p> + <input type="hidden" name="two-factor-totp-key" value="<?php echo esc_attr( $key ); ?>" /> + <label for="two-factor-totp-authcode"> + <?php esc_html_e( 'Authentication Code:', 'two-factor' ); ?> + <input type="tel" name="two-factor-totp-authcode" id="two-factor-totp-authcode" class="input" value="" size="20" pattern="[0-9]*" /> + </label> + <input type="submit" class="button" name="two-factor-totp-submit" value="<?php esc_attr_e( 'Submit', 'two-factor' ); ?>" /> + </p> + <?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> + <a class="button" href="<?php echo esc_url( self::get_token_delete_url_for_user( $user->ID ) ); ?>"><?php esc_html_e( 'Reset Key', 'two-factor' ); ?></a> + <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> + </p> + <?php endif; ?> + </div> + <?php + } + + /** + * Save the options specified in `::user_two_factor_options()` + * + * @param integer $user_id The user ID whose options are being updated. + * + * @return void + */ + public function user_two_factor_options_update( $user_id ) { + $notices = array(); + $errors = array(); + + if ( isset( $_POST['_nonce_user_two_factor_totp_options'] ) ) { + check_admin_referer( 'user_two_factor_totp_options', '_nonce_user_two_factor_totp_options' ); + + // Validate and store a new secret key. + if ( ! empty( $_POST['two-factor-totp-authcode'] ) && ! empty( $_POST['two-factor-totp-key'] ) ) { + // Don't use filter_input() because we can't mock it during tests for now. + $authcode = filter_var( sanitize_text_field( $_POST['two-factor-totp-authcode'] ), FILTER_SANITIZE_NUMBER_INT ); + $key = sanitize_text_field( $_POST['two-factor-totp-key'] ); + + if ( $this->is_valid_key( $key ) ) { + if ( $this->is_valid_authcode( $key, $authcode ) ) { + if ( ! $this->set_user_totp_key( $user_id, $key ) ) { + $errors[] = __( 'Unable to save Two Factor Authentication code. Please re-scan the QR code and enter the code provided by your application.', 'two-factor' ); + } + } else { + $errors[] = __( 'Invalid Two Factor Authentication code.', 'two-factor' ); + } + } else { + $errors[] = __( 'Invalid Two Factor Authentication secret key.', 'two-factor' ); + } + } + + if ( ! empty( $errors ) ) { + $notices['error'] = $errors; + } + + if ( ! empty( $notices ) ) { + update_user_meta( $user_id, self::NOTICES_META_KEY, $notices ); + } + } + } + + /** + * 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 ) { + 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; + } + + /** + * Display any available admin notices. + * + * @param integer $user_id User ID. + * + * @return void + */ + public function admin_notices( $user_id ) { + $notices = get_user_meta( $user_id, self::NOTICES_META_KEY, true ); + + if ( ! empty( $notices ) ) { + delete_user_meta( $user_id, self::NOTICES_META_KEY ); + + foreach ( $notices as $class => $messages ) { + ?> + <div class="<?php echo esc_attr( $class ); ?>"> + <?php + foreach ( $messages as $msg ) { + ?> + <p> + <span><?php echo esc_html( $msg ); ?><span> + </p> + <?php + } + ?> + </div> + <?php + } + } + } + + /** + * Validates authentication. + * + * @param WP_User $user WP_User object of the logged-in user. + * + * @return bool Whether the user gave a valid code + */ + public function validate_authentication( $user ) { + if ( ! empty( $_REQUEST['authcode'] ) ) { + return $this->is_valid_authcode( + $this->get_user_totp_key( $user->ID ), + sanitize_text_field( $_REQUEST['authcode'] ) + ); + } + + return false; + } + + /** + * 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 bool Whether the code is valid within the time frame + */ + public static function is_valid_authcode( $key, $authcode ) { + /** + * 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' ) ); + + $time = time() / self::DEFAULT_TIME_STEP_SEC; + + foreach ( $ticks as $offset ) { + $log_time = $time + $offset; + if ( self::calc_totp( $key, $log_time ) === $authcode ) { + return true; + } + } + 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 ); + } + $highmap = 0xffffffff << 32; + $higher = ( $value & $highmap ) >> 32; + } else { + /* + * 32bit 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 ); + } + + /** + * Uses the Google Charts API to build a QR Code for use with an otpauth url + * + * @param string $name The name to display in the Authentication app. + * @param string $key The secret key to share with the Authentication app. + * @param string $title The title to display in the Authentication app. + * + * @return string A URL to use as an img src to display the QR code + */ + public static function get_google_qr_code( $name, $key, $title = null ) { + // Encode to support spaces, question marks and other characters. + $name = rawurlencode( $name ); + $google_url = urlencode( 'otpauth://totp/' . $name . '?secret=' . $key ); + if ( isset( $title ) ) { + $google_url .= urlencode( '&issuer=' . rawurlencode( $title ) ); + } + return 'https://chart.googleapis.com/chart?chs=200x200&chld=M|0&cht=qr&chl=' . $google_url; + } + + /** + * 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. + */ + public function authentication_page( $user ) { + require_once ABSPATH . '/wp-admin/includes/template.php'; + ?> + <p> + <?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> + <input type="tel" autocomplete="off" name="authcode" id="authcode" class="input" value="" size="20" pattern="[0-9]*" /> + </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; + } +} diff --git a/wp-content/plugins/two-factor/readme.md b/wp-content/plugins/two-factor/readme.md index ba7bf6312fb2a6b2295b8d763db592287fc7bf45..fd83f1e69f3eba6b01a4fd47c0e42c0e5695c19b 100644 --- a/wp-content/plugins/two-factor/readme.md +++ b/wp-content/plugins/two-factor/readme.md @@ -7,11 +7,11 @@ Enable Two-Factor Authentication using time-based one-time passwords (OTP, Googl **Contributors:** [georgestephanis](https://profiles.wordpress.org/georgestephanis), [valendesigns](https://profiles.wordpress.org/valendesigns), [stevenkword](https://profiles.wordpress.org/stevenkword), [extendwings](https://profiles.wordpress.org/extendwings), [sgrant](https://profiles.wordpress.org/sgrant), [aaroncampbell](https://profiles.wordpress.org/aaroncampbell), [johnbillion](https://profiles.wordpress.org/johnbillion), [stevegrunwell](https://profiles.wordpress.org/stevegrunwell), [netweb](https://profiles.wordpress.org/netweb), [kasparsd](https://profiles.wordpress.org/kasparsd), [alihusnainarshad](https://profiles.wordpress.org/alihusnainarshad), [passoniate](https://profiles.wordpress.org/passoniate) **Tags:** [two factor](https://wordpress.org/plugins/tags/two-factor), [two step](https://wordpress.org/plugins/tags/two-step), [authentication](https://wordpress.org/plugins/tags/authentication), [login](https://wordpress.org/plugins/tags/login), [totp](https://wordpress.org/plugins/tags/totp), [fido u2f](https://wordpress.org/plugins/tags/fido-u2f), [u2f](https://wordpress.org/plugins/tags/u2f), [email](https://wordpress.org/plugins/tags/email), [backup codes](https://wordpress.org/plugins/tags/backup-codes), [2fa](https://wordpress.org/plugins/tags/2fa), [yubikey](https://wordpress.org/plugins/tags/yubikey) **Requires at least:** 4.3 -**Tested up to:** 5.4 +**Tested up to:** 5.5 **Stable tag:** trunk (master) **Requires PHP:** 5.6 -[](https://travis-ci.org/WordPress/two-factor) [](https://coveralls.io/github/WordPress/two-factor) [](http://gruntjs.com) +[](https://travis-ci.org/wordpress/two-factor) [](https://coveralls.io/github/wordpress/two-factor) [](http://gruntjs.com) ## Description ## diff --git a/wp-content/plugins/two-factor/readme.txt b/wp-content/plugins/two-factor/readme.txt index b355343a971ac2f2a38c811f39529fb7d208e3b2..59aaee4a0659565009f1b671d34f6b3f5b578687 100644 --- a/wp-content/plugins/two-factor/readme.txt +++ b/wp-content/plugins/two-factor/readme.txt @@ -2,7 +2,7 @@ Contributors: georgestephanis, valendesigns, stevenkword, extendwings, sgrant, aaroncampbell, johnbillion, stevegrunwell, netweb, kasparsd, alihusnainarshad, passoniate Tags: two factor, two step, authentication, login, totp, fido u2f, u2f, email, backup codes, 2fa, yubikey Requires at least: 4.3 -Tested up to: 5.4 +Tested up to: 5.5 Requires PHP: 5.6 Stable tag: trunk diff --git a/wp-content/plugins/two-factor/two-factor.php b/wp-content/plugins/two-factor/two-factor.php index a607d0bfc6697e8dfa014f010a48ce6298a838e0..d94f9924b8e2ca9bbefbe9e4678de68c185a5512 100644 --- a/wp-content/plugins/two-factor/two-factor.php +++ b/wp-content/plugins/two-factor/two-factor.php @@ -1,10 +1,18 @@ <?php /** + * Two Factor + * + * @package Two_Factor + * @author Plugin Contributors + * @copyright 2020 Plugin Contributors + * @license GPL-2.0-or-later + * + * @wordpress-plugin * Plugin Name: Two Factor * Plugin URI: https://wordpress.org/plugins/two-factor/ * Description: Two-Factor Authentication using time-based one-time passwords, Universal 2nd Factor (FIDO U2F), email and backup verification codes. * Author: Plugin Contributors - * Version: 0.6.0 + * Version: 0.7.0 * Author URI: https://github.com/wordpress/two-factor/graphs/contributors * Network: True * Text Domain: two-factor @@ -15,20 +23,25 @@ */ define( 'TWO_FACTOR_DIR', plugin_dir_path( __FILE__ ) ); +/** + * Version of the plugin. + */ +define( 'TWO_FACTOR_VERSION', '0.7.0' ); + /** * Include the base class here, so that other plugins can also extend it. */ -require_once( TWO_FACTOR_DIR . 'providers/class.two-factor-provider.php' ); +require_once TWO_FACTOR_DIR . 'providers/class-two-factor-provider.php'; /** * Include the core that handles the common bits. */ -require_once( TWO_FACTOR_DIR . 'class-two-factor-core.php' ); +require_once TWO_FACTOR_DIR . 'class-two-factor-core.php'; /** * A compatability layer for some of the most-used plugins out there. */ -require_once( TWO_FACTOR_DIR . 'class-two-factor-compat.php' ); +require_once TWO_FACTOR_DIR . 'class-two-factor-compat.php'; $two_factor_compat = new Two_Factor_Compat();