Skip to content

Commit 2d4b9ec

Browse files
dd32iandunn
authored andcommitted
Destroy existing sessions when activating 2FA.
1 parent 286aa22 commit 2d4b9ec

File tree

2 files changed

+157
-3
lines changed

2 files changed

+157
-3
lines changed

class-two-factor-core.php

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1543,9 +1543,9 @@ public static function user_two_factor_options_update( $user_id ) {
15431543
return;
15441544
}
15451545

1546-
$providers = self::get_providers();
1547-
1548-
$enabled_providers = $_POST[ self::ENABLED_PROVIDERS_USER_META_KEY ];
1546+
$providers = self::get_providers();
1547+
$enabled_providers = $_POST[ self::ENABLED_PROVIDERS_USER_META_KEY ];
1548+
$existing_providers = self::get_enabled_providers_for_user( $user_id );
15491549

15501550
// Enable only the available providers.
15511551
$enabled_providers = array_intersect( $enabled_providers, array_keys( $providers ) );
@@ -1556,6 +1556,17 @@ public static function user_two_factor_options_update( $user_id ) {
15561556
if ( ! empty( $new_provider ) && in_array( $new_provider, $enabled_providers, true ) ) {
15571557
update_user_meta( $user_id, self::PROVIDER_USER_META_KEY, $new_provider );
15581558
}
1559+
1560+
// Destroy other sessions if we've activated a new provider.
1561+
if ( array_diff( $enabled_providers, $existing_providers ) ) {
1562+
if ( $user_id === get_current_user_id() ) {
1563+
// Keep the current session, destroy others sessions for this user.
1564+
wp_destroy_other_sessions();
1565+
} else {
1566+
// Destroy all sessions for the user.
1567+
WP_Session_Tokens::get_instance( $user_id )->destroy_all();
1568+
}
1569+
}
15591570
}
15601571
}
15611572

tests/class-two-factor-core.php

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,149 @@ public function test_dont_notify_admin_when_filter_disabled() {
800800
reset_phpmailer_instance();
801801
}
802802

803+
/**
804+
* Validate that other sessions are destroyed once Two-Factor is enabled.
805+
*
806+
* @covers Two_Factor_Core::user_two_factor_options_update
807+
*/
808+
public function test_other_sessions_destroyed_when_enabling_2fa() {
809+
$user_id = self::factory()->user->create(
810+
array(
811+
'user_login' => 'username',
812+
'user_pass' => 'password',
813+
)
814+
);
815+
816+
$user = new WP_User( $user_id );
817+
818+
$session_manager = WP_Session_Tokens::get_instance( $user->ID );
819+
820+
$this->assertCount( 0, $session_manager->get_all(), 'No user sessions are present first' );
821+
822+
// Generate multiple existing sessions.
823+
$session_manager->create( time() + HOUR_IN_SECONDS );
824+
$session_manager->create( time() + DAY_IN_SECONDS );
825+
$this->assertCount( 2, $session_manager->get_all(), 'Can fetch active sessions' );
826+
827+
$user_authenticated = wp_signon(
828+
array(
829+
'user_login' => 'username',
830+
'user_password' => 'password',
831+
)
832+
);
833+
$this->assertEquals( $user_authenticated, $user, 'User can authenticate' );
834+
835+
// Enable Two-Factor for the user.
836+
wp_set_current_user( $user->ID );
837+
838+
$key = '_nonce_user_two_factor_options';
839+
$nonce = wp_create_nonce( 'user_two_factor_options' );
840+
$_POST[ $key ] = $nonce;
841+
$_REQUEST[ $key ] = $nonce;
842+
843+
$_POST[ Two_Factor_Core::ENABLED_PROVIDERS_USER_META_KEY ] = array(
844+
'Two_Factor_Dummy' => 'Two_Factor_Dummy'
845+
);
846+
847+
Two_Factor_Core::user_two_factor_options_update( $user->ID );
848+
849+
// Validate that Two-Factor is now enabled.
850+
$this->assertCount( 1, Two_Factor_Core::get_available_providers_for_user( $user->ID ) );
851+
$this->assertCount( 1, Two_Factor_Core::get_enabled_providers_for_user( $user->ID ) );
852+
853+
// Validate that only the current session still exists.
854+
$this->assertCount( 1, $session_manager->get_all(), 'All known authentication sessions have been destroyed' );
855+
856+
// Create another session, activate another provider, verify sessions are still valid.
857+
$session_manager->create( time() + DAY_IN_SECONDS );
858+
$this->assertCount( 2, $session_manager->get_all(), 'Failed to create another session' );
859+
860+
$_POST[ Two_Factor_Core::ENABLED_PROVIDERS_USER_META_KEY ] = array(
861+
'Two_Factor_Dummy' => 'Two_Factor_Dummy',
862+
'Two_Factor_Email' => 'Two_Factor_Email',
863+
);
864+
865+
Two_Factor_Core::user_two_factor_options_update( $user->ID );
866+
867+
// Validate that Two-Factor is now enabled with two providers.
868+
$this->assertCount( 2, Two_Factor_Core::get_available_providers_for_user( $user->ID ) );
869+
$this->assertCount( 2, Two_Factor_Core::get_enabled_providers_for_user( $user->ID ) );
870+
871+
$this->assertCount( 1, $session_manager->get_all(), 'All known authentication sessions have been destroyed' );
872+
873+
// Create another session, disable a provider, verify both sessions still exist.
874+
$session_manager->create( time() + DAY_IN_SECONDS );
875+
$this->assertCount( 2, $session_manager->get_all(), 'Failed to create another session' );
876+
877+
$_POST[ Two_Factor_Core::ENABLED_PROVIDERS_USER_META_KEY ] = array(
878+
'Two_Factor_Dummy' => 'Two_Factor_Dummy',
879+
);
880+
881+
Two_Factor_Core::user_two_factor_options_update( $user->ID );
882+
883+
// Validate that Two-Factor is now enabled with two providers.
884+
$this->assertCount( 1, Two_Factor_Core::get_available_providers_for_user( $user->ID ) );
885+
$this->assertCount( 1, Two_Factor_Core::get_enabled_providers_for_user( $user->ID ) );
886+
887+
$this->assertCount( 2, $session_manager->get_all(), 'All known authentication sessions have been destroyed' );
888+
889+
}
890+
891+
/**
892+
* Validate the administrators sessions are not modified when modifying another user.
893+
*
894+
* @covers Two_Factor_Core::user_two_factor_options_update
895+
*/
896+
public function test_all_sessions_destroyed_when_enabling_2fa_by_admin() {
897+
$admin_id = self::factory()->user->create(
898+
array(
899+
'role' => 'administrator'
900+
)
901+
);
902+
wp_set_current_user( $admin_id );
903+
904+
// Create an admin session,.
905+
$admin_session_manager = WP_Session_Tokens::get_instance( $admin_id );
906+
907+
$admin_session_manager->create( time() + DAY_IN_SECONDS );
908+
$this->assertCount( 1, $admin_session_manager->get_all(), 'No admin sessions are present first' );
909+
910+
// Create the target user.
911+
$user_id = self::factory()->user->create(
912+
array(
913+
'user_login' => 'username',
914+
'user_pass' => 'password',
915+
)
916+
);
917+
918+
$session_manager = WP_Session_Tokens::get_instance( $user_id );
919+
920+
$this->assertCount( 0, $session_manager->get_all(), 'No user sessions are present first' );
921+
922+
// Generate multiple existing sessions.
923+
$session_manager->create( time() + DAY_IN_SECONDS );
924+
$this->assertCount( 1, $session_manager->get_all(), 'Can fetch active sessions' );
925+
926+
$key = '_nonce_user_two_factor_options';
927+
$nonce = wp_create_nonce( 'user_two_factor_options' );
928+
$_POST[ $key ] = $nonce;
929+
$_REQUEST[ $key ] = $nonce;
930+
931+
$_POST[ Two_Factor_Core::ENABLED_PROVIDERS_USER_META_KEY ] = array( 'Two_Factor_Dummy' => 'Two_Factor_Dummy' );
932+
933+
Two_Factor_Core::user_two_factor_options_update( $user_id );
934+
935+
// Validate that Two-Factor is now enabled.
936+
$this->assertCount( 1, Two_Factor_Core::get_available_providers_for_user( $user_id ) );
937+
$this->assertCount( 1, Two_Factor_Core::get_enabled_providers_for_user( $user_id ) );
938+
939+
// Validate the User has no sessions.
940+
$this->assertCount( 0, $session_manager->get_all(), 'All known authentication sessions have been destroyed' );
941+
942+
// Validate that the Admin still has a session.
943+
$this->assertCount( 1, $admin_session_manager->get_all(), 'No admin sessions are present first' );
944+
}
945+
803946
/**
804947
* @covers Two_Factor_Core::show_password_reset_error
805948
*/

0 commit comments

Comments
 (0)