diff --git a/src/applications/auth/controller/PhabricatorAuthUnlinkController.php b/src/applications/auth/controller/PhabricatorAuthUnlinkController.php --- a/src/applications/auth/controller/PhabricatorAuthUnlinkController.php +++ b/src/applications/auth/controller/PhabricatorAuthUnlinkController.php @@ -57,6 +57,11 @@ if ($request->isDialogFormPost()) { $account->delete(); + + id(new PhabricatorAuthSessionEngine())->terminateLoginSessions( + $viewer, + $request->getCookie(PhabricatorCookies::COOKIE_SESSION)); + return id(new AphrontRedirectResponse())->setURI($this->getDoneURI()); } @@ -130,7 +135,11 @@ $dialog = id(new AphrontDialogView()) ->setUser($this->getRequest()->getUser()) ->setTitle($title) - ->appendChild($body) + ->appendParagraph($body) + ->appendParagraph( + pht( + 'Note: Unlinking an authentication provider will terminate any '. + 'other active login sessions.')) ->addSubmitButton(pht('Unlink Account')) ->addCancelButton($this->getDoneURI()); diff --git a/src/applications/auth/engine/PhabricatorAuthSessionEngine.php b/src/applications/auth/engine/PhabricatorAuthSessionEngine.php --- a/src/applications/auth/engine/PhabricatorAuthSessionEngine.php +++ b/src/applications/auth/engine/PhabricatorAuthSessionEngine.php @@ -250,6 +250,43 @@ } + /** + * Terminate all of a user's login sessions. + * + * This is used when users change passwords, linked accounts, or add + * multifactor authentication. + * + * @param PhabricatorUser User whose sessions should be terminated. + * @param string|null Optionally, one session to keep. Normally, the current + * login session. + * + * @return void + */ + public function terminateLoginSessions( + PhabricatorUser $user, + $except_session = null) { + + $sessions = id(new PhabricatorAuthSessionQuery()) + ->setViewer($user) + ->withIdentityPHIDs(array($user->getPHID())) + ->execute(); + + if ($except_session !== null) { + $except_session = PhabricatorHash::digest($except_session); + } + + foreach ($sessions as $key => $session) { + if ($except_session !== null) { + if ($except_session == $session->getSessionKey()) { + continue; + } + } + + $session->delete(); + } + } + + /* -( High Security )------------------------------------------------------ */ diff --git a/src/applications/settings/panel/PhabricatorSettingsPanelMultiFactor.php b/src/applications/settings/panel/PhabricatorSettingsPanelMultiFactor.php --- a/src/applications/settings/panel/PhabricatorSettingsPanelMultiFactor.php +++ b/src/applications/settings/panel/PhabricatorSettingsPanelMultiFactor.php @@ -198,6 +198,13 @@ $user->updateMultiFactorEnrollment(); + // Terminate other sessions so they must log in and survive the + // multi-factor auth check. + + id(new PhabricatorAuthSessionEngine())->terminateLoginSessions( + $user, + $request->getCookie(PhabricatorCookies::COOKIE_SESSION)); + return id(new AphrontRedirectResponse()) ->setURI($this->getPanelURI('?id='.$config->getID())); } diff --git a/src/applications/settings/panel/PhabricatorSettingsPanelPassword.php b/src/applications/settings/panel/PhabricatorSettingsPanelPassword.php --- a/src/applications/settings/panel/PhabricatorSettingsPanelPassword.php +++ b/src/applications/settings/panel/PhabricatorSettingsPanelPassword.php @@ -116,6 +116,10 @@ $next = $this->getPanelURI('?saved=true'); } + id(new PhabricatorAuthSessionEngine())->terminateLoginSessions( + $user, + $request->getCookie(PhabricatorCookies::COOKIE_SESSION)); + return id(new AphrontRedirectResponse())->setURI($next); } } @@ -177,6 +181,11 @@ ->setLabel(pht('Best Available Algorithm')) ->setValue(PhabricatorPasswordHasher::getBestAlgorithmName())); + $form->appendRemarkupInstructions( + pht( + 'NOTE: Changing your password will terminate any other outstanding '. + 'login sessions.')); + $form_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Change Password')) ->setFormSaved($request->getStr('saved')) @@ -187,4 +196,6 @@ $form_box, ); } + + }