@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.

Make partial sessions expire after 30 minutes, and do not extend them

Summary:
Depends on D19904. Ref T13226. Ref T13222. Currently, partial sessions (where you've provided a primary auth factor like a password, but not yet provided MFA) work like normal sessions: they're good for 30 days and extend indefinitely under regular use.

This behavior is convenient for full sessions, but normal users don't ever spend 30 minutes answering MFA, so there's no real reason to do it for partial sessions. If we add login alerts in the future, limiting partial sessions to a short lifetime will make them more useful, since an attacker can't get one partial session and keep extending it forever while waiting for an opportunity to get past your MFA.

Test Plan:
- Did a partial login (to the MFA prompt), checked database, saw a ~29 minute partial session.
- Did a full login, saw session extend to ~30 days.

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13226, T13222

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

+46 -23
+40 -21
src/applications/auth/engine/PhabricatorAuthSessionEngine.php
··· 213 213 214 214 $session = id(new PhabricatorAuthSession())->loadFromArray($session_dict); 215 215 216 - $ttl = PhabricatorAuthSession::getSessionTypeTTL($session_type); 217 - 218 - // If more than 20% of the time on this session has been used, refresh the 219 - // TTL back up to the full duration. The idea here is that sessions are 220 - // good forever if used regularly, but get GC'd when they fall out of use. 221 - 222 - // NOTE: If we begin rotating session keys when extending sessions, the 223 - // CSRF code needs to be updated so CSRF tokens survive session rotation. 224 - 225 - if (time() + (0.80 * $ttl) > $session->getSessionExpires()) { 226 - $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 227 - $conn_w = $session_table->establishConnection('w'); 228 - queryfx( 229 - $conn_w, 230 - 'UPDATE %T SET sessionExpires = UNIX_TIMESTAMP() + %d WHERE id = %d', 231 - $session->getTableName(), 232 - $ttl, 233 - $session->getID()); 234 - unset($unguarded); 235 - } 216 + $this->extendSession($session); 236 217 237 218 // TODO: Remove this, see T13225. 238 219 if ($is_weak) { ··· 284 265 $conn_w = $session_table->establishConnection('w'); 285 266 286 267 // This has a side effect of validating the session type. 287 - $session_ttl = PhabricatorAuthSession::getSessionTypeTTL($session_type); 268 + $session_ttl = PhabricatorAuthSession::getSessionTypeTTL( 269 + $session_type, 270 + $partial); 288 271 289 272 $digest_key = PhabricatorAuthSession::newSessionDigest( 290 273 new PhutilOpaqueEnvelope($session_key)); ··· 1085 1068 $extension->willServeRequestForUser($user); 1086 1069 } 1087 1070 } 1071 + 1072 + private function extendSession(PhabricatorAuthSession $session) { 1073 + $is_partial = $session->getIsPartial(); 1074 + 1075 + // Don't extend partial sessions. You have a relatively short window to 1076 + // upgrade into a full session, and your session expires otherwise. 1077 + if ($is_partial) { 1078 + return; 1079 + } 1080 + 1081 + $session_type = $session->getType(); 1082 + 1083 + $ttl = PhabricatorAuthSession::getSessionTypeTTL( 1084 + $session_type, 1085 + $session->getIsPartial()); 1086 + 1087 + // If more than 20% of the time on this session has been used, refresh the 1088 + // TTL back up to the full duration. The idea here is that sessions are 1089 + // good forever if used regularly, but get GC'd when they fall out of use. 1090 + 1091 + $now = PhabricatorTime::getNow(); 1092 + if ($now + (0.80 * $ttl) <= $session->getSessionExpires()) { 1093 + return; 1094 + } 1095 + 1096 + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 1097 + queryfx( 1098 + $session->establishConnection('w'), 1099 + 'UPDATE %R SET sessionExpires = UNIX_TIMESTAMP() + %d 1100 + WHERE id = %d', 1101 + $session, 1102 + $ttl, 1103 + $session->getID()); 1104 + unset($unguarded); 1105 + } 1106 + 1088 1107 1089 1108 }
+6 -2
src/applications/auth/storage/PhabricatorAuthSession.php
··· 72 72 return $this->assertAttached($this->identityObject); 73 73 } 74 74 75 - public static function getSessionTypeTTL($session_type) { 75 + public static function getSessionTypeTTL($session_type, $is_partial) { 76 76 switch ($session_type) { 77 77 case self::TYPE_WEB: 78 - return phutil_units('30 days in seconds'); 78 + if ($is_partial) { 79 + return phutil_units('30 minutes in seconds'); 80 + } else { 81 + return phutil_units('30 days in seconds'); 82 + } 79 83 case self::TYPE_CONDUIT: 80 84 return phutil_units('24 hours in seconds'); 81 85 default: