diff --git a/class-two-factor-core.php b/class-two-factor-core.php index c034c53d..6580339e 100644 --- a/class-two-factor-core.php +++ b/class-two-factor-core.php @@ -1124,7 +1124,7 @@ public static function login_html( $user, $login_nonce, $redirect_to, $error_msg // Enforce numeric-only input for numeric inputmode elements. const form = document.querySelector( '#loginform' ), inputEl = document.querySelector( 'input.authcode[inputmode="numeric"]' ), - expectedLength = inputEl?.dataset.digits || 0; + expectedLength = parseInt( ( inputEl?.dataset.digits || 0 ), 10 ); if ( inputEl ) { let spaceInserted = false; diff --git a/providers/class-two-factor-backup-codes.php b/providers/class-two-factor-backup-codes.php index 6d6896bb..e20c1e4d 100644 --- a/providers/class-two-factor-backup-codes.php +++ b/providers/class-two-factor-backup-codes.php @@ -257,10 +257,10 @@ private function get_backup_code_length( $user ) { * * @since 0.11.0 * - * @param int $code_length Length of the backup code. Default 8. + * @param int $code_length Length of the backup code. Default is taken from the `two_factor_code_length` filter (which defaults to 8 if not filtered). * @param WP_User $user User object. */ - $code_length = (int) apply_filters( 'two_factor_backup_code_length', 8, $user ); + $code_length = (int) apply_filters( 'two_factor_backup_code_length', self::get_code_length(), $user ); return $code_length; } @@ -387,6 +387,18 @@ public function authentication_page( $user ) { $code_length = $this->get_backup_code_length( $user ); $code_placeholder = str_repeat( 'X', $code_length ); + /** + * Filters the `digits` dataset attribute of the backup code input field on the authentication screen. + * + * To disable autosubmit, set the digits to `0` via the core method `__return_zero`. + * + * @since 0.?.0 + * + * @param int $code_length The length of the backup code. + * @param Two_Factor_Provider $provider The two-factor provider instance. + */ + $code_length = apply_filters( 'two_factor_autosubmit_length', $code_length, $this ); + ?> get_token_length(); $token_placeholder = str_repeat( 'X', $token_length ); + /** This filter is documented in providers/class-two-factor-backup-codes.php */ + $token_length = apply_filters( 'two_factor_autosubmit_length', $token_length, $this ); + require_once ABSPATH . '/wp-admin/includes/template.php'; ?> > 32 ) & 0xFFFFFFFF; $lower = $value & 0xFFFFFFFF; - + return pack( 'NN', $higher, $lower ); } @@ -832,6 +832,10 @@ public function is_available_for_user( $user ) { * @codeCoverageIgnore */ public function authentication_page( $user ) { + + /** This filter is documented in providers/class-two-factor-backup-codes.php */ + $code_length = apply_filters( 'two_factor_autosubmit_length', self::DEFAULT_DIGIT_COUNT, $this ); + require_once ABSPATH . '/wp-admin/includes/template.php'; ?>
- +
provider->authentication_page( false ); + $default_output = ob_get_clean(); + $this->assertStringContainsString( 'data-digits="8"', $default_output ); + + // With filter: data-digits should be overridden to 0 (disables autosubmit). + add_filter( 'two_factor_autosubmit_length', '__return_zero' ); + ob_start(); + $this->provider->authentication_page( false ); + $filtered_output = ob_get_clean(); + remove_filter( 'two_factor_autosubmit_length', '__return_zero' ); + + $this->assertStringContainsString( 'data-digits="0"', $filtered_output ); + } } diff --git a/tests/providers/class-two-factor-provider.php b/tests/providers/class-two-factor-provider.php index dcce6c6f..93520ddc 100644 --- a/tests/providers/class-two-factor-provider.php +++ b/tests/providers/class-two-factor-provider.php @@ -87,4 +87,52 @@ public function test_get_instance() { $this->assertSame( $instance_one, $instance_two ); } + + /** + * Test that get_code_length() returns the default value when no filter is applied. + * + * @covers Two_Factor_Provider::get_code_length + */ + public function test_get_code_length_returns_default() { + $this->assertSame( 8, Two_Factor_Provider::get_code_length( 8 ) ); + $this->assertSame( 6, Two_Factor_Provider::get_code_length( 6 ) ); + } + + /** + * Test that the two_factor_code_length filter can override the default code length. + * + * @covers Two_Factor_Provider::get_code_length + */ + public function test_get_code_length_filter_overrides_default() { + $set_length_to_4 = function() { + return 4; + }; + add_filter( 'two_factor_code_length', $set_length_to_4 ); + $this->assertSame( 4, Two_Factor_Provider::get_code_length( 8 ) ); + remove_filter( 'two_factor_code_length', $set_length_to_4 ); + + $set_length_to_12 = function() { + return 12; + }; + add_filter( 'two_factor_code_length', $set_length_to_12 ); + $this->assertSame( 12, Two_Factor_Provider::get_code_length( 8 ) ); + remove_filter( 'two_factor_code_length', $set_length_to_12 ); + } + + /** + * Test that get_code( null ) uses the filtered code length from two_factor_code_length. + * + * @covers Two_Factor_Provider::get_code + * @covers Two_Factor_Provider::get_code_length + */ + public function test_get_code_with_null_uses_filtered_length() { + $set_length_to_5 = function() { + return 5; + }; + add_filter( 'two_factor_code_length', $set_length_to_5 ); + $code = Two_Factor_Provider::get_code( null ); + remove_filter( 'two_factor_code_length', $set_length_to_5 ); + + $this->assertSame( 5, strlen( $code ) ); + } }