Skip to content

Commit

Permalink
Destroy existing sessions when activating 2FA.
Browse files Browse the repository at this point in the history
  • Loading branch information
dd32 authored and iandunn committed Sep 4, 2023
1 parent 286aa22 commit 2d4b9ec
Show file tree
Hide file tree
Showing 2 changed files with 157 additions and 3 deletions.
17 changes: 14 additions & 3 deletions class-two-factor-core.php
Original file line number Diff line number Diff line change
Expand Up @@ -1543,9 +1543,9 @@ public static function user_two_factor_options_update( $user_id ) {
return;
}

$providers = self::get_providers();

$enabled_providers = $_POST[ self::ENABLED_PROVIDERS_USER_META_KEY ];
$providers = self::get_providers();
$enabled_providers = $_POST[ self::ENABLED_PROVIDERS_USER_META_KEY ];
$existing_providers = self::get_enabled_providers_for_user( $user_id );

// Enable only the available providers.
$enabled_providers = array_intersect( $enabled_providers, array_keys( $providers ) );
Expand All @@ -1556,6 +1556,17 @@ public static function user_two_factor_options_update( $user_id ) {
if ( ! empty( $new_provider ) && in_array( $new_provider, $enabled_providers, true ) ) {
update_user_meta( $user_id, self::PROVIDER_USER_META_KEY, $new_provider );
}

// Destroy other sessions if we've activated a new provider.
if ( array_diff( $enabled_providers, $existing_providers ) ) {
if ( $user_id === get_current_user_id() ) {
// Keep the current session, destroy others sessions for this user.
wp_destroy_other_sessions();
} else {
// Destroy all sessions for the user.
WP_Session_Tokens::get_instance( $user_id )->destroy_all();
}
}
}
}

Expand Down
143 changes: 143 additions & 0 deletions tests/class-two-factor-core.php
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,149 @@ public function test_dont_notify_admin_when_filter_disabled() {
reset_phpmailer_instance();
}

/**
* Validate that other sessions are destroyed once Two-Factor is enabled.
*
* @covers Two_Factor_Core::user_two_factor_options_update
*/
public function test_other_sessions_destroyed_when_enabling_2fa() {
$user_id = self::factory()->user->create(
array(
'user_login' => 'username',
'user_pass' => 'password',
)
);

$user = new WP_User( $user_id );

$session_manager = WP_Session_Tokens::get_instance( $user->ID );

$this->assertCount( 0, $session_manager->get_all(), 'No user sessions are present first' );

// Generate multiple existing sessions.
$session_manager->create( time() + HOUR_IN_SECONDS );
$session_manager->create( time() + DAY_IN_SECONDS );
$this->assertCount( 2, $session_manager->get_all(), 'Can fetch active sessions' );

$user_authenticated = wp_signon(
array(
'user_login' => 'username',
'user_password' => 'password',
)
);
$this->assertEquals( $user_authenticated, $user, 'User can authenticate' );

// Enable Two-Factor for the user.
wp_set_current_user( $user->ID );

$key = '_nonce_user_two_factor_options';
$nonce = wp_create_nonce( 'user_two_factor_options' );
$_POST[ $key ] = $nonce;
$_REQUEST[ $key ] = $nonce;

$_POST[ Two_Factor_Core::ENABLED_PROVIDERS_USER_META_KEY ] = array(
'Two_Factor_Dummy' => 'Two_Factor_Dummy'
);

Two_Factor_Core::user_two_factor_options_update( $user->ID );

// Validate that Two-Factor is now enabled.
$this->assertCount( 1, Two_Factor_Core::get_available_providers_for_user( $user->ID ) );
$this->assertCount( 1, Two_Factor_Core::get_enabled_providers_for_user( $user->ID ) );

// Validate that only the current session still exists.
$this->assertCount( 1, $session_manager->get_all(), 'All known authentication sessions have been destroyed' );

// Create another session, activate another provider, verify sessions are still valid.
$session_manager->create( time() + DAY_IN_SECONDS );
$this->assertCount( 2, $session_manager->get_all(), 'Failed to create another session' );

$_POST[ Two_Factor_Core::ENABLED_PROVIDERS_USER_META_KEY ] = array(
'Two_Factor_Dummy' => 'Two_Factor_Dummy',
'Two_Factor_Email' => 'Two_Factor_Email',
);

Two_Factor_Core::user_two_factor_options_update( $user->ID );

// Validate that Two-Factor is now enabled with two providers.
$this->assertCount( 2, Two_Factor_Core::get_available_providers_for_user( $user->ID ) );
$this->assertCount( 2, Two_Factor_Core::get_enabled_providers_for_user( $user->ID ) );

$this->assertCount( 1, $session_manager->get_all(), 'All known authentication sessions have been destroyed' );

// Create another session, disable a provider, verify both sessions still exist.
$session_manager->create( time() + DAY_IN_SECONDS );
$this->assertCount( 2, $session_manager->get_all(), 'Failed to create another session' );

$_POST[ Two_Factor_Core::ENABLED_PROVIDERS_USER_META_KEY ] = array(
'Two_Factor_Dummy' => 'Two_Factor_Dummy',
);

Two_Factor_Core::user_two_factor_options_update( $user->ID );

// Validate that Two-Factor is now enabled with two providers.
$this->assertCount( 1, Two_Factor_Core::get_available_providers_for_user( $user->ID ) );
$this->assertCount( 1, Two_Factor_Core::get_enabled_providers_for_user( $user->ID ) );

$this->assertCount( 2, $session_manager->get_all(), 'All known authentication sessions have been destroyed' );

}

/**
* Validate the administrators sessions are not modified when modifying another user.
*
* @covers Two_Factor_Core::user_two_factor_options_update
*/
public function test_all_sessions_destroyed_when_enabling_2fa_by_admin() {
$admin_id = self::factory()->user->create(
array(
'role' => 'administrator'
)
);
wp_set_current_user( $admin_id );

// Create an admin session,.
$admin_session_manager = WP_Session_Tokens::get_instance( $admin_id );

$admin_session_manager->create( time() + DAY_IN_SECONDS );
$this->assertCount( 1, $admin_session_manager->get_all(), 'No admin sessions are present first' );

// Create the target user.
$user_id = self::factory()->user->create(
array(
'user_login' => 'username',
'user_pass' => 'password',
)
);

$session_manager = WP_Session_Tokens::get_instance( $user_id );

$this->assertCount( 0, $session_manager->get_all(), 'No user sessions are present first' );

// Generate multiple existing sessions.
$session_manager->create( time() + DAY_IN_SECONDS );
$this->assertCount( 1, $session_manager->get_all(), 'Can fetch active sessions' );

$key = '_nonce_user_two_factor_options';
$nonce = wp_create_nonce( 'user_two_factor_options' );
$_POST[ $key ] = $nonce;
$_REQUEST[ $key ] = $nonce;

$_POST[ Two_Factor_Core::ENABLED_PROVIDERS_USER_META_KEY ] = array( 'Two_Factor_Dummy' => 'Two_Factor_Dummy' );

Two_Factor_Core::user_two_factor_options_update( $user_id );

// Validate that Two-Factor is now enabled.
$this->assertCount( 1, Two_Factor_Core::get_available_providers_for_user( $user_id ) );
$this->assertCount( 1, Two_Factor_Core::get_enabled_providers_for_user( $user_id ) );

// Validate the User has no sessions.
$this->assertCount( 0, $session_manager->get_all(), 'All known authentication sessions have been destroyed' );

// Validate that the Admin still has a session.
$this->assertCount( 1, $admin_session_manager->get_all(), 'No admin sessions are present first' );
}

/**
* @covers Two_Factor_Core::show_password_reset_error
*/
Expand Down

0 comments on commit 2d4b9ec

Please sign in to comment.