X-Git-Url: https://git.cyclocoop.org/?a=blobdiff_plain;f=includes%2Fsession%2FSessionManager.php;h=1aab12a89512a06a0255cae6b505fdc0c128db77;hb=43420a0506b3115833d4b5d11b9e56579a967cdd;hp=57d56646b435e7f1897c25c49fe0ac05859196b4;hpb=733bafe46f835ba5a8c2db3f101bee71f79efeda;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/session/SessionManager.php b/includes/session/SessionManager.php index 57d56646b4..1aab12a895 100644 --- a/includes/session/SessionManager.php +++ b/includes/session/SessionManager.php @@ -28,8 +28,6 @@ use BagOStuff; use CachedBagOStuff; use Config; use FauxRequest; -use Language; -use Message; use User; use WebRequest; @@ -68,13 +66,13 @@ final class SessionManager implements SessionManagerInterface { private $varyHeaders = null; /** @var SessionBackend[] */ - private $allSessionBackends = array(); + private $allSessionBackends = []; /** @var SessionId[] */ - private $allSessionIds = array(); + private $allSessionIds = []; /** @var string[] */ - private $preventUsers = array(); + private $preventUsers = []; /** * Get the global SessionManager @@ -137,7 +135,7 @@ final class SessionManager implements SessionManagerInterface { * - logger: LoggerInterface to use for logging. Defaults to the 'session' channel. * - store: BagOStuff to store session data in. */ - public function __construct( $options = array() ) { + public function __construct( $options = [] ) { if ( isset( $options['config'] ) ) { $this->config = $options['config']; if ( !$this->config instanceof Config ) { @@ -169,11 +167,10 @@ final class SessionManager implements SessionManagerInterface { $store = $options['store']; } else { $store = \ObjectCache::getInstance( $this->config->get( 'SessionCacheType' ) ); - $store->setLogger( $this->logger ); } $this->store = $store instanceof CachedBagOStuff ? $store : new CachedBagOStuff( $store ); - register_shutdown_function( array( $this, 'shutdown' ) ); + register_shutdown_function( [ $this, 'shutdown' ] ); } public function setLogger( LoggerInterface $logger ) { @@ -205,7 +202,7 @@ final class SessionManager implements SessionManagerInterface { // of "no such ID" $key = wfMemcKey( 'MWSession', $id ); if ( is_array( $this->store->get( $key ) ) ) { - $info = new SessionInfo( SessionInfo::MIN_PRIORITY, array( 'id' => $id, 'idIsSafe' => true ) ); + $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [ 'id' => $id, 'idIsSafe' => true ] ); if ( $this->loadSessionInfoFromStore( $info, $request ) ) { $session = $this->getSessionFromInfo( $info, $request ); } @@ -216,8 +213,11 @@ final class SessionManager implements SessionManagerInterface { try { $session = $this->getEmptySessionInternal( $request, $id ); } catch ( \Exception $ex ) { - $this->logger->error( __METHOD__ . ': failed to create empty session: ' . - $ex->getMessage() ); + $this->logger->error( 'Failed to create empty session: {exception}', + [ + 'method' => __METHOD__, + 'exception' => $ex, + ] ); $session = null; } } @@ -250,7 +250,7 @@ final class SessionManager implements SessionManagerInterface { $request = new FauxRequest; } - $infos = array(); + $infos = []; foreach ( $this->getProviders() as $provider ) { $info = $provider->newSessionInfo( $id ); if ( !$info ) { @@ -279,7 +279,7 @@ final class SessionManager implements SessionManagerInterface { if ( $compare === 0 ) { $infos[] = $info; } else { - $infos = array( $info ); + $infos = [ $info ]; } } @@ -297,11 +297,11 @@ final class SessionManager implements SessionManagerInterface { public function getVaryHeaders() { if ( $this->varyHeaders === null ) { - $headers = array(); + $headers = []; foreach ( $this->getProviders() as $provider ) { foreach ( $provider->getVaryHeaders() as $header => $options ) { if ( !isset( $headers[$header] ) ) { - $headers[$header] = array(); + $headers[$header] = []; } if ( is_array( $options ) ) { $headers[$header] = array_unique( array_merge( $headers[$header], $options ) ); @@ -315,7 +315,7 @@ final class SessionManager implements SessionManagerInterface { public function getVaryCookies() { if ( $this->varyCookies === null ) { - $cookies = array(); + $cookies = []; foreach ( $this->getProviders() as $provider ) { $cookies = array_merge( $cookies, $provider->getVaryCookies() ); } @@ -356,19 +356,21 @@ final class SessionManager implements SessionManagerInterface { // Try the local user from the slave DB $localId = User::idFromName( $user->getName() ); + $flags = 0; // Fetch the user ID from the master, so that we don't try to create the user // when they already exist, due to replication lag // @codeCoverageIgnoreStart if ( !$localId && wfGetLB()->getReaderIndex() != 0 ) { $localId = User::idFromName( $user->getName(), User::READ_LATEST ); + $flags = User::READ_LATEST; } // @codeCoverageIgnoreEnd if ( $localId ) { // User exists after all. $user->setId( $localId ); - $user->loadFromId(); + $user->loadFromId( $flags ); return false; } @@ -428,7 +430,7 @@ final class SessionManager implements SessionManagerInterface { // Give other extensions a chance to stop auto creation. $user->loadDefaults( $userName ); $abortMessage = ''; - if ( !\Hooks::run( 'AbortAutoAccount', array( $user, &$abortMessage ) ) ) { + if ( !\Hooks::run( 'AbortAutoAccount', [ $user, &$abortMessage ] ) ) { // In this case we have no way to return the message to the user, // but we can log it. $logger->debug( __METHOD__ . ": denied by hook: $abortMessage" ); @@ -462,22 +464,41 @@ final class SessionManager implements SessionManagerInterface { // Checks passed, create the user... $from = isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : 'CLI'; - $logger->info( __METHOD__ . ": creating new user ($userName) - from: $from" ); + $logger->info( __METHOD__ . ': creating new user ({username}) - from: {url}', + [ + 'username' => $userName, + 'url' => $from, + ] ); try { // Insert the user into the local DB master $status = $user->addToDatabase(); if ( !$status->isOK() ) { // @codeCoverageIgnoreStart - $logger->error( __METHOD__ . ': failed with message ' . $status->getWikiText() ); - $user->setId( 0 ); - $user->loadFromId(); + // double-check for a race condition (T70012) + $id = User::idFromName( $user->getName(), User::READ_LATEST ); + if ( $id ) { + $logger->info( __METHOD__ . ': tried to autocreate existing user', + [ + 'username' => $userName, + ] ); + } else { + $logger->error( __METHOD__ . ': failed with message ' . $status->getWikiText(), + [ + 'username' => $userName, + ] ); + } + $user->setId( $id ); + $user->loadFromId( User::READ_LATEST ); return false; // @codeCoverageIgnoreEnd } } catch ( \Exception $ex ) { // @codeCoverageIgnoreStart - $logger->error( __METHOD__ . ': failed with exception ' . $ex->getMessage() ); + $logger->error( __METHOD__ . ': failed with exception {exception}', [ + 'exception' => $ex, + 'username' => $userName, + ] ); // Do not keep throwing errors for a while $cache->set( $backoffKey, 1, 600 ); // Bubble up error; which should normally trigger DB rollbacks @@ -494,8 +515,8 @@ final class SessionManager implements SessionManagerInterface { } # Notify hooks (e.g. Newuserlog) - \Hooks::run( 'AuthPluginAutoCreate', array( $user ) ); - \Hooks::run( 'LocalUserCreated', array( $user, true ) ); + \Hooks::run( 'AuthPluginAutoCreate', [ $user ] ); + \Hooks::run( 'LocalUserCreated', [ $user, true ] ); $user->saveSettings(); @@ -520,13 +541,6 @@ final class SessionManager implements SessionManagerInterface { public function preventSessionsForUser( $username ) { $this->preventUsers[$username] = true; - // Reset the user's token to kill existing sessions - $user = User::newFromName( $username ); - if ( $user && $user->getToken( false ) ) { - $user->setToken(); - $user->saveSettings(); - } - // Instruct the session providers to kill any other sessions too. foreach ( $this->getProviders() as $provider ) { $provider->preventSessionsForUser( $username ); @@ -549,7 +563,7 @@ final class SessionManager implements SessionManagerInterface { */ protected function getProviders() { if ( $this->sessionProviders === null ) { - $this->sessionProviders = array(); + $this->sessionProviders = []; foreach ( $this->config->get( 'SessionProviders' ) as $spec ) { $provider = \ObjectFactory::getObjectFromSpec( $spec ); $provider->setLogger( $this->logger ); @@ -604,7 +618,7 @@ final class SessionManager implements SessionManagerInterface { */ private function getSessionInfoForRequest( WebRequest $request ) { // Call all providers to fetch "the" session - $infos = array(); + $infos = []; foreach ( $this->getProviders() as $provider ) { $info = $provider->provideSessionInfo( $request ); if ( !$info ) { @@ -622,7 +636,7 @@ final class SessionManager implements SessionManagerInterface { // successfully loaded, and then all the ones after it with the same // priority. usort( $infos, 'MediaWiki\\Session\\SessionInfo::compare' ); - $retInfos = array(); + $retInfos = []; while ( $infos ) { $info = array_pop( $infos ); if ( $this->loadSessionInfoFromStore( $info, $request ) ) { @@ -664,12 +678,14 @@ final class SessionManager implements SessionManagerInterface { $key = wfMemcKey( 'MWSession', $info->getId() ); $blob = $this->store->get( $key ); - $newParams = array(); + $newParams = []; if ( $blob !== false ) { // Sanity check: blob must be an array, if it's saved at all if ( !is_array( $blob ) ) { - $this->logger->warning( "Session $info: Bad data" ); + $this->logger->warning( 'Session "{session}": Bad data', [ + 'session' => $info, + ] ); $this->store->delete( $key ); return false; } @@ -678,7 +694,9 @@ final class SessionManager implements SessionManagerInterface { if ( !isset( $blob['data'] ) || !is_array( $blob['data'] ) || !isset( $blob['metadata'] ) || !is_array( $blob['metadata'] ) ) { - $this->logger->warning( "Session $info: Bad data structure" ); + $this->logger->warning( 'Session "{session}": Bad data structure', [ + 'session' => $info, + ] ); $this->store->delete( $key ); return false; } @@ -693,7 +711,9 @@ final class SessionManager implements SessionManagerInterface { !array_key_exists( 'userToken', $metadata ) || !array_key_exists( 'provider', $metadata ) ) { - $this->logger->warning( "Session $info: Bad metadata" ); + $this->logger->warning( 'Session "{session}": Bad metadata', [ + 'session' => $info, + ] ); $this->store->delete( $key ); return false; } @@ -703,13 +723,21 @@ final class SessionManager implements SessionManagerInterface { if ( $provider === null ) { $newParams['provider'] = $provider = $this->getProvider( $metadata['provider'] ); if ( !$provider ) { - $this->logger->warning( "Session $info: Unknown provider, " . $metadata['provider'] ); + $this->logger->warning( + 'Session "{session}": Unknown provider ' . $metadata['provider'], + [ + 'session' => $info, + ] + ); $this->store->delete( $key ); return false; } } elseif ( $metadata['provider'] !== (string)$provider ) { - $this->logger->warning( "Session $info: Wrong provider, " . - $metadata['provider'] . ' !== ' . $provider ); + $this->logger->warning( 'Session "{session}": Wrong provider ' . + $metadata['provider'] . ' !== ' . $provider, + [ + 'session' => $info, + ] ); return false; } @@ -726,8 +754,14 @@ final class SessionManager implements SessionManagerInterface { if ( $newProviderMetadata !== $providerMetadata ) { $newParams['metadata'] = $newProviderMetadata; } - } catch ( \UnexpectedValueException $ex ) { - $this->logger->warning( "Session $info: Metadata merge failed: " . $ex->getMessage() ); + } catch ( MetadataMergeException $ex ) { + $this->logger->warning( + 'Session "{session}": Metadata merge failed: {exception}', + [ + 'session' => $info, + 'exception' => $ex, + ] + $ex->getContext() + ); return false; } } @@ -746,7 +780,10 @@ final class SessionManager implements SessionManagerInterface { $userInfo = UserInfo::newAnonymous(); } } catch ( \InvalidArgumentException $ex ) { - $this->logger->error( "Session $info: " . $ex->getMessage() ); + $this->logger->error( 'Session "{session}": {exception}', [ + 'session' => $info, + 'exception' => $ex, + ] ); return false; } $newParams['userInfo'] = $userInfo; @@ -755,8 +792,13 @@ final class SessionManager implements SessionManagerInterface { // is no saved ID and the names match. if ( $metadata['userId'] ) { if ( $metadata['userId'] !== $userInfo->getId() ) { - $this->logger->warning( "Session $info: User ID mismatch, " . - $metadata['userId'] . ' !== ' . $userInfo->getId() ); + $this->logger->warning( + 'Session "{session}": User ID mismatch, {uid_a} !== {uid_b}', + [ + 'session' => $info, + 'uid_a' => $metadata['userId'], + 'uid_b' => $userInfo->getId(), + ] ); return false; } @@ -764,24 +806,35 @@ final class SessionManager implements SessionManagerInterface { if ( $metadata['userName'] !== null && $userInfo->getName() !== $metadata['userName'] ) { - $this->logger->warning( "Session $info: User ID matched but name didn't (rename?), " . - $metadata['userName'] . ' !== ' . $userInfo->getName() ); + $this->logger->warning( + 'Session "{session}": User ID matched but name didn\'t (rename?), {uname_a} !== {uname_b}', + [ + 'session' => $info, + 'uname_a' => $metadata['userName'], + 'uname_b' => $userInfo->getName(), + ] ); return false; } } elseif ( $metadata['userName'] !== null ) { // Shouldn't happen, but just in case if ( $metadata['userName'] !== $userInfo->getName() ) { - $this->logger->warning( "Session $info: User name mismatch, " . - $metadata['userName'] . ' !== ' . $userInfo->getName() ); + $this->logger->warning( + 'Session "{session}": User name mismatch, {uname_a} !== {uname_b}', + [ + 'session' => $info, + 'uname_a' => $metadata['userName'], + 'uname_b' => $userInfo->getName(), + ] ); return false; } } elseif ( !$userInfo->isAnon() ) { // Metadata specifies an anonymous user, but the passed-in // user isn't anonymous. $this->logger->warning( - "Session $info: Metadata has an anonymous user, " . - 'but a non-anon user was provided' - ); + 'Session "{session}": Metadata has an anonymous user, but a non-anon user was provided', + [ + 'session' => $info, + ] ); return false; } } @@ -790,7 +843,9 @@ final class SessionManager implements SessionManagerInterface { if ( $metadata['userToken'] !== null && $userInfo->getToken() !== $metadata['userToken'] ) { - $this->logger->warning( "Session $info: User token mismatch" ); + $this->logger->warning( 'Session "{session}": User token mismatch', [ + 'session' => $info, + ] ); return false; } if ( !$userInfo->isVerified() ) { @@ -813,7 +868,11 @@ final class SessionManager implements SessionManagerInterface { } else { // No metadata, so we can't load the provider if one wasn't given. if ( $info->getProvider() === null ) { - $this->logger->warning( "Session $info: Null provider and no metadata" ); + $this->logger->warning( + 'Session "{session}": Null provider and no metadata', + [ + 'session' => $info, + ] ); return false; } @@ -823,14 +882,18 @@ final class SessionManager implements SessionManagerInterface { $newParams['userInfo'] = UserInfo::newAnonymous(); } else { $this->logger->info( - "Session $info: No user provided and provider cannot set user" - ); + 'Session "{session}": No user provided and provider cannot set user', + [ + 'session' => $info, + ] ); return false; } } elseif ( !$info->getUserInfo()->isVerified() ) { $this->logger->warning( - "Session $info: Unverified user provided and no metadata to auth it" - ); + 'Session "{session}": Unverified user provided and no metadata to auth it', + [ + 'session' => $info, + ] ); return false; } @@ -856,10 +919,10 @@ final class SessionManager implements SessionManagerInterface { return false; } if ( $providerMetadata !== $info->getProviderMetadata() ) { - $info = new SessionInfo( $info->getPriority(), array( + $info = new SessionInfo( $info->getPriority(), [ 'metadata' => $providerMetadata, 'copyFrom' => $info, - ) ); + ] ); } // Give hooks a chance to abort. Combined with the SessionMetadata @@ -868,9 +931,11 @@ final class SessionManager implements SessionManagerInterface { $reason = 'Hook aborted'; if ( !\Hooks::run( 'SessionCheckInfo', - array( &$reason, $info, $request, $metadata, $data ) + [ &$reason, $info, $request, $metadata, $data ] ) ) { - $this->logger->warning( "Session $info: $reason" ); + $this->logger->warning( 'Session "{session}": ' . $reason, [ + 'session' => $info, + ] ); return false; } @@ -886,6 +951,15 @@ final class SessionManager implements SessionManagerInterface { * @return Session */ public function getSessionFromInfo( SessionInfo $info, WebRequest $request ) { + if ( defined( 'MW_NO_SESSION' ) ) { + if ( MW_NO_SESSION === 'warn' ) { + // Undocumented safety case for converting existing entry points + $this->logger->error( 'Sessions are supposed to be disabled for this entry point' ); + } else { + throw new \BadMethodCallException( 'Sessions are disabled for this entry point' ); + } + } + $id = $info->getId(); if ( !isset( $this->allSessionBackends[$id] ) ) {