diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index d9265cdeca..8a8ffdca87 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1226,6 +1226,7 @@ phutil_register_library_map(array( 'PhabricatorAuthSessionGarbageCollector' => 'applications/auth/garbagecollector/PhabricatorAuthSessionGarbageCollector.php', 'PhabricatorAuthSessionQuery' => 'applications/auth/query/PhabricatorAuthSessionQuery.php', 'PhabricatorAuthStartController' => 'applications/auth/controller/PhabricatorAuthStartController.php', + 'PhabricatorAuthTerminateSessionController' => 'applications/auth/controller/PhabricatorAuthTerminateSessionController.php', 'PhabricatorAuthUnlinkController' => 'applications/auth/controller/PhabricatorAuthUnlinkController.php', 'PhabricatorAuthValidateController' => 'applications/auth/controller/PhabricatorAuthValidateController.php', 'PhabricatorAuthenticationConfigOptions' => 'applications/config/option/PhabricatorAuthenticationConfigOptions.php', @@ -3910,6 +3911,7 @@ phutil_register_library_map(array( 'PhabricatorAuthSessionGarbageCollector' => 'PhabricatorGarbageCollector', 'PhabricatorAuthSessionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorAuthStartController' => 'PhabricatorAuthController', + 'PhabricatorAuthTerminateSessionController' => 'PhabricatorAuthController', 'PhabricatorAuthUnlinkController' => 'PhabricatorAuthController', 'PhabricatorAuthValidateController' => 'PhabricatorAuthController', 'PhabricatorAuthenticationConfigOptions' => 'PhabricatorApplicationConfigOptions', diff --git a/src/applications/auth/application/PhabricatorApplicationAuth.php b/src/applications/auth/application/PhabricatorApplicationAuth.php index f8023efd5c..28d7209fda 100644 --- a/src/applications/auth/application/PhabricatorApplicationAuth.php +++ b/src/applications/auth/application/PhabricatorApplicationAuth.php @@ -86,6 +86,8 @@ final class PhabricatorApplicationAuth extends PhabricatorApplication { => 'PhabricatorAuthLinkController', 'confirmlink/(?P[^/]+)/' => 'PhabricatorAuthConfirmLinkController', + 'session/terminate/(?P[^/]+)/' + => 'PhabricatorAuthTerminateSessionController', ), '/oauth/(?P\w+)/login/' diff --git a/src/applications/auth/controller/PhabricatorAuthTerminateSessionController.php b/src/applications/auth/controller/PhabricatorAuthTerminateSessionController.php new file mode 100644 index 0000000000..7461660d3d --- /dev/null +++ b/src/applications/auth/controller/PhabricatorAuthTerminateSessionController.php @@ -0,0 +1,83 @@ +id = $data['id']; + } + + public function processRequest() { + $request = $this->getRequest(); + $viewer = $request->getUser(); + + $is_all = ($this->id === 'all'); + + $query = id(new PhabricatorAuthSessionQuery()) + ->setViewer($viewer) + ->withIdentityPHIDs(array($viewer->getPHID())); + if (!$is_all) { + $query->withIDs(array($this->id)); + } + + $current_key = PhabricatorHash::digest( + $request->getCookie(PhabricatorCookies::COOKIE_SESSION)); + + $sessions = $query->execute(); + foreach ($sessions as $key => $session) { + if ($session->getSessionKey() == $current_key) { + // Don't terminate the current login session. + unset($sessions[$key]); + } + } + + $panel_uri = '/settings/panel/sessions/'; + + if (!$sessions) { + $dialog = id(new AphrontDialogView()) + ->setUser($viewer) + ->setTitle(pht('No Matching Sessions')) + ->appendParagraph( + pht('There are no matching sessions to terminate.')) + ->appendParagraph( + pht( + '(You can not terminate your current login session. To '. + 'terminate it, log out.)')) + ->addCancelButton($panel_uri); + + return id(new AphrontDialogResponse())->setDialog($dialog); + } + + if ($request->isDialogFormPost()) { + foreach ($sessions as $session) { + $session->delete(); + } + return id(new AphrontRedirectResponse())->setURI($panel_uri); + } + + if ($is_all) { + $title = pht('Terminate Sessions?'); + $body = pht( + 'Really terminate all sessions? (Your current login session will '. + 'not be terminated.)'); + } else { + $title = pht('Terminate Session?'); + $body = pht( + 'Really terminate session %s?', + phutil_tag('strong', array(), substr($session->getSessionKey(), 0, 6))); + } + + $dialog = id(new AphrontDialogView()) + ->setUser($viewer) + ->setTitle($title) + ->appendParagraph($body) + ->addSubmitButton(pht('Terminate')) + ->addCancelButton($panel_uri); + + return id(new AphrontDialogResponse())->setDialog($dialog); + } + + +} diff --git a/src/applications/auth/query/PhabricatorAuthSessionQuery.php b/src/applications/auth/query/PhabricatorAuthSessionQuery.php index d1e4dca895..e451e1a3a3 100644 --- a/src/applications/auth/query/PhabricatorAuthSessionQuery.php +++ b/src/applications/auth/query/PhabricatorAuthSessionQuery.php @@ -3,6 +3,7 @@ final class PhabricatorAuthSessionQuery extends PhabricatorCursorPagedPolicyAwareQuery { + private $ids; private $identityPHIDs; private $sessionKeys; private $sessionTypes; @@ -22,6 +23,11 @@ final class PhabricatorAuthSessionQuery return $this; } + public function withIDs(array $ids) { + $this->ids = $ids; + return $this; + } + protected function loadPage() { $table = new PhabricatorAuthSession(); $conn_r = $table->establishConnection('r'); @@ -62,6 +68,13 @@ final class PhabricatorAuthSessionQuery protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); + if ($this->ids) { + $where[] = qsprintf( + $conn_r, + 'id IN (%Ld)', + $this->ids); + } + if ($this->identityPHIDs) { $where[] = qsprintf( $conn_r, diff --git a/src/applications/settings/panel/PhabricatorSettingsPanelSessions.php b/src/applications/settings/panel/PhabricatorSettingsPanelSessions.php index 2081ec2825..4c98efc27f 100644 --- a/src/applications/settings/panel/PhabricatorSettingsPanelSessions.php +++ b/src/applications/settings/panel/PhabricatorSettingsPanelSessions.php @@ -48,16 +48,31 @@ final class PhabricatorSettingsPanelSessions foreach ($sessions as $session) { if ($session->getSessionKey() == $current_key) { $rowc[] = 'highlighted'; + $button = phutil_tag( + 'a', + array( + 'class' => 'small grey button disabled', + ), + pht('Current')); } else { $rowc[] = null; + $button = javelin_tag( + 'a', + array( + 'href' => '/auth/session/terminate/'.$session->getID().'/', + 'class' => 'small grey button', + 'sigil' => 'workflow', + ), + pht('Terminate')); } $rows[] = array( $handles[$session->getUserPHID()]->renderLink(), - substr($session->getSessionKey(), 0, 12), + substr($session->getSessionKey(), 0, 6), $session->getType(), phabricator_datetime($session->getSessionStart(), $viewer), - phabricator_datetime($session->getSessionExpires(), $viewer), + phabricator_date($session->getSessionExpires(), $viewer), + $button, ); } @@ -71,6 +86,7 @@ final class PhabricatorSettingsPanelSessions pht('Type'), pht('Created'), pht('Expires'), + pht(''), )); $table->setColumnClasses( array( @@ -79,11 +95,23 @@ final class PhabricatorSettingsPanelSessions '', 'right', 'right', + 'action', )); + $terminate_icon = id(new PHUIIconView()) + ->setSpriteSheet(PHUIIconView::SPRITE_ICONS) + ->setSpriteIcon('warning'); + $terminate_button = id(new PHUIButtonView()) + ->setText(pht('Terminate All Sessions')) + ->setHref('/auth/session/terminate/all/') + ->setTag('a') + ->setWorkflow(true) + ->setIcon($terminate_icon); + $header = id(new PHUIHeaderView()) - ->setHeader(pht('Active Login Sessions')); + ->setHeader(pht('Active Login Sessions')) + ->addActionLink($terminate_button); $panel = id(new PHUIObjectBoxView()) ->setHeader($header)