Index: src/__phutil_library_map__.php =================================================================== --- src/__phutil_library_map__.php +++ src/__phutil_library_map__.php @@ -1202,6 +1202,8 @@ 'PhabricatorAuthProviderPassword' => 'applications/auth/provider/PhabricatorAuthProviderPassword.php', 'PhabricatorAuthProviderPersona' => 'applications/auth/provider/PhabricatorAuthProviderPersona.php', 'PhabricatorAuthRegisterController' => 'applications/auth/controller/PhabricatorAuthRegisterController.php', + 'PhabricatorAuthSession' => 'applications/auth/storage/PhabricatorAuthSession.php', + 'PhabricatorAuthSessionQuery' => 'applications/auth/query/PhabricatorAuthSessionQuery.php', 'PhabricatorAuthStartController' => 'applications/auth/controller/PhabricatorAuthStartController.php', 'PhabricatorAuthUnlinkController' => 'applications/auth/controller/PhabricatorAuthUnlinkController.php', 'PhabricatorAuthValidateController' => 'applications/auth/controller/PhabricatorAuthValidateController.php', @@ -1915,6 +1917,7 @@ 'PhabricatorSettingsPanelPassword' => 'applications/settings/panel/PhabricatorSettingsPanelPassword.php', 'PhabricatorSettingsPanelSSHKeys' => 'applications/settings/panel/PhabricatorSettingsPanelSSHKeys.php', 'PhabricatorSettingsPanelSearchPreferences' => 'applications/settings/panel/PhabricatorSettingsPanelSearchPreferences.php', + 'PhabricatorSettingsPanelSessions' => 'applications/settings/panel/PhabricatorSettingsPanelSessions.php', 'PhabricatorSetupCheck' => 'applications/config/check/PhabricatorSetupCheck.php', 'PhabricatorSetupCheckAPC' => 'applications/config/check/PhabricatorSetupCheckAPC.php', 'PhabricatorSetupCheckAuth' => 'applications/config/check/PhabricatorSetupCheckAuth.php', @@ -3760,6 +3763,12 @@ 'PhabricatorAuthProviderPassword' => 'PhabricatorAuthProvider', 'PhabricatorAuthProviderPersona' => 'PhabricatorAuthProvider', 'PhabricatorAuthRegisterController' => 'PhabricatorAuthController', + 'PhabricatorAuthSession' => + array( + 0 => 'PhabricatorAuthDAO', + 1 => 'PhabricatorPolicyInterface', + ), + 'PhabricatorAuthSessionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorAuthStartController' => 'PhabricatorAuthController', 'PhabricatorAuthUnlinkController' => 'PhabricatorAuthController', 'PhabricatorAuthValidateController' => 'PhabricatorAuthController', @@ -4548,6 +4557,7 @@ 'PhabricatorSettingsPanelPassword' => 'PhabricatorSettingsPanel', 'PhabricatorSettingsPanelSSHKeys' => 'PhabricatorSettingsPanel', 'PhabricatorSettingsPanelSearchPreferences' => 'PhabricatorSettingsPanel', + 'PhabricatorSettingsPanelSessions' => 'PhabricatorSettingsPanel', 'PhabricatorSetupCheckAPC' => 'PhabricatorSetupCheck', 'PhabricatorSetupCheckAuth' => 'PhabricatorSetupCheck', 'PhabricatorSetupCheckBaseURI' => 'PhabricatorSetupCheck', Index: src/applications/auth/query/PhabricatorAuthSessionQuery.php =================================================================== --- /dev/null +++ src/applications/auth/query/PhabricatorAuthSessionQuery.php @@ -0,0 +1,73 @@ +identityPHIDs = $identity_phids; + return $this; + } + + protected function loadPage() { + $table = new PhabricatorAuthSession(); + $conn_r = $table->establishConnection('r'); + + $data = queryfx_all( + $conn_r, + 'SELECT * FROM %T %Q %Q %Q', + $table->getTableName(), + $this->buildWhereClause($conn_r), + $this->buildOrderClause($conn_r), + $this->buildLimitClause($conn_r)); + + return $table->loadAllFromArray($data); + } + + protected function willFilterPage(array $sessions) { + $identity_phids = mpull($sessions, 'getUserPHID'); + + $identity_objects = id(new PhabricatorObjectQuery()) + ->setViewer($this->getViewer()) + ->setParentQuery($this) + ->withPHIDs($identity_phids) + ->execute(); + $identity_objects = mpull($identity_objects, null, 'getPHID'); + + foreach ($sessions as $key => $session) { + $identity_object = idx($identity_objects, $session->getUserPHID()); + if (!$identity_object) { + unset($sessions[$key]); + } else { + $session->attachIdentityObject($identity_object); + } + } + + return $sessions; + } + + protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { + $where = array(); + + if ($this->identityPHIDs) { + $where[] = qsprintf( + $conn_r, + 'userPHID IN (%Ls)', + $this->identityPHIDs); + } + + $where[] = $this->buildPagingClause($conn_r); + + return $this->formatWhereClause($where); + } + + public function getPagingColumn() { + return 'sessionKey'; + } + + public function getQueryApplicationClass() { + return 'PhabricatorApplicationAuth'; + } + +} Index: src/applications/auth/storage/PhabricatorAuthSession.php =================================================================== --- /dev/null +++ src/applications/auth/storage/PhabricatorAuthSession.php @@ -0,0 +1,72 @@ + self::IDS_MANUAL, + self::CONFIG_TIMESTAMPS => false, + ) + parent::getConfiguration(); + } + + public function getApplicationName() { + // This table predates the "Auth" application, and really all applications. + return 'user'; + } + + public function getTableName() { + // This is a very old table with a nonstandard name. + return PhabricatorUser::SESSION_TABLE; + } + + public function attachIdentityObject($identity_object) { + $this->identityObject = $identity_object; + return $this; + } + + public function getIdentityObject() { + return $this->assertAttached($this->identityObject); + } + + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + ); + } + + public function getPolicy($capability) { + return PhabricatorPolicies::POLICY_NOONE; + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + if (!$viewer->getPHID()) { + return false; + } + + $object = $this->getIdentityObject(); + if ($object instanceof PhabricatorUser) { + return ($object->getPHID() == $viewer->getPHID()); + } else if ($object instanceof PhabricatorExternalAccount) { + return ($object->getUserPHID() == $viewer->getPHID()); + } + + return false; + } + + public function describeAutomaticCapability($capability) { + return pht('A session is visible only to its owner.'); + } + +} Index: src/applications/base/controller/PhabricatorController.php =================================================================== --- src/applications/base/controller/PhabricatorController.php +++ src/applications/base/controller/PhabricatorController.php @@ -44,7 +44,7 @@ 'SELECT u.* FROM %T u JOIN %T s ON u.phid = s.userPHID AND s.type LIKE %> AND s.sessionKey = %s', $user->getTableName(), - 'phabricator_session', + PhabricatorUser::SESSION_TABLE, 'web-', PhabricatorHash::digest($phsid)); if ($info) { Index: src/applications/settings/panel/PhabricatorSettingsPanelSessions.php =================================================================== --- /dev/null +++ src/applications/settings/panel/PhabricatorSettingsPanelSessions.php @@ -0,0 +1,95 @@ +getUser(); + + $accounts = id(new PhabricatorExternalAccountQuery()) + ->setViewer($viewer) + ->withUserPHIDs(array($viewer->getPHID())) + ->execute(); + + $identity_phids = mpull($accounts, 'getPHID'); + $identity_phids[] = $viewer->getPHID(); + + $sessions = id(new PhabricatorAuthSessionQuery()) + ->setViewer($viewer) + ->withIdentityPHIDs($identity_phids) + ->execute(); + + $handles = id(new PhabricatorHandleQuery()) + ->setViewer($viewer) + ->withPHIDs($identity_phids) + ->execute(); + + // TODO: Once this has a real ID column, use that instead. + $sessions = msort($sessions, 'getSessionStart'); + $sessions = array_reverse($sessions); + + $current_key = PhabricatorHash::digest($request->getCookie('phsid')); + + $rows = array(); + $rowc = array(); + foreach ($sessions as $session) { + if ($session->getSessionKey() == $current_key) { + $rowc[] = 'highlighted'; + } else { + $rowc[] = null; + } + + $rows[] = array( + $handles[$session->getUserPHID()]->renderLink(), + substr($session->getSessionKey(), 0, 12), + $session->getType(), + phabricator_datetime($session->getSessionStart(), $viewer), + ); + } + + $table = new AphrontTableView($rows); + $table->setNoDataString(pht("You don't have any active sessions.")); + $table->setRowClasses($rowc); + $table->setHeaders( + array( + pht('Identity'), + pht('Session'), + pht('Type'), + pht('Created'), + )); + $table->setColumnClasses( + array( + 'wide', + 'n', + '', + 'right', + )); + + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Active Login Sessions')); + + $panel = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->appendChild($table); + + return $panel; + } + +}