Index: resources/sql/autopatches/20140115.auth.2.expires.sql =================================================================== --- /dev/null +++ resources/sql/autopatches/20140115.auth.2.expires.sql @@ -0,0 +1,8 @@ +ALTER TABLE {$NAMESPACE}_user.phabricator_session + ADD sessionExpires INT UNSIGNED NOT NULL; + +UPDATE {$NAMESPACE}_user.phabricator_session + SET sessionExpires = UNIX_TIMESTAMP() + (60 * 60 * 24 * 30); + +ALTER TABLE {$NAMESPACE}_user.phabricator_session + ADD KEY `key_expires` (sessionExpires); Index: src/__phutil_library_map__.php =================================================================== --- src/__phutil_library_map__.php +++ src/__phutil_library_map__.php @@ -1211,6 +1211,7 @@ 'PhabricatorAuthRegisterController' => 'applications/auth/controller/PhabricatorAuthRegisterController.php', 'PhabricatorAuthSession' => 'applications/auth/storage/PhabricatorAuthSession.php', 'PhabricatorAuthSessionEngine' => 'applications/auth/engine/PhabricatorAuthSessionEngine.php', + 'PhabricatorAuthSessionGarbageCollector' => 'applications/auth/garbagecollector/PhabricatorAuthSessionGarbageCollector.php', 'PhabricatorAuthSessionQuery' => 'applications/auth/query/PhabricatorAuthSessionQuery.php', 'PhabricatorAuthStartController' => 'applications/auth/controller/PhabricatorAuthStartController.php', 'PhabricatorAuthUnlinkController' => 'applications/auth/controller/PhabricatorAuthUnlinkController.php', @@ -3791,6 +3792,7 @@ 1 => 'PhabricatorPolicyInterface', ), 'PhabricatorAuthSessionEngine' => 'Phobject', + 'PhabricatorAuthSessionGarbageCollector' => 'PhabricatorGarbageCollector', 'PhabricatorAuthSessionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorAuthStartController' => 'PhabricatorAuthController', 'PhabricatorAuthUnlinkController' => 'PhabricatorAuthController', Index: src/applications/auth/engine/PhabricatorAuthSessionEngine.php =================================================================== --- src/applications/auth/engine/PhabricatorAuthSessionEngine.php +++ src/applications/auth/engine/PhabricatorAuthSessionEngine.php @@ -5,11 +5,15 @@ public function loadUserForSession($session_type, $session_key) { $session_table = new PhabricatorAuthSession(); $user_table = new PhabricatorUser(); - $conn_r = $session_table->establishConnection('w'); + $conn_r = $session_table->establishConnection('r'); + + // NOTE: We're being clever here because this happens on every page load, + // and by joining we can save a query. $info = queryfx_one( $conn_r, - 'SELECT u.* FROM %T u JOIN %T s ON u.phid = s.userPHID + 'SELECT s.sessionExpires AS _sessionExpires, s.id AS _sessionID, u.* + FROM %T u JOIN %T s ON u.phid = s.userPHID AND s.type LIKE %> AND s.sessionKey = %s', $user_table->getTableName(), $session_table->getTableName(), @@ -20,6 +24,29 @@ return null; } + $expires = $info['_sessionExpires']; + $id = $info['_sessionID']; + unset($info['_sessionExpires']); + unset($info['_sessionID']); + + $ttl = PhabricatorAuthSession::getSessionTypeTTL($session_type); + + // If more than 20% of the time on this session has been used, refresh the + // TTL back up to the full duration. The idea here is that sessions are + // good forever if used regularly, but get GC'd when they fall out of use. + + if (time() + (0.80 * $ttl) > $expires) { + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + $conn_w = $session_table->establishConnection('w'); + queryfx( + $conn_w, + 'UPDATE %T SET sessionExpires = UNIX_TIMESTAMP() + %d WHERE id = %d', + $session_table->getTableName(), + $ttl, + $id); + unset($unguarded); + } + return $user_table->loadFromArray($info); } @@ -84,6 +111,7 @@ // Consume entropy to generate a new session key, forestalling the eventual // heat death of the universe. $session_key = Filesystem::readRandomCharacters(40); + $session_ttl = PhabricatorAuthSession::getSessionTypeTTL($session_type); // Load all the currently active sessions. $sessions = queryfx_all( @@ -119,12 +147,14 @@ // care if we race here or not. queryfx( $conn_w, - 'INSERT IGNORE INTO %T (userPHID, type, sessionKey, sessionStart) - VALUES (%s, %s, %s, 0)', + 'INSERT IGNORE INTO %T + (userPHID, type, sessionKey, sessionStart, sessionExpires) + VALUES (%s, %s, %s, 0, UNIX_TIMESTAMP() + %d)', $session_table->getTableName(), $identity_phid, $establish_type, - PhabricatorHash::digest($session_key)); + PhabricatorHash::digest($session_key), + $session_ttl); break; } } @@ -144,10 +174,12 @@ queryfx( $conn_w, - 'UPDATE %T SET sessionKey = %s, sessionStart = UNIX_TIMESTAMP() + 'UPDATE %T SET sessionKey = %s, sessionStart = UNIX_TIMESTAMP(), + sessionExpires = UNIX_TIMESTAMP() + %d WHERE userPHID = %s AND type = %s AND sessionKey = %s', $session_table->getTableName(), PhabricatorHash::digest($session_key), + $session_ttl, $identity_phid, $establish_type, $expect_key); Index: src/applications/auth/garbagecollector/PhabricatorAuthSessionGarbageCollector.php =================================================================== --- /dev/null +++ src/applications/auth/garbagecollector/PhabricatorAuthSessionGarbageCollector.php @@ -0,0 +1,18 @@ +establishConnection('w'); + + queryfx( + $conn_w, + 'DELETE FROM %T WHERE sessionExpires <= UNIX_TIMESTAMP() LIMIT 100', + $session_table->getTableName()); + + return ($conn_w->getAffectedRows() == 100); + } + +} Index: src/applications/auth/storage/PhabricatorAuthSession.php =================================================================== --- src/applications/auth/storage/PhabricatorAuthSession.php +++ src/applications/auth/storage/PhabricatorAuthSession.php @@ -10,6 +10,7 @@ protected $type; protected $sessionKey; protected $sessionStart; + protected $sessionExpires; private $identityObject = self::ATTACHABLE; @@ -38,6 +39,18 @@ return $this->assertAttached($this->identityObject); } + public static function getSessionTypeTTL($session_type) { + switch ($session_type) { + case self::TYPE_WEB: + return (60 * 60 * 24 * 30); // 30 days + case self::TYPE_CONDUIT: + return (60 * 60 * 24); // 24 hours + default: + throw new Exception(pht('Unknown session type "%s".', $session_type)); + } + } + + /* -( PhabricatorPolicyInterface )----------------------------------------- */ Index: src/applications/settings/panel/PhabricatorSettingsPanelSessions.php =================================================================== --- src/applications/settings/panel/PhabricatorSettingsPanelSessions.php +++ src/applications/settings/panel/PhabricatorSettingsPanelSessions.php @@ -60,6 +60,7 @@ substr($session->getSessionKey(), 0, 12), $session->getType(), phabricator_datetime($session->getSessionStart(), $viewer), + phabricator_datetime($session->getSessionExpires(), $viewer), ); } @@ -72,6 +73,7 @@ pht('Session'), pht('Type'), pht('Created'), + pht('Expires'), )); $table->setColumnClasses( array( @@ -79,6 +81,7 @@ 'n', '', 'right', + 'right', ));