Prevent "Failed to create..." warnings when session loading fails
[lhc/web/wiklou.git] / includes / session / SessionManager.php
index 6e4f99c..81f8243 100644 (file)
@@ -66,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
@@ -135,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 ) {
@@ -167,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 ) {
@@ -203,7 +202,8 @@ 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 ) );
+                       $create = false;
+                       $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [ 'id' => $id, 'idIsSafe' => true ] );
                        if ( $this->loadSessionInfoFromStore( $info, $request ) ) {
                                $session = $this->getSessionFromInfo( $info, $request );
                        }
@@ -215,10 +215,10 @@ final class SessionManager implements SessionManagerInterface {
                                $session = $this->getEmptySessionInternal( $request, $id );
                        } catch ( \Exception $ex ) {
                                $this->logger->error( 'Failed to create empty session: {exception}',
-                                       array(
+                                       [
                                                'method' => __METHOD__,
                                                'exception' => $ex,
-                               ) );
+                               ] );
                                $session = null;
                        }
                }
@@ -251,7 +251,7 @@ final class SessionManager implements SessionManagerInterface {
                        $request = new FauxRequest;
                }
 
-               $infos = array();
+               $infos = [];
                foreach ( $this->getProviders() as $provider ) {
                        $info = $provider->newSessionInfo( $id );
                        if ( !$info ) {
@@ -280,7 +280,7 @@ final class SessionManager implements SessionManagerInterface {
                        if ( $compare === 0 ) {
                                $infos[] = $info;
                        } else {
-                               $infos = array( $info );
+                               $infos = [ $info ];
                        }
                }
 
@@ -297,12 +297,17 @@ final class SessionManager implements SessionManagerInterface {
        }
 
        public function getVaryHeaders() {
+               // @codeCoverageIgnoreStart
+               if ( defined( 'MW_NO_SESSION' ) && MW_NO_SESSION !== 'warn' ) {
+                       return [];
+               }
+               // @codeCoverageIgnoreEnd
                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,8 +320,13 @@ final class SessionManager implements SessionManagerInterface {
        }
 
        public function getVaryCookies() {
+               // @codeCoverageIgnoreStart
+               if ( defined( 'MW_NO_SESSION' ) && MW_NO_SESSION !== 'warn' ) {
+                       return [];
+               }
+               // @codeCoverageIgnoreEnd
                if ( $this->varyCookies === null ) {
-                       $cookies = array();
+                       $cookies = [];
                        foreach ( $this->getProviders() as $provider ) {
                                $cookies = array_merge( $cookies, $provider->getVaryCookies() );
                        }
@@ -357,19 +367,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;
                }
 
@@ -429,7 +441,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" );
@@ -464,31 +476,40 @@ 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: {url}',
-                       array(
+                       [
                                '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(),
-                                       array(
-                                               'username' => $userName,
-                               ) );
-                               $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 {exception}', array(
+                       $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
@@ -497,16 +518,18 @@ final class SessionManager implements SessionManagerInterface {
                }
 
                # Notify AuthPlugin
+               // @codeCoverageIgnoreStart
                $tmpUser = $user;
                $wgAuth->initUser( $tmpUser, true );
                if ( $tmpUser !== $user ) {
                        $logger->warning( __METHOD__ . ': ' .
                                get_class( $wgAuth ) . '::initUser() replaced the user object' );
                }
+               // @codeCoverageIgnoreEnd
 
                # 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();
 
@@ -553,7 +576,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 );
@@ -608,7 +631,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 ) {
@@ -626,7 +649,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 ) ) {
@@ -641,8 +664,14 @@ final class SessionManager implements SessionManagerInterface {
                                                // This is going to error out below, but we want to
                                                // provide a complete list.
                                                $retInfos[] = $info;
+                                       } else {
+                                               // Session load failed, so unpersist it from this request
+                                               $info->getProvider()->unpersistSession( $request );
                                        }
                                }
+                       } else {
+                               // Session load failed, so unpersist it from this request
+                               $info->getProvider()->unpersistSession( $request );
                        }
                }
 
@@ -668,14 +697,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 "{session}": Bad data', array(
+                               $this->logger->warning( 'Session "{session}": Bad data', [
                                        'session' => $info,
-                               ) );
+                               ] );
                                $this->store->delete( $key );
                                return false;
                        }
@@ -684,9 +713,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 "{session}": Bad data structure', array(
+                               $this->logger->warning( 'Session "{session}": Bad data structure', [
                                        'session' => $info,
-                               ) );
+                               ] );
                                $this->store->delete( $key );
                                return false;
                        }
@@ -701,9 +730,9 @@ final class SessionManager implements SessionManagerInterface {
                                !array_key_exists( 'userToken', $metadata ) ||
                                !array_key_exists( 'provider', $metadata )
                        ) {
-                               $this->logger->warning( 'Session "{session}": Bad metadata', array(
+                               $this->logger->warning( 'Session "{session}": Bad metadata', [
                                        'session' => $info,
-                               ) );
+                               ] );
                                $this->store->delete( $key );
                                return false;
                        }
@@ -715,9 +744,9 @@ final class SessionManager implements SessionManagerInterface {
                                if ( !$provider ) {
                                        $this->logger->warning(
                                                'Session "{session}": Unknown provider ' . $metadata['provider'],
-                                               array(
+                                               [
                                                        'session' => $info,
-                                               )
+                                               ]
                                        );
                                        $this->store->delete( $key );
                                        return false;
@@ -725,9 +754,9 @@ final class SessionManager implements SessionManagerInterface {
                        } elseif ( $metadata['provider'] !== (string)$provider ) {
                                $this->logger->warning( 'Session "{session}": Wrong provider ' .
                                        $metadata['provider'] . ' !== ' . $provider,
-                                       array(
+                                       [
                                                'session' => $info,
-                               ) );
+                               ] );
                                return false;
                        }
 
@@ -747,10 +776,10 @@ final class SessionManager implements SessionManagerInterface {
                                        } catch ( MetadataMergeException $ex ) {
                                                $this->logger->warning(
                                                        'Session "{session}": Metadata merge failed: {exception}',
-                                                       array(
+                                                       [
                                                                'session' => $info,
                                                                'exception' => $ex,
-                                                       ) + $ex->getContext()
+                                                       ] + $ex->getContext()
                                                );
                                                return false;
                                        }
@@ -770,10 +799,10 @@ final class SessionManager implements SessionManagerInterface {
                                                $userInfo = UserInfo::newAnonymous();
                                        }
                                } catch ( \InvalidArgumentException $ex ) {
-                                       $this->logger->error( 'Session "{session}": {exception}', array(
+                                       $this->logger->error( 'Session "{session}": {exception}', [
                                                'session' => $info,
                                                'exception' => $ex,
-                                       ) );
+                                       ] );
                                        return false;
                                }
                                $newParams['userInfo'] = $userInfo;
@@ -784,11 +813,11 @@ final class SessionManager implements SessionManagerInterface {
                                        if ( $metadata['userId'] !== $userInfo->getId() ) {
                                                $this->logger->warning(
                                                        'Session "{session}": User ID mismatch, {uid_a} !== {uid_b}',
-                                                       array(
+                                                       [
                                                                'session' => $info,
                                                                'uid_a' => $metadata['userId'],
                                                                'uid_b' => $userInfo->getId(),
-                                               ) );
+                                               ] );
                                                return false;
                                        }
 
@@ -798,11 +827,11 @@ final class SessionManager implements SessionManagerInterface {
                                        ) {
                                                $this->logger->warning(
                                                        'Session "{session}": User ID matched but name didn\'t (rename?), {uname_a} !== {uname_b}',
-                                                       array(
+                                                       [
                                                                'session' => $info,
                                                                'uname_a' => $metadata['userName'],
                                                                'uname_b' => $userInfo->getName(),
-                                               ) );
+                                               ] );
                                                return false;
                                        }
 
@@ -810,11 +839,11 @@ final class SessionManager implements SessionManagerInterface {
                                        if ( $metadata['userName'] !== $userInfo->getName() ) {
                                                $this->logger->warning(
                                                        'Session "{session}": User name mismatch, {uname_a} !== {uname_b}',
-                                                       array(
+                                                       [
                                                                'session' => $info,
                                                                'uname_a' => $metadata['userName'],
                                                                'uname_b' => $userInfo->getName(),
-                                               ) );
+                                               ] );
                                                return false;
                                        }
                                } elseif ( !$userInfo->isAnon() ) {
@@ -822,9 +851,9 @@ final class SessionManager implements SessionManagerInterface {
                                        // user isn't anonymous.
                                        $this->logger->warning(
                                                'Session "{session}": Metadata has an anonymous user, but a non-anon user was provided',
-                                               array(
+                                               [
                                                        'session' => $info,
-                                       ) );
+                                       ] );
                                        return false;
                                }
                        }
@@ -833,9 +862,9 @@ final class SessionManager implements SessionManagerInterface {
                        if ( $metadata['userToken'] !== null &&
                                $userInfo->getToken() !== $metadata['userToken']
                        ) {
-                               $this->logger->warning( 'Session "{session}": User token mismatch', array(
+                               $this->logger->warning( 'Session "{session}": User token mismatch', [
                                        'session' => $info,
-                               ) );
+                               ] );
                                return false;
                        }
                        if ( !$userInfo->isVerified() ) {
@@ -860,9 +889,9 @@ final class SessionManager implements SessionManagerInterface {
                        if ( $info->getProvider() === null ) {
                                $this->logger->warning(
                                        'Session "{session}": Null provider and no metadata',
-                                       array(
+                                       [
                                                'session' => $info,
-                               ) );
+                               ] );
                                return false;
                        }
 
@@ -873,17 +902,17 @@ final class SessionManager implements SessionManagerInterface {
                                } else {
                                        $this->logger->info(
                                                'Session "{session}": No user provided and provider cannot set user',
-                                               array(
+                                               [
                                                        'session' => $info,
-                                       ) );
+                                       ] );
                                        return false;
                                }
                        } elseif ( !$info->getUserInfo()->isVerified() ) {
                                $this->logger->warning(
                                        'Session "{session}": Unverified user provided and no metadata to auth it',
-                                       array(
+                                       [
                                                'session' => $info,
-                               ) );
+                               ] );
                                return false;
                        }
 
@@ -909,10 +938,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
@@ -921,11 +950,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 "{session}": ' . $reason, array(
+                       $this->logger->warning( 'Session "{session}": ' . $reason, [
                                'session' => $info,
-                       ) );
+                       ] );
                        return false;
                }
 
@@ -941,6 +970,17 @@ final class SessionManager implements SessionManagerInterface {
         * @return Session
         */
        public function getSessionFromInfo( SessionInfo $info, WebRequest $request ) {
+               // @codeCoverageIgnoreStart
+               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' );
+                       }
+               }
+               // @codeCoverageIgnoreEnd
+
                $id = $info->getId();
 
                if ( !isset( $this->allSessionBackends[$id] ) ) {