/** * Load and verify the session info against the store * * @param SessionInfo &$info Will likely be replaced with an updated SessionInfo instance * @param WebRequest $request * @return bool Whether the session info matches the stored data (if any) */ private function loadSessionInfoFromStore(SessionInfo &$info, WebRequest $request) { $key = wfMemcKey('MWSession', $info->getId()); $blob = $this->store->get($key); $newParams = array(); 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->store->delete($key); return false; } // Sanity check: blob has data and metadata arrays if (!isset($blob['data']) || !is_array($blob['data']) || !isset($blob['metadata']) || !is_array($blob['metadata'])) { $this->logger->warning("Session {$info}: Bad data structure"); $this->store->delete($key); return false; } $data = $blob['data']; $metadata = $blob['metadata']; // Sanity check: metadata must be an array and must contain certain // keys, if it's saved at all if (!array_key_exists('userId', $metadata) || !array_key_exists('userName', $metadata) || !array_key_exists('userToken', $metadata) || !array_key_exists('provider', $metadata)) { $this->logger->warning("Session {$info}: Bad metadata"); $this->store->delete($key); return false; } // First, load the provider from metadata, or validate it against the metadata. $provider = $info->getProvider(); if ($provider === null) { $newParams['provider'] = $provider = $this->getProvider($metadata['provider']); if (!$provider) { $this->logger->warning("Session {$info}: Unknown provider, " . $metadata['provider']); $this->store->delete($key); return false; } } elseif ($metadata['provider'] !== (string) $provider) { $this->logger->warning("Session {$info}: Wrong provider, " . $metadata['provider'] . ' !== ' . $provider); return false; } // Load provider metadata from metadata, or validate it against the metadata $providerMetadata = $info->getProviderMetadata(); if (isset($metadata['providerMetadata'])) { if ($providerMetadata === null) { $newParams['metadata'] = $metadata['providerMetadata']; } else { try { $newProviderMetadata = $provider->mergeMetadata($metadata['providerMetadata'], $providerMetadata); if ($newProviderMetadata !== $providerMetadata) { $newParams['metadata'] = $newProviderMetadata; } } catch (\UnexpectedValueException $ex) { $this->logger->warning("Session {$info}: Metadata merge failed: " . $ex->getMessage()); return false; } } } // Next, load the user from metadata, or validate it against the metadata. $userInfo = $info->getUserInfo(); if (!$userInfo) { // For loading, id is preferred to name. try { if ($metadata['userId']) { $userInfo = UserInfo::newFromId($metadata['userId']); } elseif ($metadata['userName'] !== null) { // Shouldn't happen, but just in case $userInfo = UserInfo::newFromName($metadata['userName']); } else { $userInfo = UserInfo::newAnonymous(); } } catch (\InvalidArgumentException $ex) { $this->logger->error("Session {$info}: " . $ex->getMessage()); return false; } $newParams['userInfo'] = $userInfo; } else { // User validation passes if user ID matches, or if there // 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()); return false; } // If the user was renamed, probably best to fail here. 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()); 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()); 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'); return false; } } // And if we have a token in the metadata, it must match the loaded/provided user. if ($metadata['userToken'] !== null && $userInfo->getToken() !== $metadata['userToken']) { $this->logger->warning("Session {$info}: User token mismatch"); return false; } if (!$userInfo->isVerified()) { $newParams['userInfo'] = $userInfo->verified(); } if (!empty($metadata['remember']) && !$info->wasRemembered()) { $newParams['remembered'] = true; } if (!empty($metadata['forceHTTPS']) && !$info->forceHTTPS()) { $newParams['forceHTTPS'] = true; } if (!$info->isIdSafe()) { $newParams['idIsSafe'] = true; } } 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"); return false; } // If no user was provided and no metadata, it must be anon. if (!$info->getUserInfo()) { if ($info->getProvider()->canChangeUser()) { $newParams['userInfo'] = UserInfo::newAnonymous(); } else { $this->logger->info("Session {$info}: No user provided and provider cannot set user"); return false; } } elseif (!$info->getUserInfo()->isVerified()) { $this->logger->warning("Session {$info}: Unverified user provided and no metadata to auth it"); return false; } $data = false; $metadata = false; if (!$info->getProvider()->persistsSessionId() && !$info->isIdSafe()) { // The ID doesn't come from the user, so it should be safe // (and if not, nothing we can do about it anyway) $newParams['idIsSafe'] = true; } } // Construct the replacement SessionInfo, if necessary if ($newParams) { $newParams['copyFrom'] = $info; $info = new SessionInfo($info->getPriority(), $newParams); } // Allow the provider to check the loaded SessionInfo $providerMetadata = $info->getProviderMetadata(); if (!$info->getProvider()->refreshSessionInfo($info, $request, $providerMetadata)) { return false; } if ($providerMetadata !== $info->getProviderMetadata()) { $info = new SessionInfo($info->getPriority(), array('metadata' => $providerMetadata, 'copyFrom' => $info)); } // Give hooks a chance to abort. Combined with the SessionMetadata // hook, this can allow for tying a session to an IP address or the // like. $reason = 'Hook aborted'; if (!\Hooks::run('SessionCheckInfo', array(&$reason, $info, $request, $metadata, $data))) { $this->logger->warning("Session {$info}: {$reason}"); return false; } return true; }
public function testBasics() { $anonInfo = UserInfo::newAnonymous(); $userInfo = UserInfo::newFromName('UTSysop', true); $unverifiedUserInfo = UserInfo::newFromName('UTSysop', false); try { new SessionInfo(SessionInfo::MIN_PRIORITY - 1, array()); $this->fail('Expected exception not thrown', 'priority < min'); } catch (\InvalidArgumentException $ex) { $this->assertSame('Invalid priority', $ex->getMessage(), 'priority < min'); } try { new SessionInfo(SessionInfo::MAX_PRIORITY + 1, array()); $this->fail('Expected exception not thrown', 'priority > max'); } catch (\InvalidArgumentException $ex) { $this->assertSame('Invalid priority', $ex->getMessage(), 'priority > max'); } try { new SessionInfo(SessionInfo::MIN_PRIORITY, array('id' => 'ABC?')); $this->fail('Expected exception not thrown', 'bad session ID'); } catch (\InvalidArgumentException $ex) { $this->assertSame('Invalid session ID', $ex->getMessage(), 'bad session ID'); } try { new SessionInfo(SessionInfo::MIN_PRIORITY, array('userInfo' => new \stdClass())); $this->fail('Expected exception not thrown', 'bad userInfo'); } catch (\InvalidArgumentException $ex) { $this->assertSame('Invalid userInfo', $ex->getMessage(), 'bad userInfo'); } try { new SessionInfo(SessionInfo::MIN_PRIORITY, array()); $this->fail('Expected exception not thrown', 'no provider, no id'); } catch (\InvalidArgumentException $ex) { $this->assertSame('Must supply an ID when no provider is given', $ex->getMessage(), 'no provider, no id'); } try { new SessionInfo(SessionInfo::MIN_PRIORITY, array('copyFrom' => new \stdClass())); $this->fail('Expected exception not thrown', 'bad copyFrom'); } catch (\InvalidArgumentException $ex) { $this->assertSame('Invalid copyFrom', $ex->getMessage(), 'bad copyFrom'); } $manager = new SessionManager(); $provider = $this->getMockBuilder('MediaWiki\\Session\\SessionProvider')->setMethods(array('persistsSessionId', 'canChangeUser', '__toString'))->getMockForAbstractClass(); $provider->setManager($manager); $provider->expects($this->any())->method('persistsSessionId')->will($this->returnValue(true)); $provider->expects($this->any())->method('canChangeUser')->will($this->returnValue(true)); $provider->expects($this->any())->method('__toString')->will($this->returnValue('Mock')); $provider2 = $this->getMockBuilder('MediaWiki\\Session\\SessionProvider')->setMethods(array('persistsSessionId', 'canChangeUser', '__toString'))->getMockForAbstractClass(); $provider2->setManager($manager); $provider2->expects($this->any())->method('persistsSessionId')->will($this->returnValue(true)); $provider2->expects($this->any())->method('canChangeUser')->will($this->returnValue(true)); $provider2->expects($this->any())->method('__toString')->will($this->returnValue('Mock2')); try { new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $provider, 'userInfo' => $anonInfo, 'metadata' => 'foo')); $this->fail('Expected exception not thrown', 'bad metadata'); } catch (\InvalidArgumentException $ex) { $this->assertSame('Invalid metadata', $ex->getMessage(), 'bad metadata'); } $info = new SessionInfo(SessionInfo::MIN_PRIORITY + 5, array('provider' => $provider, 'userInfo' => $anonInfo)); $this->assertSame($provider, $info->getProvider()); $this->assertNotNull($info->getId()); $this->assertSame(SessionInfo::MIN_PRIORITY + 5, $info->getPriority()); $this->assertSame($anonInfo, $info->getUserInfo()); $this->assertTrue($info->isIdSafe()); $this->assertFalse($info->wasPersisted()); $this->assertFalse($info->wasRemembered()); $this->assertFalse($info->forceHTTPS()); $this->assertNull($info->getProviderMetadata()); $info = new SessionInfo(SessionInfo::MIN_PRIORITY + 5, array('provider' => $provider, 'userInfo' => $unverifiedUserInfo, 'metadata' => array('Foo'))); $this->assertSame($provider, $info->getProvider()); $this->assertNotNull($info->getId()); $this->assertSame(SessionInfo::MIN_PRIORITY + 5, $info->getPriority()); $this->assertSame($unverifiedUserInfo, $info->getUserInfo()); $this->assertTrue($info->isIdSafe()); $this->assertFalse($info->wasPersisted()); $this->assertFalse($info->wasRemembered()); $this->assertFalse($info->forceHTTPS()); $this->assertSame(array('Foo'), $info->getProviderMetadata()); $info = new SessionInfo(SessionInfo::MIN_PRIORITY + 5, array('provider' => $provider, 'userInfo' => $userInfo)); $this->assertSame($provider, $info->getProvider()); $this->assertNotNull($info->getId()); $this->assertSame(SessionInfo::MIN_PRIORITY + 5, $info->getPriority()); $this->assertSame($userInfo, $info->getUserInfo()); $this->assertTrue($info->isIdSafe()); $this->assertFalse($info->wasPersisted()); $this->assertTrue($info->wasRemembered()); $this->assertFalse($info->forceHTTPS()); $this->assertNull($info->getProviderMetadata()); $id = $manager->generateSessionId(); $info = new SessionInfo(SessionInfo::MIN_PRIORITY + 5, array('provider' => $provider, 'id' => $id, 'persisted' => true, 'userInfo' => $anonInfo)); $this->assertSame($provider, $info->getProvider()); $this->assertSame($id, $info->getId()); $this->assertSame(SessionInfo::MIN_PRIORITY + 5, $info->getPriority()); $this->assertSame($anonInfo, $info->getUserInfo()); $this->assertFalse($info->isIdSafe()); $this->assertTrue($info->wasPersisted()); $this->assertFalse($info->wasRemembered()); $this->assertFalse($info->forceHTTPS()); $this->assertNull($info->getProviderMetadata()); $info = new SessionInfo(SessionInfo::MIN_PRIORITY + 5, array('provider' => $provider, 'id' => $id, 'userInfo' => $userInfo)); $this->assertSame($provider, $info->getProvider()); $this->assertSame($id, $info->getId()); $this->assertSame(SessionInfo::MIN_PRIORITY + 5, $info->getPriority()); $this->assertSame($userInfo, $info->getUserInfo()); $this->assertFalse($info->isIdSafe()); $this->assertFalse($info->wasPersisted()); $this->assertTrue($info->wasRemembered()); $this->assertFalse($info->forceHTTPS()); $this->assertNull($info->getProviderMetadata()); $info = new SessionInfo(SessionInfo::MIN_PRIORITY + 5, array('id' => $id, 'persisted' => true, 'userInfo' => $userInfo, 'metadata' => array('Foo'))); $this->assertSame($id, $info->getId()); $this->assertSame(SessionInfo::MIN_PRIORITY + 5, $info->getPriority()); $this->assertSame($userInfo, $info->getUserInfo()); $this->assertFalse($info->isIdSafe()); $this->assertTrue($info->wasPersisted()); $this->assertFalse($info->wasRemembered()); $this->assertFalse($info->forceHTTPS()); $this->assertNull($info->getProviderMetadata()); $info = new SessionInfo(SessionInfo::MIN_PRIORITY + 5, array('id' => $id, 'remembered' => true, 'userInfo' => $userInfo)); $this->assertFalse($info->wasRemembered(), 'no provider'); $info = new SessionInfo(SessionInfo::MIN_PRIORITY + 5, array('provider' => $provider, 'id' => $id, 'remembered' => true)); $this->assertFalse($info->wasRemembered(), 'no user'); $info = new SessionInfo(SessionInfo::MIN_PRIORITY + 5, array('provider' => $provider, 'id' => $id, 'remembered' => true, 'userInfo' => $anonInfo)); $this->assertFalse($info->wasRemembered(), 'anonymous user'); $info = new SessionInfo(SessionInfo::MIN_PRIORITY + 5, array('provider' => $provider, 'id' => $id, 'remembered' => true, 'userInfo' => $unverifiedUserInfo)); $this->assertFalse($info->wasRemembered(), 'unverified user'); $info = new SessionInfo(SessionInfo::MIN_PRIORITY + 5, array('provider' => $provider, 'id' => $id, 'remembered' => false, 'userInfo' => $userInfo)); $this->assertFalse($info->wasRemembered(), 'specific override'); $info = new SessionInfo(SessionInfo::MIN_PRIORITY + 5, array('id' => $id, 'idIsSafe' => true)); $this->assertSame($id, $info->getId()); $this->assertSame(SessionInfo::MIN_PRIORITY + 5, $info->getPriority()); $this->assertTrue($info->isIdSafe()); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('id' => $id, 'forceHTTPS' => 1)); $this->assertTrue($info->forceHTTPS()); $fromInfo = new SessionInfo(SessionInfo::MIN_PRIORITY, array('id' => $id . 'A', 'provider' => $provider, 'userInfo' => $userInfo, 'idIsSafe' => true, 'persisted' => true, 'remembered' => true, 'forceHTTPS' => true, 'metadata' => array('foo!'))); $info = new SessionInfo(SessionInfo::MIN_PRIORITY + 4, array('copyFrom' => $fromInfo)); $this->assertSame($id . 'A', $info->getId()); $this->assertSame(SessionInfo::MIN_PRIORITY + 4, $info->getPriority()); $this->assertSame($provider, $info->getProvider()); $this->assertSame($userInfo, $info->getUserInfo()); $this->assertTrue($info->isIdSafe()); $this->assertTrue($info->wasPersisted()); $this->assertTrue($info->wasRemembered()); $this->assertTrue($info->forceHTTPS()); $this->assertSame(array('foo!'), $info->getProviderMetadata()); $info = new SessionInfo(SessionInfo::MIN_PRIORITY + 4, array('id' => $id . 'X', 'provider' => $provider2, 'userInfo' => $unverifiedUserInfo, 'idIsSafe' => false, 'persisted' => false, 'remembered' => false, 'forceHTTPS' => false, 'metadata' => null, 'copyFrom' => $fromInfo)); $this->assertSame($id . 'X', $info->getId()); $this->assertSame(SessionInfo::MIN_PRIORITY + 4, $info->getPriority()); $this->assertSame($provider2, $info->getProvider()); $this->assertSame($unverifiedUserInfo, $info->getUserInfo()); $this->assertFalse($info->isIdSafe()); $this->assertFalse($info->wasPersisted()); $this->assertFalse($info->wasRemembered()); $this->assertFalse($info->forceHTTPS()); $this->assertNull($info->getProviderMetadata()); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('id' => $id)); $this->assertSame('[' . SessionInfo::MIN_PRIORITY . "]null<null>{$id}", (string) $info, 'toString'); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $provider, 'id' => $id, 'persisted' => true, 'userInfo' => $userInfo)); $this->assertSame('[' . SessionInfo::MIN_PRIORITY . "]Mock<+:{$userInfo->getId()}:UTSysop>{$id}", (string) $info, 'toString'); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $provider, 'id' => $id, 'persisted' => true, 'userInfo' => $unverifiedUserInfo)); $this->assertSame('[' . SessionInfo::MIN_PRIORITY . "]Mock<-:{$userInfo->getId()}:UTSysop>{$id}", (string) $info, 'toString'); }
/** * @param SessionId $id Session ID object * @param SessionInfo $info Session info to populate from * @param CachedBagOStuff $store Backend data store * @param LoggerInterface $logger * @param int $lifetime Session data lifetime in seconds */ public function __construct(SessionId $id, SessionInfo $info, CachedBagOStuff $store, LoggerInterface $logger, $lifetime) { $phpSessionHandling = \RequestContext::getMain()->getConfig()->get('PHPSessionHandling'); $this->usePhpSessionHandling = $phpSessionHandling !== 'disable'; if ($info->getUserInfo() && !$info->getUserInfo()->isVerified()) { throw new \InvalidArgumentException("Refusing to create session for unverified user {$info->getUserInfo()}"); } if ($info->getProvider() === null) { throw new \InvalidArgumentException('Cannot create session without a provider'); } if ($info->getId() !== $id->getId()) { throw new \InvalidArgumentException('SessionId and SessionInfo don\'t match'); } $this->id = $id; $this->user = $info->getUserInfo() ? $info->getUserInfo()->getUser() : new User(); $this->store = $store; $this->logger = $logger; $this->lifetime = $lifetime; $this->provider = $info->getProvider(); $this->persist = $info->wasPersisted(); $this->remember = $info->wasRemembered(); $this->forceHTTPS = $info->forceHTTPS(); $this->providerMetadata = $info->getProviderMetadata(); $blob = $store->get(wfMemcKey('MWSession', (string) $this->id)); if (!is_array($blob) || !isset($blob['metadata']) || !is_array($blob['metadata']) || !isset($blob['data']) || !is_array($blob['data'])) { $this->data = []; $this->dataDirty = true; $this->metaDirty = true; $this->logger->debug('SessionBackend "{session}" is unsaved, marking dirty in constructor', ['session' => $this->id]); } else { $this->data = $blob['data']; if (isset($blob['metadata']['loggedOut'])) { $this->loggedOut = (int) $blob['metadata']['loggedOut']; } if (isset($blob['metadata']['expires'])) { $this->expires = (int) $blob['metadata']['expires']; } else { $this->metaDirty = true; $this->logger->debug('SessionBackend "{session}" metadata dirty due to missing expiration timestamp', ['session' => $this->id]); } } $this->dataHash = md5(serialize($this->data)); }
public function testLoadSessionInfoFromStore() { $manager = $this->getManager(); $logger = new \TestLogger(true, function ($m) { return preg_replace('/^Session \\[\\d+\\]\\w+<(?:null|anon|[+-]:\\d+:\\w+)>\\w+: /', 'Session X: ', $m); }); $manager->setLogger($logger); $request = new \FauxRequest(); // TestingAccessWrapper can't handle methods with reference arguments, sigh. $rClass = new \ReflectionClass($manager); $rMethod = $rClass->getMethod('loadSessionInfoFromStore'); $rMethod->setAccessible(true); $loadSessionInfoFromStore = function (&$info) use($rMethod, $manager, $request) { return $rMethod->invokeArgs($manager, array(&$info, $request)); }; $userInfo = UserInfo::newFromName('UTSysop', true); $unverifiedUserInfo = UserInfo::newFromName('UTSysop', false); $id = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; $metadata = array('userId' => $userInfo->getId(), 'userName' => $userInfo->getName(), 'userToken' => $userInfo->getToken(true), 'provider' => 'Mock'); $builder = $this->getMockBuilder('MediaWiki\\Session\\SessionProvider')->setMethods(array('__toString', 'mergeMetadata', 'refreshSessionInfo')); $provider = $builder->getMockForAbstractClass(); $provider->setManager($manager); $provider->expects($this->any())->method('persistsSessionId')->will($this->returnValue(true)); $provider->expects($this->any())->method('canChangeUser')->will($this->returnValue(true)); $provider->expects($this->any())->method('refreshSessionInfo')->will($this->returnValue(true)); $provider->expects($this->any())->method('__toString')->will($this->returnValue('Mock')); $provider->expects($this->any())->method('mergeMetadata')->will($this->returnCallback(function ($a, $b) { if ($b === array('Throw')) { throw new \UnexpectedValueException('no merge!'); } return array('Merged'); })); $provider2 = $builder->getMockForAbstractClass(); $provider2->setManager($manager); $provider2->expects($this->any())->method('persistsSessionId')->will($this->returnValue(false)); $provider2->expects($this->any())->method('canChangeUser')->will($this->returnValue(false)); $provider2->expects($this->any())->method('__toString')->will($this->returnValue('Mock2')); $provider2->expects($this->any())->method('refreshSessionInfo')->will($this->returnCallback(function ($info, $request, &$metadata) { $metadata['changed'] = true; return true; })); $provider3 = $builder->getMockForAbstractClass(); $provider3->setManager($manager); $provider3->expects($this->any())->method('persistsSessionId')->will($this->returnValue(true)); $provider3->expects($this->any())->method('canChangeUser')->will($this->returnValue(true)); $provider3->expects($this->once())->method('refreshSessionInfo')->will($this->returnValue(false)); $provider3->expects($this->any())->method('__toString')->will($this->returnValue('Mock3')); \TestingAccessWrapper::newFromObject($manager)->sessionProviders = array((string) $provider => $provider, (string) $provider2 => $provider2, (string) $provider3 => $provider3); // No metadata, basic usage $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $provider, 'id' => $id, 'userInfo' => $userInfo)); $this->assertFalse($info->isIdSafe(), 'sanity check'); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertFalse($info->isIdSafe()); $this->assertSame(array(), $logger->getBuffer()); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $provider, 'userInfo' => $userInfo)); $this->assertTrue($info->isIdSafe(), 'sanity check'); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertTrue($info->isIdSafe()); $this->assertSame(array(), $logger->getBuffer()); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $provider2, 'id' => $id, 'userInfo' => $userInfo)); $this->assertFalse($info->isIdSafe(), 'sanity check'); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertTrue($info->isIdSafe()); $this->assertSame(array(), $logger->getBuffer()); // Unverified user, no metadata $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $provider, 'id' => $id, 'userInfo' => $unverifiedUserInfo)); $this->assertSame($unverifiedUserInfo, $info->getUserInfo()); $this->assertFalse($loadSessionInfoFromStore($info)); $this->assertSame(array(array(LogLevel::WARNING, 'Session X: Unverified user provided and no metadata to auth it')), $logger->getBuffer()); $logger->clearBuffer(); // No metadata, missing data $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('id' => $id, 'userInfo' => $userInfo)); $this->assertFalse($loadSessionInfoFromStore($info)); $this->assertSame(array(array(LogLevel::WARNING, 'Session X: Null provider and no metadata')), $logger->getBuffer()); $logger->clearBuffer(); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $provider, 'id' => $id)); $this->assertFalse($info->isIdSafe(), 'sanity check'); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertInstanceOf('MediaWiki\\Session\\UserInfo', $info->getUserInfo()); $this->assertTrue($info->getUserInfo()->isVerified()); $this->assertTrue($info->getUserInfo()->isAnon()); $this->assertFalse($info->isIdSafe()); $this->assertSame(array(), $logger->getBuffer()); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $provider2, 'id' => $id)); $this->assertFalse($info->isIdSafe(), 'sanity check'); $this->assertFalse($loadSessionInfoFromStore($info)); $this->assertSame(array(array(LogLevel::INFO, 'Session X: No user provided and provider cannot set user')), $logger->getBuffer()); $logger->clearBuffer(); // Incomplete/bad metadata $this->store->setRawSession($id, true); $this->assertFalse($loadSessionInfoFromStore($info)); $this->assertSame(array(array(LogLevel::WARNING, 'Session X: Bad data')), $logger->getBuffer()); $logger->clearBuffer(); $this->store->setRawSession($id, array('data' => array())); $this->assertFalse($loadSessionInfoFromStore($info)); $this->assertSame(array(array(LogLevel::WARNING, 'Session X: Bad data structure')), $logger->getBuffer()); $logger->clearBuffer(); $this->store->deleteSession($id); $this->store->setRawSession($id, array('metadata' => $metadata)); $this->assertFalse($loadSessionInfoFromStore($info)); $this->assertSame(array(array(LogLevel::WARNING, 'Session X: Bad data structure')), $logger->getBuffer()); $logger->clearBuffer(); $this->store->setRawSession($id, array('metadata' => $metadata, 'data' => true)); $this->assertFalse($loadSessionInfoFromStore($info)); $this->assertSame(array(array(LogLevel::WARNING, 'Session X: Bad data structure')), $logger->getBuffer()); $logger->clearBuffer(); $this->store->setRawSession($id, array('metadata' => true, 'data' => array())); $this->assertFalse($loadSessionInfoFromStore($info)); $this->assertSame(array(array(LogLevel::WARNING, 'Session X: Bad data structure')), $logger->getBuffer()); $logger->clearBuffer(); foreach ($metadata as $key => $dummy) { $tmp = $metadata; unset($tmp[$key]); $this->store->setRawSession($id, array('metadata' => $tmp, 'data' => array())); $this->assertFalse($loadSessionInfoFromStore($info)); $this->assertSame(array(array(LogLevel::WARNING, 'Session X: Bad metadata')), $logger->getBuffer()); $logger->clearBuffer(); } // Basic usage with metadata $this->store->setRawSession($id, array('metadata' => $metadata, 'data' => array())); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $provider, 'id' => $id, 'userInfo' => $userInfo)); $this->assertFalse($info->isIdSafe(), 'sanity check'); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertTrue($info->isIdSafe()); $this->assertSame(array(), $logger->getBuffer()); // Mismatched provider $this->store->setSessionMeta($id, array('provider' => 'Bad') + $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $provider, 'id' => $id, 'userInfo' => $userInfo)); $this->assertFalse($loadSessionInfoFromStore($info)); $this->assertSame(array(array(LogLevel::WARNING, 'Session X: Wrong provider, Bad !== Mock')), $logger->getBuffer()); $logger->clearBuffer(); // Unknown provider $this->store->setSessionMeta($id, array('provider' => 'Bad') + $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('id' => $id, 'userInfo' => $userInfo)); $this->assertFalse($loadSessionInfoFromStore($info)); $this->assertSame(array(array(LogLevel::WARNING, 'Session X: Unknown provider, Bad')), $logger->getBuffer()); $logger->clearBuffer(); // Fill in provider $this->store->setSessionMeta($id, $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('id' => $id, 'userInfo' => $userInfo)); $this->assertFalse($info->isIdSafe(), 'sanity check'); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertTrue($info->isIdSafe()); $this->assertSame(array(), $logger->getBuffer()); // Bad user metadata $this->store->setSessionMeta($id, array('userId' => -1, 'userToken' => null) + $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $provider, 'id' => $id)); $this->assertFalse($loadSessionInfoFromStore($info)); $this->assertSame(array(array(LogLevel::ERROR, 'Session X: Invalid ID')), $logger->getBuffer()); $logger->clearBuffer(); $this->store->setSessionMeta($id, array('userId' => 0, 'userName' => '<X>', 'userToken' => null) + $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $provider, 'id' => $id)); $this->assertFalse($loadSessionInfoFromStore($info)); $this->assertSame(array(array(LogLevel::ERROR, 'Session X: Invalid user name')), $logger->getBuffer()); $logger->clearBuffer(); // Mismatched user by ID $this->store->setSessionMeta($id, array('userId' => $userInfo->getId() + 1, 'userToken' => null) + $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $provider, 'id' => $id, 'userInfo' => $userInfo)); $this->assertFalse($loadSessionInfoFromStore($info)); $this->assertSame(array(array(LogLevel::WARNING, 'Session X: User ID mismatch, 2 !== 1')), $logger->getBuffer()); $logger->clearBuffer(); // Mismatched user by name $this->store->setSessionMeta($id, array('userId' => 0, 'userName' => 'X', 'userToken' => null) + $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $provider, 'id' => $id, 'userInfo' => $userInfo)); $this->assertFalse($loadSessionInfoFromStore($info)); $this->assertSame(array(array(LogLevel::WARNING, 'Session X: User name mismatch, X !== UTSysop')), $logger->getBuffer()); $logger->clearBuffer(); // ID matches, name doesn't $this->store->setSessionMeta($id, array('userId' => $userInfo->getId(), 'userName' => 'X', 'userToken' => null) + $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $provider, 'id' => $id, 'userInfo' => $userInfo)); $this->assertFalse($loadSessionInfoFromStore($info)); $this->assertSame(array(array(LogLevel::WARNING, 'Session X: User ID matched but name didn\'t (rename?), X !== UTSysop')), $logger->getBuffer()); $logger->clearBuffer(); // Mismatched anon user $this->store->setSessionMeta($id, array('userId' => 0, 'userName' => null, 'userToken' => null) + $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $provider, 'id' => $id, 'userInfo' => $userInfo)); $this->assertFalse($loadSessionInfoFromStore($info)); $this->assertSame(array(array(LogLevel::WARNING, 'Session X: Metadata has an anonymous user, but a non-anon user was provided')), $logger->getBuffer()); $logger->clearBuffer(); // Lookup user by ID $this->store->setSessionMeta($id, array('userToken' => null) + $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $provider, 'id' => $id)); $this->assertFalse($info->isIdSafe(), 'sanity check'); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertSame($userInfo->getId(), $info->getUserInfo()->getId()); $this->assertTrue($info->isIdSafe()); $this->assertSame(array(), $logger->getBuffer()); // Lookup user by name $this->store->setSessionMeta($id, array('userId' => 0, 'userName' => 'UTSysop', 'userToken' => null) + $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $provider, 'id' => $id)); $this->assertFalse($info->isIdSafe(), 'sanity check'); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertSame($userInfo->getId(), $info->getUserInfo()->getId()); $this->assertTrue($info->isIdSafe()); $this->assertSame(array(), $logger->getBuffer()); // Lookup anonymous user $this->store->setSessionMeta($id, array('userId' => 0, 'userName' => null, 'userToken' => null) + $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $provider, 'id' => $id)); $this->assertFalse($info->isIdSafe(), 'sanity check'); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertTrue($info->getUserInfo()->isAnon()); $this->assertTrue($info->isIdSafe()); $this->assertSame(array(), $logger->getBuffer()); // Unverified user with metadata $this->store->setSessionMeta($id, $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $provider, 'id' => $id, 'userInfo' => $unverifiedUserInfo)); $this->assertFalse($info->isIdSafe(), 'sanity check'); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertTrue($info->getUserInfo()->isVerified()); $this->assertSame($unverifiedUserInfo->getId(), $info->getUserInfo()->getId()); $this->assertSame($unverifiedUserInfo->getName(), $info->getUserInfo()->getName()); $this->assertTrue($info->isIdSafe()); $this->assertSame(array(), $logger->getBuffer()); // Unverified user with metadata $this->store->setSessionMeta($id, $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $provider, 'id' => $id, 'userInfo' => $unverifiedUserInfo)); $this->assertFalse($info->isIdSafe(), 'sanity check'); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertTrue($info->getUserInfo()->isVerified()); $this->assertSame($unverifiedUserInfo->getId(), $info->getUserInfo()->getId()); $this->assertSame($unverifiedUserInfo->getName(), $info->getUserInfo()->getName()); $this->assertTrue($info->isIdSafe()); $this->assertSame(array(), $logger->getBuffer()); // Wrong token $this->store->setSessionMeta($id, array('userToken' => 'Bad') + $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $provider, 'id' => $id, 'userInfo' => $userInfo)); $this->assertFalse($loadSessionInfoFromStore($info)); $this->assertSame(array(array(LogLevel::WARNING, 'Session X: User token mismatch')), $logger->getBuffer()); $logger->clearBuffer(); // Provider metadata $this->store->setSessionMeta($id, array('provider' => 'Mock2') + $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $provider2, 'id' => $id, 'userInfo' => $userInfo, 'metadata' => array('Info'))); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertSame(array('Info', 'changed' => true), $info->getProviderMetadata()); $this->assertSame(array(), $logger->getBuffer()); $this->store->setSessionMeta($id, array('providerMetadata' => array('Saved')) + $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $provider, 'id' => $id, 'userInfo' => $userInfo)); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertSame(array('Saved'), $info->getProviderMetadata()); $this->assertSame(array(), $logger->getBuffer()); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $provider, 'id' => $id, 'userInfo' => $userInfo, 'metadata' => array('Info'))); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertSame(array('Merged'), $info->getProviderMetadata()); $this->assertSame(array(), $logger->getBuffer()); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $provider, 'id' => $id, 'userInfo' => $userInfo, 'metadata' => array('Throw'))); $this->assertFalse($loadSessionInfoFromStore($info)); $this->assertSame(array(array(LogLevel::WARNING, 'Session X: Metadata merge failed: no merge!')), $logger->getBuffer()); $logger->clearBuffer(); // Remember from session $this->store->setSessionMeta($id, $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $provider, 'id' => $id)); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertFalse($info->wasRemembered()); $this->assertSame(array(), $logger->getBuffer()); $this->store->setSessionMeta($id, array('remember' => true) + $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $provider, 'id' => $id)); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertTrue($info->wasRemembered()); $this->assertSame(array(), $logger->getBuffer()); $this->store->setSessionMeta($id, array('remember' => false) + $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $provider, 'id' => $id, 'userInfo' => $userInfo)); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertTrue($info->wasRemembered()); $this->assertSame(array(), $logger->getBuffer()); // forceHTTPS from session $this->store->setSessionMeta($id, $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $provider, 'id' => $id, 'userInfo' => $userInfo)); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertFalse($info->forceHTTPS()); $this->assertSame(array(), $logger->getBuffer()); $this->store->setSessionMeta($id, array('forceHTTPS' => true) + $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $provider, 'id' => $id, 'userInfo' => $userInfo)); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertTrue($info->forceHTTPS()); $this->assertSame(array(), $logger->getBuffer()); $this->store->setSessionMeta($id, array('forceHTTPS' => false) + $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $provider, 'id' => $id, 'userInfo' => $userInfo, 'forceHTTPS' => true)); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertTrue($info->forceHTTPS()); $this->assertSame(array(), $logger->getBuffer()); // "Persist" flag from session $this->store->setSessionMeta($id, $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $provider, 'id' => $id, 'userInfo' => $userInfo)); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertFalse($info->wasPersisted()); $this->assertSame(array(), $logger->getBuffer()); $this->store->setSessionMeta($id, array('persisted' => true) + $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $provider, 'id' => $id, 'userInfo' => $userInfo)); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertTrue($info->wasPersisted()); $this->assertSame(array(), $logger->getBuffer()); $this->store->setSessionMeta($id, array('persisted' => false) + $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $provider, 'id' => $id, 'userInfo' => $userInfo, 'persisted' => true)); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertTrue($info->wasPersisted()); $this->assertSame(array(), $logger->getBuffer()); // Provider refreshSessionInfo() returning false $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $provider3)); $this->assertFalse($loadSessionInfoFromStore($info)); $this->assertSame(array(), $logger->getBuffer()); // Hook $that = $this; $called = false; $data = array('foo' => 1); $this->store->setSession($id, array('metadata' => $metadata, 'data' => $data)); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $provider, 'id' => $id, 'userInfo' => $userInfo)); $this->mergeMwGlobalArrayValue('wgHooks', array('SessionCheckInfo' => array(function (&$reason, $i, $r, $m, $d) use($that, $info, $metadata, $data, $request, &$called) { $that->assertSame($info->getId(), $i->getId()); $that->assertSame($info->getProvider(), $i->getProvider()); $that->assertSame($info->getUserInfo(), $i->getUserInfo()); $that->assertSame($request, $r); $that->assertEquals($metadata, $m); $that->assertEquals($data, $d); $called = true; return false; }))); $this->assertFalse($loadSessionInfoFromStore($info)); $this->assertTrue($called); $this->assertSame(array(array(LogLevel::WARNING, 'Session X: Hook aborted')), $logger->getBuffer()); $logger->clearBuffer(); }
public function testLoadSessionInfoFromStore() { $manager = $this->getManager(); $logger = new \TestLogger(true); $manager->setLogger($logger); $request = new \FauxRequest(); // TestingAccessWrapper can't handle methods with reference arguments, sigh. $rClass = new \ReflectionClass($manager); $rMethod = $rClass->getMethod('loadSessionInfoFromStore'); $rMethod->setAccessible(true); $loadSessionInfoFromStore = function (&$info) use($rMethod, $manager, $request) { return $rMethod->invokeArgs($manager, [&$info, $request]); }; $userInfo = UserInfo::newFromName('UTSysop', true); $unverifiedUserInfo = UserInfo::newFromName('UTSysop', false); $id = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; $metadata = ['userId' => $userInfo->getId(), 'userName' => $userInfo->getName(), 'userToken' => $userInfo->getToken(true), 'provider' => 'Mock']; $builder = $this->getMockBuilder(SessionProvider::class)->setMethods(['__toString', 'mergeMetadata', 'refreshSessionInfo']); $provider = $builder->getMockForAbstractClass(); $provider->setManager($manager); $provider->expects($this->any())->method('persistsSessionId')->will($this->returnValue(true)); $provider->expects($this->any())->method('canChangeUser')->will($this->returnValue(true)); $provider->expects($this->any())->method('refreshSessionInfo')->will($this->returnValue(true)); $provider->expects($this->any())->method('__toString')->will($this->returnValue('Mock')); $provider->expects($this->any())->method('mergeMetadata')->will($this->returnCallback(function ($a, $b) { if ($b === ['Throw']) { throw new MetadataMergeException('no merge!'); } return ['Merged']; })); $provider2 = $builder->getMockForAbstractClass(); $provider2->setManager($manager); $provider2->expects($this->any())->method('persistsSessionId')->will($this->returnValue(false)); $provider2->expects($this->any())->method('canChangeUser')->will($this->returnValue(false)); $provider2->expects($this->any())->method('__toString')->will($this->returnValue('Mock2')); $provider2->expects($this->any())->method('refreshSessionInfo')->will($this->returnCallback(function ($info, $request, &$metadata) { $metadata['changed'] = true; return true; })); $provider3 = $builder->getMockForAbstractClass(); $provider3->setManager($manager); $provider3->expects($this->any())->method('persistsSessionId')->will($this->returnValue(true)); $provider3->expects($this->any())->method('canChangeUser')->will($this->returnValue(true)); $provider3->expects($this->once())->method('refreshSessionInfo')->will($this->returnValue(false)); $provider3->expects($this->any())->method('__toString')->will($this->returnValue('Mock3')); \TestingAccessWrapper::newFromObject($manager)->sessionProviders = [(string) $provider => $provider, (string) $provider2 => $provider2, (string) $provider3 => $provider3]; // No metadata, basic usage $info = new SessionInfo(SessionInfo::MIN_PRIORITY, ['provider' => $provider, 'id' => $id, 'userInfo' => $userInfo]); $this->assertFalse($info->isIdSafe(), 'sanity check'); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertFalse($info->isIdSafe()); $this->assertSame([], $logger->getBuffer()); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, ['provider' => $provider, 'userInfo' => $userInfo]); $this->assertTrue($info->isIdSafe(), 'sanity check'); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertTrue($info->isIdSafe()); $this->assertSame([], $logger->getBuffer()); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, ['provider' => $provider2, 'id' => $id, 'userInfo' => $userInfo]); $this->assertFalse($info->isIdSafe(), 'sanity check'); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertTrue($info->isIdSafe()); $this->assertSame([], $logger->getBuffer()); // Unverified user, no metadata $info = new SessionInfo(SessionInfo::MIN_PRIORITY, ['provider' => $provider, 'id' => $id, 'userInfo' => $unverifiedUserInfo]); $this->assertSame($unverifiedUserInfo, $info->getUserInfo()); $this->assertFalse($loadSessionInfoFromStore($info)); $this->assertSame([[LogLevel::WARNING, 'Session "{session}": Unverified user provided and no metadata to auth it']], $logger->getBuffer()); $logger->clearBuffer(); // No metadata, missing data $info = new SessionInfo(SessionInfo::MIN_PRIORITY, ['id' => $id, 'userInfo' => $userInfo]); $this->assertFalse($loadSessionInfoFromStore($info)); $this->assertSame([[LogLevel::WARNING, 'Session "{session}": Null provider and no metadata']], $logger->getBuffer()); $logger->clearBuffer(); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, ['provider' => $provider, 'id' => $id]); $this->assertFalse($info->isIdSafe(), 'sanity check'); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertInstanceOf(UserInfo::class, $info->getUserInfo()); $this->assertTrue($info->getUserInfo()->isVerified()); $this->assertTrue($info->getUserInfo()->isAnon()); $this->assertFalse($info->isIdSafe()); $this->assertSame([], $logger->getBuffer()); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, ['provider' => $provider2, 'id' => $id]); $this->assertFalse($info->isIdSafe(), 'sanity check'); $this->assertFalse($loadSessionInfoFromStore($info)); $this->assertSame([[LogLevel::INFO, 'Session "{session}": No user provided and provider cannot set user']], $logger->getBuffer()); $logger->clearBuffer(); // Incomplete/bad metadata $this->store->setRawSession($id, true); $this->assertFalse($loadSessionInfoFromStore($info)); $this->assertSame([[LogLevel::WARNING, 'Session "{session}": Bad data']], $logger->getBuffer()); $logger->clearBuffer(); $this->store->setRawSession($id, ['data' => []]); $this->assertFalse($loadSessionInfoFromStore($info)); $this->assertSame([[LogLevel::WARNING, 'Session "{session}": Bad data structure']], $logger->getBuffer()); $logger->clearBuffer(); $this->store->deleteSession($id); $this->store->setRawSession($id, ['metadata' => $metadata]); $this->assertFalse($loadSessionInfoFromStore($info)); $this->assertSame([[LogLevel::WARNING, 'Session "{session}": Bad data structure']], $logger->getBuffer()); $logger->clearBuffer(); $this->store->setRawSession($id, ['metadata' => $metadata, 'data' => true]); $this->assertFalse($loadSessionInfoFromStore($info)); $this->assertSame([[LogLevel::WARNING, 'Session "{session}": Bad data structure']], $logger->getBuffer()); $logger->clearBuffer(); $this->store->setRawSession($id, ['metadata' => true, 'data' => []]); $this->assertFalse($loadSessionInfoFromStore($info)); $this->assertSame([[LogLevel::WARNING, 'Session "{session}": Bad data structure']], $logger->getBuffer()); $logger->clearBuffer(); foreach ($metadata as $key => $dummy) { $tmp = $metadata; unset($tmp[$key]); $this->store->setRawSession($id, ['metadata' => $tmp, 'data' => []]); $this->assertFalse($loadSessionInfoFromStore($info)); $this->assertSame([[LogLevel::WARNING, 'Session "{session}": Bad metadata']], $logger->getBuffer()); $logger->clearBuffer(); } // Basic usage with metadata $this->store->setRawSession($id, ['metadata' => $metadata, 'data' => []]); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, ['provider' => $provider, 'id' => $id, 'userInfo' => $userInfo]); $this->assertFalse($info->isIdSafe(), 'sanity check'); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertTrue($info->isIdSafe()); $this->assertSame([], $logger->getBuffer()); // Mismatched provider $this->store->setSessionMeta($id, ['provider' => 'Bad'] + $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, ['provider' => $provider, 'id' => $id, 'userInfo' => $userInfo]); $this->assertFalse($loadSessionInfoFromStore($info)); $this->assertSame([[LogLevel::WARNING, 'Session "{session}": Wrong provider Bad !== Mock']], $logger->getBuffer()); $logger->clearBuffer(); // Unknown provider $this->store->setSessionMeta($id, ['provider' => 'Bad'] + $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, ['id' => $id, 'userInfo' => $userInfo]); $this->assertFalse($loadSessionInfoFromStore($info)); $this->assertSame([[LogLevel::WARNING, 'Session "{session}": Unknown provider Bad']], $logger->getBuffer()); $logger->clearBuffer(); // Fill in provider $this->store->setSessionMeta($id, $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, ['id' => $id, 'userInfo' => $userInfo]); $this->assertFalse($info->isIdSafe(), 'sanity check'); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertTrue($info->isIdSafe()); $this->assertSame([], $logger->getBuffer()); // Bad user metadata $this->store->setSessionMeta($id, ['userId' => -1, 'userToken' => null] + $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, ['provider' => $provider, 'id' => $id]); $this->assertFalse($loadSessionInfoFromStore($info)); $this->assertSame([[LogLevel::ERROR, 'Session "{session}": {exception}']], $logger->getBuffer()); $logger->clearBuffer(); $this->store->setSessionMeta($id, ['userId' => 0, 'userName' => '<X>', 'userToken' => null] + $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, ['provider' => $provider, 'id' => $id]); $this->assertFalse($loadSessionInfoFromStore($info)); $this->assertSame([[LogLevel::ERROR, 'Session "{session}": {exception}']], $logger->getBuffer()); $logger->clearBuffer(); // Mismatched user by ID $this->store->setSessionMeta($id, ['userId' => $userInfo->getId() + 1, 'userToken' => null] + $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, ['provider' => $provider, 'id' => $id, 'userInfo' => $userInfo]); $this->assertFalse($loadSessionInfoFromStore($info)); $this->assertSame([[LogLevel::WARNING, 'Session "{session}": User ID mismatch, {uid_a} !== {uid_b}']], $logger->getBuffer()); $logger->clearBuffer(); // Mismatched user by name $this->store->setSessionMeta($id, ['userId' => 0, 'userName' => 'X', 'userToken' => null] + $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, ['provider' => $provider, 'id' => $id, 'userInfo' => $userInfo]); $this->assertFalse($loadSessionInfoFromStore($info)); $this->assertSame([[LogLevel::WARNING, 'Session "{session}": User name mismatch, {uname_a} !== {uname_b}']], $logger->getBuffer()); $logger->clearBuffer(); // ID matches, name doesn't $this->store->setSessionMeta($id, ['userId' => $userInfo->getId(), 'userName' => 'X', 'userToken' => null] + $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, ['provider' => $provider, 'id' => $id, 'userInfo' => $userInfo]); $this->assertFalse($loadSessionInfoFromStore($info)); $this->assertSame([[LogLevel::WARNING, 'Session "{session}": User ID matched but name didn\'t (rename?), {uname_a} !== {uname_b}']], $logger->getBuffer()); $logger->clearBuffer(); // Mismatched anon user $this->store->setSessionMeta($id, ['userId' => 0, 'userName' => null, 'userToken' => null] + $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, ['provider' => $provider, 'id' => $id, 'userInfo' => $userInfo]); $this->assertFalse($loadSessionInfoFromStore($info)); $this->assertSame([[LogLevel::WARNING, 'Session "{session}": Metadata has an anonymous user, ' . 'but a non-anon user was provided']], $logger->getBuffer()); $logger->clearBuffer(); // Lookup user by ID $this->store->setSessionMeta($id, ['userToken' => null] + $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, ['provider' => $provider, 'id' => $id]); $this->assertFalse($info->isIdSafe(), 'sanity check'); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertSame($userInfo->getId(), $info->getUserInfo()->getId()); $this->assertTrue($info->isIdSafe()); $this->assertSame([], $logger->getBuffer()); // Lookup user by name $this->store->setSessionMeta($id, ['userId' => 0, 'userName' => 'UTSysop', 'userToken' => null] + $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, ['provider' => $provider, 'id' => $id]); $this->assertFalse($info->isIdSafe(), 'sanity check'); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertSame($userInfo->getId(), $info->getUserInfo()->getId()); $this->assertTrue($info->isIdSafe()); $this->assertSame([], $logger->getBuffer()); // Lookup anonymous user $this->store->setSessionMeta($id, ['userId' => 0, 'userName' => null, 'userToken' => null] + $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, ['provider' => $provider, 'id' => $id]); $this->assertFalse($info->isIdSafe(), 'sanity check'); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertTrue($info->getUserInfo()->isAnon()); $this->assertTrue($info->isIdSafe()); $this->assertSame([], $logger->getBuffer()); // Unverified user with metadata $this->store->setSessionMeta($id, $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, ['provider' => $provider, 'id' => $id, 'userInfo' => $unverifiedUserInfo]); $this->assertFalse($info->isIdSafe(), 'sanity check'); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertTrue($info->getUserInfo()->isVerified()); $this->assertSame($unverifiedUserInfo->getId(), $info->getUserInfo()->getId()); $this->assertSame($unverifiedUserInfo->getName(), $info->getUserInfo()->getName()); $this->assertTrue($info->isIdSafe()); $this->assertSame([], $logger->getBuffer()); // Unverified user with metadata $this->store->setSessionMeta($id, $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, ['provider' => $provider, 'id' => $id, 'userInfo' => $unverifiedUserInfo]); $this->assertFalse($info->isIdSafe(), 'sanity check'); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertTrue($info->getUserInfo()->isVerified()); $this->assertSame($unverifiedUserInfo->getId(), $info->getUserInfo()->getId()); $this->assertSame($unverifiedUserInfo->getName(), $info->getUserInfo()->getName()); $this->assertTrue($info->isIdSafe()); $this->assertSame([], $logger->getBuffer()); // Wrong token $this->store->setSessionMeta($id, ['userToken' => 'Bad'] + $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, ['provider' => $provider, 'id' => $id, 'userInfo' => $userInfo]); $this->assertFalse($loadSessionInfoFromStore($info)); $this->assertSame([[LogLevel::WARNING, 'Session "{session}": User token mismatch']], $logger->getBuffer()); $logger->clearBuffer(); // Provider metadata $this->store->setSessionMeta($id, ['provider' => 'Mock2'] + $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, ['provider' => $provider2, 'id' => $id, 'userInfo' => $userInfo, 'metadata' => ['Info']]); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertSame(['Info', 'changed' => true], $info->getProviderMetadata()); $this->assertSame([], $logger->getBuffer()); $this->store->setSessionMeta($id, ['providerMetadata' => ['Saved']] + $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, ['provider' => $provider, 'id' => $id, 'userInfo' => $userInfo]); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertSame(['Saved'], $info->getProviderMetadata()); $this->assertSame([], $logger->getBuffer()); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, ['provider' => $provider, 'id' => $id, 'userInfo' => $userInfo, 'metadata' => ['Info']]); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertSame(['Merged'], $info->getProviderMetadata()); $this->assertSame([], $logger->getBuffer()); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, ['provider' => $provider, 'id' => $id, 'userInfo' => $userInfo, 'metadata' => ['Throw']]); $this->assertFalse($loadSessionInfoFromStore($info)); $this->assertSame([[LogLevel::WARNING, 'Session "{session}": Metadata merge failed: {exception}']], $logger->getBuffer()); $logger->clearBuffer(); // Remember from session $this->store->setSessionMeta($id, $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, ['provider' => $provider, 'id' => $id]); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertFalse($info->wasRemembered()); $this->assertSame([], $logger->getBuffer()); $this->store->setSessionMeta($id, ['remember' => true] + $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, ['provider' => $provider, 'id' => $id]); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertTrue($info->wasRemembered()); $this->assertSame([], $logger->getBuffer()); $this->store->setSessionMeta($id, ['remember' => false] + $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, ['provider' => $provider, 'id' => $id, 'userInfo' => $userInfo]); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertTrue($info->wasRemembered()); $this->assertSame([], $logger->getBuffer()); // forceHTTPS from session $this->store->setSessionMeta($id, $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, ['provider' => $provider, 'id' => $id, 'userInfo' => $userInfo]); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertFalse($info->forceHTTPS()); $this->assertSame([], $logger->getBuffer()); $this->store->setSessionMeta($id, ['forceHTTPS' => true] + $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, ['provider' => $provider, 'id' => $id, 'userInfo' => $userInfo]); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertTrue($info->forceHTTPS()); $this->assertSame([], $logger->getBuffer()); $this->store->setSessionMeta($id, ['forceHTTPS' => false] + $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, ['provider' => $provider, 'id' => $id, 'userInfo' => $userInfo, 'forceHTTPS' => true]); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertTrue($info->forceHTTPS()); $this->assertSame([], $logger->getBuffer()); // "Persist" flag from session $this->store->setSessionMeta($id, $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, ['provider' => $provider, 'id' => $id, 'userInfo' => $userInfo]); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertFalse($info->wasPersisted()); $this->assertSame([], $logger->getBuffer()); $this->store->setSessionMeta($id, ['persisted' => true] + $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, ['provider' => $provider, 'id' => $id, 'userInfo' => $userInfo]); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertTrue($info->wasPersisted()); $this->assertSame([], $logger->getBuffer()); $this->store->setSessionMeta($id, ['persisted' => false] + $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, ['provider' => $provider, 'id' => $id, 'userInfo' => $userInfo, 'persisted' => true]); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertTrue($info->wasPersisted()); $this->assertSame([], $logger->getBuffer()); // Provider refreshSessionInfo() returning false $info = new SessionInfo(SessionInfo::MIN_PRIORITY, ['provider' => $provider3]); $this->assertFalse($loadSessionInfoFromStore($info)); $this->assertSame([], $logger->getBuffer()); // Hook $called = false; $data = ['foo' => 1]; $this->store->setSession($id, ['metadata' => $metadata, 'data' => $data]); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, ['provider' => $provider, 'id' => $id, 'userInfo' => $userInfo]); $this->mergeMwGlobalArrayValue('wgHooks', ['SessionCheckInfo' => [function (&$reason, $i, $r, $m, $d) use($info, $metadata, $data, $request, &$called) { $this->assertSame($info->getId(), $i->getId()); $this->assertSame($info->getProvider(), $i->getProvider()); $this->assertSame($info->getUserInfo(), $i->getUserInfo()); $this->assertSame($request, $r); $this->assertEquals($metadata, $m); $this->assertEquals($data, $d); $called = true; return false; }]]); $this->assertFalse($loadSessionInfoFromStore($info)); $this->assertTrue($called); $this->assertSame([[LogLevel::WARNING, 'Session "{session}": Hook aborted']], $logger->getBuffer()); $logger->clearBuffer(); $this->mergeMwGlobalArrayValue('wgHooks', ['SessionCheckInfo' => []]); // forceUse deletes bad backend data $this->store->setSessionMeta($id, ['userToken' => 'Bad'] + $metadata); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, ['provider' => $provider, 'id' => $id, 'userInfo' => $userInfo, 'forceUse' => true]); $this->assertTrue($loadSessionInfoFromStore($info)); $this->assertFalse($this->store->getSession($id)); $this->assertSame([[LogLevel::WARNING, 'Session "{session}": User token mismatch']], $logger->getBuffer()); $logger->clearBuffer(); }