@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Remove session limits and sequencing

Summary:
Ref T4310. Fixes T3720. This change:

- Removes concurrent session limits. Instead, unused sessions are GC'd after a while.
- Collapses all existing "web-1", "web-2", etc., sessions into "web" sessions.
- Dramatically simplifies the code for establishing a session (like omg).

Test Plan: Ran migration, checked Sessions panel and database for sanity. Used existing session. Logged out, logged in. Ran Conduit commands.

Reviewers: btrahan

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T4310, T3720

Differential Revision: https://secure.phabricator.com/D7978

+59 -167
-9
conf/default.conf.php
··· 543 543 544 544 // -- Auth ------------------------------------------------------------------ // 545 545 546 - // Maximum number of simultaneous web sessions each user is permitted to have. 547 - // Setting this to "1" will prevent a user from logging in on more than one 548 - // browser at the same time. 549 - 'auth.sessions.web' => 5, 550 - 551 - // Maximum number of simultaneous Conduit sessions each user is permitted 552 - // to have. 553 - 'auth.sessions.conduit' => 5, 554 - 555 546 // If true, email addresses must be verified (by clicking a link in an 556 547 // email) before a user can login. By default, verification is optional 557 548 // unless 'auth.email-domains' is nonempty (see below).
+26
resources/sql/autopatches/20140115.auth.3.unlimit.php
··· 1 + <?php 2 + 3 + // Prior to this patch, we issued sessions "web-1", "web-2", etc., up to some 4 + // limit. This collapses all the "web-X" sessions into "web" sessions. 5 + 6 + $session_table = new PhabricatorAuthSession(); 7 + $conn_w = $session_table->establishConnection('w'); 8 + 9 + foreach (new LiskMigrationIterator($session_table) as $session) { 10 + $id = $session->getID(); 11 + 12 + echo "Migrating session {$id}...\n"; 13 + $old_type = $session->getType(); 14 + $new_type = preg_replace('/-.*$/', '', $old_type); 15 + 16 + if ($old_type !== $new_type) { 17 + queryfx( 18 + $conn_w, 19 + 'UPDATE %T SET type = %s WHERE id = %d', 20 + $session_table->getTableName(), 21 + $new_type, 22 + $id); 23 + } 24 + } 25 + 26 + echo "Done.\n";
+23 -130
src/applications/auth/engine/PhabricatorAuthSessionEngine.php
··· 14 14 $conn_r, 15 15 'SELECT s.sessionExpires AS _sessionExpires, s.id AS _sessionID, u.* 16 16 FROM %T u JOIN %T s ON u.phid = s.userPHID 17 - AND s.type LIKE %> AND s.sessionKey = %s', 17 + AND s.type = %s AND s.sessionKey = %s', 18 18 $user_table->getTableName(), 19 19 $session_table->getTableName(), 20 - $session_type.'-', 20 + $session_type, 21 21 PhabricatorHash::digest($session_key)); 22 22 23 23 if (!$info) { ··· 72 72 $session_table = new PhabricatorAuthSession(); 73 73 $conn_w = $session_table->establishConnection('w'); 74 74 75 - if (strpos($session_type, '-') !== false) { 76 - throw new Exception("Session type must not contain hyphen ('-')!"); 77 - } 78 - 79 - // We allow multiple sessions of the same type, so when a caller requests 80 - // a new session of type "web", we give them the first available session in 81 - // "web-1", "web-2", ..., "web-N", up to some configurable limit. If none 82 - // of these sessions is available, we overwrite the oldest session and 83 - // reissue a new one in its place. 84 - 85 - $session_limit = 1; 86 - switch ($session_type) { 87 - case PhabricatorAuthSession::TYPE_WEB: 88 - $session_limit = PhabricatorEnv::getEnvConfig('auth.sessions.web'); 89 - break; 90 - case PhabricatorAuthSession::TYPE_CONDUIT: 91 - $session_limit = PhabricatorEnv::getEnvConfig('auth.sessions.conduit'); 92 - break; 93 - default: 94 - throw new Exception("Unknown session type '{$session_type}'!"); 95 - } 96 - 97 - $session_limit = (int)$session_limit; 98 - if ($session_limit <= 0) { 99 - throw new Exception( 100 - "Session limit for '{$session_type}' must be at least 1!"); 101 - } 102 - 103 - // NOTE: Session establishment is sensitive to race conditions, as when 104 - // piping `arc` to `arc`: 105 - // 106 - // arc export ... | arc paste ... 107 - // 108 - // To avoid this, we overwrite an old session only if it hasn't been 109 - // re-established since we read it. 110 - 111 75 // Consume entropy to generate a new session key, forestalling the eventual 112 76 // heat death of the universe. 113 77 $session_key = Filesystem::readRandomCharacters(40); 78 + 79 + // This has a side effect of validating the session type. 114 80 $session_ttl = PhabricatorAuthSession::getSessionTypeTTL($session_type); 115 81 116 - // Load all the currently active sessions. 117 - $sessions = queryfx_all( 118 - $conn_w, 119 - 'SELECT type, sessionKey, sessionStart FROM %T 120 - WHERE userPHID = %s AND type LIKE %>', 121 - $session_table->getTableName(), 122 - $identity_phid, 123 - $session_type.'-'); 124 - $sessions = ipull($sessions, null, 'type'); 125 - $sessions = isort($sessions, 'sessionStart'); 126 - 127 - $existing_sessions = array_keys($sessions); 128 - 129 - // UNGUARDED WRITES: Logging-in users don't have CSRF stuff yet. 82 + // Logging-in users don't have CSRF stuff yet, so we have to unguard this 83 + // write. 130 84 $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 85 + id(new PhabricatorAuthSession()) 86 + ->setUserPHID($identity_phid) 87 + ->setType($session_type) 88 + ->setSessionKey(PhabricatorHash::digest($session_key)) 89 + ->setSessionStart(time()) 90 + ->setSessionExpires(time() + $session_ttl) 91 + ->save(); 131 92 132 - $retries = 0; 133 - while (true) { 134 - 135 - // Choose which 'type' we'll actually establish, i.e. what number we're 136 - // going to append to the basic session type. To do this, just check all 137 - // the numbers sequentially until we find an available session. 138 - $establish_type = null; 139 - for ($ii = 1; $ii <= $session_limit; $ii++) { 140 - $try_type = $session_type.'-'.$ii; 141 - if (!in_array($try_type, $existing_sessions)) { 142 - $establish_type = $try_type; 143 - $expect_key = PhabricatorHash::digest($session_key); 144 - $existing_sessions[] = $try_type; 145 - 146 - // Ensure the row exists so we can issue an update below. We don't 147 - // care if we race here or not. 148 - queryfx( 149 - $conn_w, 150 - 'INSERT IGNORE INTO %T 151 - (userPHID, type, sessionKey, sessionStart, sessionExpires) 152 - VALUES (%s, %s, %s, 0, UNIX_TIMESTAMP() + %d)', 153 - $session_table->getTableName(), 154 - $identity_phid, 155 - $establish_type, 156 - PhabricatorHash::digest($session_key), 157 - $session_ttl); 158 - break; 159 - } 160 - } 161 - 162 - // If we didn't find an available session, choose the oldest session and 163 - // overwrite it. 164 - if (!$establish_type) { 165 - $oldest = reset($sessions); 166 - $establish_type = $oldest['type']; 167 - $expect_key = $oldest['sessionKey']; 168 - } 169 - 170 - // This is so that we'll only overwrite the session if it hasn't been 171 - // refreshed since we read it. If it has, the session key will be 172 - // different and we know we're racing other processes. Whichever one 173 - // won gets the session, we go back and try again. 174 - 175 - queryfx( 176 - $conn_w, 177 - 'UPDATE %T SET sessionKey = %s, sessionStart = UNIX_TIMESTAMP(), 178 - sessionExpires = UNIX_TIMESTAMP() + %d 179 - WHERE userPHID = %s AND type = %s AND sessionKey = %s', 180 - $session_table->getTableName(), 181 - PhabricatorHash::digest($session_key), 182 - $session_ttl, 93 + $log = PhabricatorUserLog::initializeNewLog( 94 + null, 183 95 $identity_phid, 184 - $establish_type, 185 - $expect_key); 186 - 187 - if ($conn_w->getAffectedRows()) { 188 - // The update worked, so the session is valid. 189 - break; 190 - } else { 191 - // We know this just got grabbed, so don't try it again. 192 - unset($sessions[$establish_type]); 193 - } 194 - 195 - if (++$retries > $session_limit) { 196 - throw new Exception("Failed to establish a session!"); 197 - } 198 - } 199 - 200 - $log = PhabricatorUserLog::initializeNewLog( 201 - null, 202 - $identity_phid, 203 - PhabricatorUserLog::ACTION_LOGIN); 204 - $log->setDetails( 205 - array( 206 - 'session_type' => $session_type, 207 - 'session_issued' => $establish_type, 208 - )); 209 - $log->setSession($session_key); 210 - $log->save(); 96 + PhabricatorUserLog::ACTION_LOGIN); 97 + $log->setDetails( 98 + array( 99 + 'session_type' => $session_type, 100 + )); 101 + $log->setSession($session_key); 102 + $log->save(); 103 + unset($unguarded); 211 104 212 105 return $session_key; 213 106 }
+4 -12
src/applications/auth/query/PhabricatorAuthSessionQuery.php
··· 81 81 } 82 82 83 83 if ($this->sessionTypes) { 84 - $clauses = array(); 85 - foreach ($this->sessionTypes as $session_type) { 86 - $clauses[] = qsprintf( 87 - $conn_r, 88 - 'type LIKE %>', 89 - $session_type.'-'); 90 - } 91 - $where[] = '('.implode(') OR (', $clauses).')'; 84 + $where[] = qsprintf( 85 + $conn_r, 86 + 'type IN (%Ls)', 87 + $this->sessionTypes); 92 88 } 93 89 94 90 $where[] = $this->buildPagingClause($conn_r); 95 91 96 92 return $this->formatWhereClause($where); 97 - } 98 - 99 - public function getPagingColumn() { 100 - return 'sessionKey'; 101 93 } 102 94 103 95 public function getQueryApplicationClass() {
+6
src/applications/config/check/PhabricatorSetupCheckExtraConfig.php
··· 146 146 'PhabricatorRemarkupCustomInlineRule or '. 147 147 'PhabricatorRemarkupCustomBlockRule.'); 148 148 149 + $session_reason = pht( 150 + 'Sessions now expire and are garbage collected rather than having an '. 151 + 'arbitrary concurrency limit.'); 152 + 149 153 $ancient_config += array( 150 154 'phid.external-loaders' => 151 155 pht( ··· 172 176 'multiple maps. See T4222.'), 173 177 'metamta.send-immediately' => pht( 174 178 'Mail is now always delivered by the daemons.'), 179 + 'auth.sessions.conduit' => $session_reason, 180 + 'auth.sessions.web' => $session_reason, 175 181 ); 176 182 177 183 return $ancient_config;
-16
src/applications/config/option/PhabricatorAuthenticationConfigOptions.php
··· 13 13 14 14 public function getOptions() { 15 15 return array( 16 - $this->newOption('auth.sessions.web', 'int', 5) 17 - ->setSummary( 18 - pht("Number of web sessions a user can have simultaneously.")) 19 - ->setDescription( 20 - pht( 21 - "Maximum number of simultaneous web sessions each user is ". 22 - "permitted to have. Setting this to '1' will prevent a user from ". 23 - "logging in on more than one browser at the same time.")), 24 - $this->newOption('auth.sessions.conduit', 'int', 5) 25 - ->setSummary( 26 - pht( 27 - "Number of simultaneous Conduit sessions each user is permitted.")) 28 - ->setDescription( 29 - pht( 30 - "Maximum number of simultaneous Conduit sessions each user is ". 31 - "permitted to have.")), 32 16 $this->newOption('auth.require-email-verification', 'bool', false) 33 17 ->setBoolOptions( 34 18 array(