public function testConstructor() { // Set variables $this->getBackend(); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $this->provider, 'id' => self::SESSIONID, 'persisted' => true, 'userInfo' => UserInfo::newFromName('UTSysop', false), 'idIsSafe' => true)); $id = new SessionId($info->getId()); $logger = new \Psr\Log\NullLogger(); try { new SessionBackend($id, $info, $this->store, $this->store, $logger, 10); $this->fail('Expected exception not thrown'); } catch (\InvalidArgumentException $ex) { $this->assertSame("Refusing to create session for unverified user {$info->getUserInfo()}", $ex->getMessage()); } $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('id' => self::SESSIONID, 'userInfo' => UserInfo::newFromName('UTSysop', true), 'idIsSafe' => true)); $id = new SessionId($info->getId()); try { new SessionBackend($id, $info, $this->store, $this->store, $logger, 10); $this->fail('Expected exception not thrown'); } catch (\InvalidArgumentException $ex) { $this->assertSame('Cannot create session without a provider', $ex->getMessage()); } $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $this->provider, 'id' => self::SESSIONID, 'persisted' => true, 'userInfo' => UserInfo::newFromName('UTSysop', true), 'idIsSafe' => true)); $id = new SessionId('!' . $info->getId()); try { new SessionBackend($id, $info, $this->store, $this->store, $logger, 10); $this->fail('Expected exception not thrown'); } catch (\InvalidArgumentException $ex) { $this->assertSame('SessionId and SessionInfo don\'t match', $ex->getMessage()); } $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $this->provider, 'id' => self::SESSIONID, 'persisted' => true, 'userInfo' => UserInfo::newFromName('UTSysop', true), 'idIsSafe' => true)); $id = new SessionId($info->getId()); $backend = new SessionBackend($id, $info, $this->store, $this->store, $logger, 10); $this->assertSame(self::SESSIONID, $backend->getId()); $this->assertSame($id, $backend->getSessionId()); $this->assertSame($this->provider, $backend->getProvider()); $this->assertInstanceOf('User', $backend->getUser()); $this->assertSame('UTSysop', $backend->getUser()->getName()); $this->assertSame($info->wasPersisted(), $backend->isPersistent()); $this->assertSame($info->wasRemembered(), $backend->shouldRememberUser()); $this->assertSame($info->forceHTTPS(), $backend->shouldForceHTTPS()); $expire = time() + 100; $this->store->setSessionMeta(self::SESSIONID, array('expires' => $expire), 2); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $this->provider, 'id' => self::SESSIONID, 'persisted' => true, 'forceHTTPS' => true, 'metadata' => array('foo'), 'idIsSafe' => true)); $id = new SessionId($info->getId()); $backend = new SessionBackend($id, $info, $this->store, $this->store, $logger, 10); $this->assertSame(self::SESSIONID, $backend->getId()); $this->assertSame($id, $backend->getSessionId()); $this->assertSame($this->provider, $backend->getProvider()); $this->assertInstanceOf('User', $backend->getUser()); $this->assertTrue($backend->getUser()->isAnon()); $this->assertSame($info->wasPersisted(), $backend->isPersistent()); $this->assertSame($info->wasRemembered(), $backend->shouldRememberUser()); $this->assertSame($info->forceHTTPS(), $backend->shouldForceHTTPS()); $this->assertSame($expire, \TestingAccessWrapper::newFromObject($backend)->expires); $this->assertSame(array('foo'), $backend->getProviderMetadata()); }
/** * @param int $priority Session priority * @param array $data * - provider: (SessionProvider|null) If not given, the provider will be * determined from the saved session data. * - id: (string|null) Session ID * - userInfo: (UserInfo|null) User known from the request. If * $provider->canChangeUser() is false, a verified user * must be provided. * - persisted: (bool) Whether this session was persisted * - remembered: (bool) Whether the verified user was remembered. * Defaults to true. * - forceHTTPS: (bool) Whether to force HTTPS for this session * - metadata: (array) Provider metadata, to be returned by * Session::getProviderMetadata(). * - idIsSafe: (bool) Set true if the 'id' did not come from the user. * Generally you'll use this from SessionProvider::newEmptySession(), * and not from any other method. * - copyFrom: (SessionInfo) SessionInfo to copy other data items from. */ public function __construct($priority, array $data) { if ($priority < self::MIN_PRIORITY || $priority > self::MAX_PRIORITY) { throw new \InvalidArgumentException('Invalid priority'); } if (isset($data['copyFrom'])) { $from = $data['copyFrom']; if (!$from instanceof SessionInfo) { throw new \InvalidArgumentException('Invalid copyFrom'); } $data += array('provider' => $from->provider, 'id' => $from->id, 'userInfo' => $from->userInfo, 'persisted' => $from->persisted, 'remembered' => $from->remembered, 'forceHTTPS' => $from->forceHTTPS, 'metadata' => $from->providerMetadata, 'idIsSafe' => $from->idIsSafe); // @codeCoverageIgnoreEnd } else { $data += array('provider' => null, 'id' => null, 'userInfo' => null, 'persisted' => false, 'remembered' => true, 'forceHTTPS' => false, 'metadata' => null, 'idIsSafe' => false); // @codeCoverageIgnoreEnd } if ($data['id'] !== null && !SessionManager::validateSessionId($data['id'])) { throw new \InvalidArgumentException('Invalid session ID'); } if ($data['userInfo'] !== null && !$data['userInfo'] instanceof UserInfo) { throw new \InvalidArgumentException('Invalid userInfo'); } if (!$data['provider'] && $data['id'] === null) { throw new \InvalidArgumentException('Must supply an ID when no provider is given'); } if ($data['metadata'] !== null && !is_array($data['metadata'])) { throw new \InvalidArgumentException('Invalid metadata'); } $this->provider = $data['provider']; if ($data['id'] !== null) { $this->id = $data['id']; $this->idIsSafe = $data['idIsSafe']; } else { $this->id = $this->provider->getManager()->generateSessionId(); $this->idIsSafe = true; } $this->priority = (int) $priority; $this->userInfo = $data['userInfo']; $this->persisted = (bool) $data['persisted']; if ($data['provider'] !== null) { if ($this->userInfo !== null && !$this->userInfo->isAnon() && $this->userInfo->isVerified()) { $this->remembered = (bool) $data['remembered']; } $this->providerMetadata = $data['metadata']; } $this->forceHTTPS = (bool) $data['forceHTTPS']; }
/** * 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 provideSessionInfo(WebRequest $request) { $sessionId = $this->getCookie($request, $this->params['sessionName'], ''); $info = array('provider' => $this, 'forceHTTPS' => $this->getCookie($request, 'forceHTTPS', '', false)); if (SessionManager::validateSessionId($sessionId)) { $info['id'] = $sessionId; $info['persisted'] = true; } list($userId, $userName, $token) = $this->getUserInfoFromCookies($request); if ($userId !== null) { try { $userInfo = UserInfo::newFromId($userId); } catch (\InvalidArgumentException $ex) { return null; } // Sanity check if ($userName !== null && $userInfo->getName() !== $userName) { $this->logger->warning('Session "{session}" requested with mismatched UserID and UserName cookies.', array('session' => $sessionId, 'mismatch' => array('userid' => $userId, 'cookie_username' => $userName, 'username' => $userInfo->getName()))); return null; } if ($token !== null) { if (!hash_equals($userInfo->getToken(), $token)) { $this->logger->warning('Session "{session}" requested with invalid Token cookie.', array('session' => $sessionId, 'userid' => $userId, 'username' => $userInfo->getName())); return null; } $info['userInfo'] = $userInfo->verified(); } elseif (isset($info['id'])) { $info['userInfo'] = $userInfo; } else { // No point in returning, loadSessionInfoFromStore() will // reject it anyway. return null; } } elseif (isset($info['id'])) { // No UserID cookie, so insist that the session is anonymous. // Note: this event occurs for several normal activities: // * anon visits Special:UserLogin // * anon browsing after seeing Special:UserLogin // * anon browsing after edit or preview $this->logger->debug('Session "{session}" requested without UserID cookie', array('session' => $info['id'])); $info['userInfo'] = UserInfo::newAnonymous(); } else { // No session ID and no user is the same as an empty session, so // there's no point. return null; } return new SessionInfo($this->priority, $info); }
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'); }
/** * @dataProvider provideSecuritySensitiveOperationStatus * @param bool $mutableSession */ public function testSecuritySensitiveOperationStatus($mutableSession) { $this->logger = new \Psr\Log\NullLogger(); $user = \User::newFromName('UTSysop'); $provideUser = null; $reauth = $mutableSession ? AuthManager::SEC_REAUTH : AuthManager::SEC_FAIL; list($provider, $reset) = $this->getMockSessionProvider($mutableSession, ['provideSessionInfo']); $provider->expects($this->any())->method('provideSessionInfo')->will($this->returnCallback(function () use($provider, &$provideUser) { return new SessionInfo(SessionInfo::MIN_PRIORITY, ['provider' => $provider, 'id' => \DummySessionProvider::ID, 'persisted' => true, 'userInfo' => UserInfo::newFromUser($provideUser, true)]); })); $this->initializeManager(); $this->config->set('ReauthenticateTime', []); $this->config->set('AllowSecuritySensitiveOperationIfCannotReauthenticate', []); $provideUser = new \User(); $session = $provider->getManager()->getSessionForRequest($this->request); $this->assertSame(0, $session->getUser()->getId(), 'sanity check'); // Anonymous user => reauth $session->set('AuthManager:lastAuthId', 0); $session->set('AuthManager:lastAuthTimestamp', time() - 5); $this->assertSame($reauth, $this->manager->securitySensitiveOperationStatus('foo')); $provideUser = $user; $session = $provider->getManager()->getSessionForRequest($this->request); $this->assertSame($user->getId(), $session->getUser()->getId(), 'sanity check'); // Error for no default (only gets thrown for non-anonymous user) $session->set('AuthManager:lastAuthId', $user->getId() + 1); $session->set('AuthManager:lastAuthTimestamp', time() - 5); try { $this->manager->securitySensitiveOperationStatus('foo'); $this->fail('Expected exception not thrown'); } catch (\UnexpectedValueException $ex) { $this->assertSame($mutableSession ? '$wgReauthenticateTime lacks a default' : '$wgAllowSecuritySensitiveOperationIfCannotReauthenticate lacks a default', $ex->getMessage()); } if ($mutableSession) { $this->config->set('ReauthenticateTime', ['test' => 100, 'test2' => -1, 'default' => 10]); // Mismatched user ID $session->set('AuthManager:lastAuthId', $user->getId() + 1); $session->set('AuthManager:lastAuthTimestamp', time() - 5); $this->assertSame(AuthManager::SEC_REAUTH, $this->manager->securitySensitiveOperationStatus('foo')); $this->assertSame(AuthManager::SEC_REAUTH, $this->manager->securitySensitiveOperationStatus('test')); $this->assertSame(AuthManager::SEC_OK, $this->manager->securitySensitiveOperationStatus('test2')); // Missing time $session->set('AuthManager:lastAuthId', $user->getId()); $session->set('AuthManager:lastAuthTimestamp', null); $this->assertSame(AuthManager::SEC_REAUTH, $this->manager->securitySensitiveOperationStatus('foo')); $this->assertSame(AuthManager::SEC_REAUTH, $this->manager->securitySensitiveOperationStatus('test')); $this->assertSame(AuthManager::SEC_OK, $this->manager->securitySensitiveOperationStatus('test2')); // Recent enough to pass $session->set('AuthManager:lastAuthTimestamp', time() - 5); $this->assertSame(AuthManager::SEC_OK, $this->manager->securitySensitiveOperationStatus('foo')); // Not recent enough to pass $session->set('AuthManager:lastAuthTimestamp', time() - 20); $this->assertSame(AuthManager::SEC_REAUTH, $this->manager->securitySensitiveOperationStatus('foo')); // But recent enough for the 'test' operation $this->assertSame(AuthManager::SEC_OK, $this->manager->securitySensitiveOperationStatus('test')); } else { $this->config->set('AllowSecuritySensitiveOperationIfCannotReauthenticate', ['test' => false, 'default' => true]); $this->assertEquals(AuthManager::SEC_OK, $this->manager->securitySensitiveOperationStatus('foo')); $this->assertEquals(AuthManager::SEC_FAIL, $this->manager->securitySensitiveOperationStatus('test')); } // Test hook, all three possible values foreach ([AuthManager::SEC_OK => AuthManager::SEC_OK, AuthManager::SEC_REAUTH => $reauth, AuthManager::SEC_FAIL => AuthManager::SEC_FAIL] as $hook => $expect) { $this->hook('SecuritySensitiveOperationStatus', $this->exactly(2))->with($this->anything(), $this->anything(), $this->callback(function ($s) use($session) { return $s->getId() === $session->getId(); }), $mutableSession ? $this->equalTo(500, 1) : $this->equalTo(-1))->will($this->returnCallback(function (&$v) use($hook) { $v = $hook; return true; })); $session->set('AuthManager:lastAuthTimestamp', time() - 500); $this->assertEquals($expect, $this->manager->securitySensitiveOperationStatus('test'), "hook {$hook}"); $this->assertEquals($expect, $this->manager->securitySensitiveOperationStatus('test2'), "hook {$hook}"); $this->unhook('SecuritySensitiveOperationStatus'); } \ScopedCallback::consume($reset); }
public function provideSessionInfo(WebRequest $request) { $info = array('id' => $this->getCookie($request, $this->params['sessionName'], '')); if (!SessionManager::validateSessionId($info['id'])) { unset($info['id']); } list($userId, $userName, $token) = $this->getUserInfoFromCookies($request); if ($userId !== null) { try { $userInfo = UserInfo::newFromId($userId); } catch (\InvalidArgumentException $ex) { return null; } // Sanity check if ($userName !== null && $userInfo->getName() !== $userName) { return null; } if ($token !== null) { if (!hash_equals($userInfo->getToken(), $token)) { return null; } $info['userInfo'] = $userInfo->verified(); } elseif (isset($info['id'])) { // No point if no session ID $info['userInfo'] = $userInfo; } } if (!$info) { return null; } $info += array('provider' => $this, 'persisted' => isset($info['id']), 'forceHTTPS' => $this->getCookie($request, 'forceHTTPS', '', false)); return new SessionInfo($this->priority, $info); }
/** * @dataProvider providePersistSession * @param bool $secure * @param bool $remember */ public function testPersistSession($secure, $remember) { $this->setMwGlobals(array('wgCookieExpiration' => 100, 'wgSecureLogin' => false)); $provider = $this->getProvider('session'); $provider->setLogger(new \Psr\Log\NullLogger()); $priv = \TestingAccessWrapper::newFromObject($provider); $priv->sessionCookieOptions = array('prefix' => 'x', 'path' => 'CookiePath', 'domain' => 'CookieDomain', 'secure' => false, 'httpOnly' => true); $sessionId = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; $user = User::newFromName('UTSysop'); $this->assertFalse($user->requiresHTTPS(), 'sanity check'); $backend = new SessionBackend(new SessionId($sessionId), new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $provider, 'id' => $sessionId, 'persisted' => true, 'userInfo' => UserInfo::newFromUser($user, true), 'idIsSafe' => true)), new \EmptyBagOStuff(), new \EmptyBagOStuff(), new \Psr\Log\NullLogger(), 10); \TestingAccessWrapper::newFromObject($backend)->usePhpSessionHandling = false; $backend->setRememberUser($remember); $backend->setForceHTTPS($secure); // No cookie $priv->sessionCookieName = null; $request = new \FauxRequest(); $provider->persistSession($backend, $request); $this->assertSame(array(), $request->response()->getCookies()); // Cookie $priv->sessionCookieName = 'session'; $request = new \FauxRequest(); $time = time(); $provider->persistSession($backend, $request); $cookie = $request->response()->getCookieData('xsession'); $this->assertInternalType('array', $cookie); if (isset($cookie['expire']) && $cookie['expire'] > 0) { // Round expiry so we don't randomly fail if the seconds ticked during the test. $cookie['expire'] = round($cookie['expire'] - $time, -2); } $this->assertEquals(array('value' => $sessionId, 'expire' => null, 'path' => 'CookiePath', 'domain' => 'CookieDomain', 'secure' => $secure, 'httpOnly' => true, 'raw' => false), $cookie); $cookie = $request->response()->getCookieData('forceHTTPS'); if ($secure) { $this->assertInternalType('array', $cookie); if (isset($cookie['expire']) && $cookie['expire'] > 0) { // Round expiry so we don't randomly fail if the seconds ticked during the test. $cookie['expire'] = round($cookie['expire'] - $time, -2); } $this->assertEquals(array('value' => 'true', 'expire' => $remember ? 100 : null, 'path' => 'CookiePath', 'domain' => 'CookieDomain', 'secure' => false, 'httpOnly' => true, 'raw' => false), $cookie); } else { $this->assertNull($cookie); } // Headers sent $request = $this->getSentRequest(); $provider->persistSession($backend, $request); $this->assertSame(array(), $request->response()->getCookies()); }
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 testCheckSessionInfo() { $logger = new \TestLogger(true, function ($m) { return preg_replace('/^Session \\[\\d+\\][a-zA-Z0-9_\\\\]+<(?:null|anon|[+-]:\\d+:\\w+)>\\w+: /', 'Session X: ', $m); }); $provider = $this->getProvider(); $provider->setLogger($logger); $user = \User::newFromName('UTSysop'); $request = $this->getMock('FauxRequest', array('getIP')); $request->expects($this->any())->method('getIP')->will($this->returnValue('127.0.0.1')); $bp = \BotPassword::newFromUser($user, 'BotPasswordSessionProvider'); $data = array('provider' => $provider, 'id' => 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'userInfo' => UserInfo::newFromUser($user, true), 'persisted' => false, 'metadata' => array('centralId' => $bp->getUserCentralId(), 'appId' => $bp->getAppId(), 'token' => $bp->getToken())); $dataMD = $data['metadata']; foreach (array_keys($data['metadata']) as $key) { $data['metadata'] = $dataMD; unset($data['metadata'][$key]); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, $data); $metadata = $info->getProviderMetadata(); $this->assertFalse($provider->refreshSessionInfo($info, $request, $metadata)); $this->assertSame(array(array(LogLevel::INFO, "Session X: Missing metadata: {$key}")), $logger->getBuffer()); $logger->clearBuffer(); } $data['metadata'] = $dataMD; $data['metadata']['appId'] = 'Foobar'; $info = new SessionInfo(SessionInfo::MIN_PRIORITY, $data); $metadata = $info->getProviderMetadata(); $this->assertFalse($provider->refreshSessionInfo($info, $request, $metadata)); $this->assertSame(array(array(LogLevel::INFO, "Session X: No BotPassword for {$bp->getUserCentralId()} Foobar")), $logger->getBuffer()); $logger->clearBuffer(); $data['metadata'] = $dataMD; $data['metadata']['token'] = 'Foobar'; $info = new SessionInfo(SessionInfo::MIN_PRIORITY, $data); $metadata = $info->getProviderMetadata(); $this->assertFalse($provider->refreshSessionInfo($info, $request, $metadata)); $this->assertSame(array(array(LogLevel::INFO, 'Session X: BotPassword token check failed')), $logger->getBuffer()); $logger->clearBuffer(); $request2 = $this->getMock('FauxRequest', array('getIP')); $request2->expects($this->any())->method('getIP')->will($this->returnValue('10.0.0.1')); $data['metadata'] = $dataMD; $info = new SessionInfo(SessionInfo::MIN_PRIORITY, $data); $metadata = $info->getProviderMetadata(); $this->assertFalse($provider->refreshSessionInfo($info, $request2, $metadata)); $this->assertSame(array(array(LogLevel::INFO, 'Session X: Restrictions check failed')), $logger->getBuffer()); $logger->clearBuffer(); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, $data); $metadata = $info->getProviderMetadata(); $this->assertTrue($provider->refreshSessionInfo($info, $request, $metadata)); $this->assertSame(array(), $logger->getBuffer()); $this->assertEquals($dataMD + array('rights' => array('read')), $metadata); }
public function testNewFromUser() { // User that exists $user = User::newFromName('UTSysop'); $userinfo = UserInfo::newFromUser($user); $this->assertFalse($userinfo->isAnon()); $this->assertFalse($userinfo->isVerified()); $this->assertSame($user->getId(), $userinfo->getId()); $this->assertSame($user->getName(), $userinfo->getName()); $this->assertSame($user->getToken(true), $userinfo->getToken()); $this->assertSame($user, $userinfo->getUser()); $userinfo2 = $userinfo->verified(); $this->assertNotSame($userinfo2, $userinfo); $this->assertSame("<-:{$user->getId()}:{$user->getName()}>", (string) $userinfo); $this->assertFalse($userinfo2->isAnon()); $this->assertTrue($userinfo2->isVerified()); $this->assertSame($user->getId(), $userinfo2->getId()); $this->assertSame($user->getName(), $userinfo2->getName()); $this->assertSame($user->getToken(true), $userinfo2->getToken()); $this->assertSame($user, $userinfo2->getUser()); $this->assertSame($userinfo2, $userinfo2->verified()); $this->assertSame("<+:{$user->getId()}:{$user->getName()}>", (string) $userinfo2); $userinfo = UserInfo::newFromUser($user, true); $this->assertTrue($userinfo->isVerified()); $this->assertSame($userinfo, $userinfo->verified()); // User name that does not exist should still be non-anon $user = User::newFromName('DoesNotExist'); $this->assertSame(0, $user->getId(), 'sanity check'); $userinfo = UserInfo::newFromUser($user); $this->assertFalse($userinfo->isAnon()); $this->assertFalse($userinfo->isVerified()); $this->assertSame($user->getId(), $userinfo->getId()); $this->assertSame($user->getName(), $userinfo->getName()); $this->assertSame('', $userinfo->getToken()); $this->assertSame($user, $userinfo->getUser()); $userinfo2 = $userinfo->verified(); $this->assertNotSame($userinfo2, $userinfo); $this->assertSame("<-:{$user->getId()}:{$user->getName()}>", (string) $userinfo); $this->assertFalse($userinfo2->isAnon()); $this->assertTrue($userinfo2->isVerified()); $this->assertSame($user->getId(), $userinfo2->getId()); $this->assertSame($user->getName(), $userinfo2->getName()); $this->assertSame('', $userinfo2->getToken()); $this->assertSame($user, $userinfo2->getUser()); $this->assertSame($userinfo2, $userinfo2->verified()); $this->assertSame("<+:{$user->getId()}:{$user->getName()}>", (string) $userinfo2); $userinfo = UserInfo::newFromUser($user, true); $this->assertTrue($userinfo->isVerified()); $this->assertSame($userinfo, $userinfo->verified()); // Anonymous user gives anon $userinfo = UserInfo::newFromUser(new User(), false); $this->assertTrue($userinfo->isVerified()); $this->assertSame(0, $userinfo->getId()); $this->assertSame(null, $userinfo->getName()); }
/** * Create a new session for a request * @param User $user * @param BotPassword $bp * @param WebRequest $request * @return Session */ public function newSessionForRequest(User $user, BotPassword $bp, WebRequest $request) { $id = $this->getSessionIdFromCookie($request); $info = new SessionInfo(SessionInfo::MAX_PRIORITY, ['provider' => $this, 'id' => $id, 'userInfo' => UserInfo::newFromUser($user, true), 'persisted' => $id !== null, 'metadata' => ['centralId' => $bp->getUserCentralId(), 'appId' => $bp->getAppId(), 'token' => $bp->getToken(), 'rights' => \MWGrants::getGrantRights($bp->getGrants())]]); $session = $this->getManager()->getSessionFromInfo($info, $request); $session->persist(); return $session; }
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(); }
public function testCheckSessionInfo() { $logger = new \TestLogger(true); $provider = $this->getProvider(); $provider->setLogger($logger); $user = static::getTestSysop()->getUser(); $request = $this->getMock('FauxRequest', ['getIP']); $request->expects($this->any())->method('getIP')->will($this->returnValue('127.0.0.1')); $bp = \BotPassword::newFromUser($user, 'BotPasswordSessionProvider'); $data = ['provider' => $provider, 'id' => 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'userInfo' => UserInfo::newFromUser($user, true), 'persisted' => false, 'metadata' => ['centralId' => $bp->getUserCentralId(), 'appId' => $bp->getAppId(), 'token' => $bp->getToken()]]; $dataMD = $data['metadata']; foreach (array_keys($data['metadata']) as $key) { $data['metadata'] = $dataMD; unset($data['metadata'][$key]); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, $data); $metadata = $info->getProviderMetadata(); $this->assertFalse($provider->refreshSessionInfo($info, $request, $metadata)); $this->assertSame([[LogLevel::INFO, 'Session "{session}": Missing metadata: {missing}']], $logger->getBuffer()); $logger->clearBuffer(); } $data['metadata'] = $dataMD; $data['metadata']['appId'] = 'Foobar'; $info = new SessionInfo(SessionInfo::MIN_PRIORITY, $data); $metadata = $info->getProviderMetadata(); $this->assertFalse($provider->refreshSessionInfo($info, $request, $metadata)); $this->assertSame([[LogLevel::INFO, 'Session "{session}": No BotPassword for {centralId} {appId}']], $logger->getBuffer()); $logger->clearBuffer(); $data['metadata'] = $dataMD; $data['metadata']['token'] = 'Foobar'; $info = new SessionInfo(SessionInfo::MIN_PRIORITY, $data); $metadata = $info->getProviderMetadata(); $this->assertFalse($provider->refreshSessionInfo($info, $request, $metadata)); $this->assertSame([[LogLevel::INFO, 'Session "{session}": BotPassword token check failed']], $logger->getBuffer()); $logger->clearBuffer(); $request2 = $this->getMock('FauxRequest', ['getIP']); $request2->expects($this->any())->method('getIP')->will($this->returnValue('10.0.0.1')); $data['metadata'] = $dataMD; $info = new SessionInfo(SessionInfo::MIN_PRIORITY, $data); $metadata = $info->getProviderMetadata(); $this->assertFalse($provider->refreshSessionInfo($info, $request2, $metadata)); $this->assertSame([[LogLevel::INFO, 'Session "{session}": Restrictions check failed']], $logger->getBuffer()); $logger->clearBuffer(); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, $data); $metadata = $info->getProviderMetadata(); $this->assertTrue($provider->refreshSessionInfo($info, $request, $metadata)); $this->assertSame([], $logger->getBuffer()); $this->assertEquals($dataMD + ['rights' => ['read']], $metadata); }
public function provideSessionInfo(WebRequest $request) { $info = array('id' => $this->getCookie($request, $this->params['sessionName'], ''), 'provider' => $this, 'forceHTTPS' => $this->getCookie($request, 'forceHTTPS', '', false)); if (!SessionManager::validateSessionId($info['id'])) { unset($info['id']); } $info['persisted'] = isset($info['id']); list($userId, $userName, $token) = $this->getUserInfoFromCookies($request); if ($userId !== null) { try { $userInfo = UserInfo::newFromId($userId); } catch (\InvalidArgumentException $ex) { return null; } // Sanity check if ($userName !== null && $userInfo->getName() !== $userName) { return null; } if ($token !== null) { if (!hash_equals($userInfo->getToken(), $token)) { return null; } $info['userInfo'] = $userInfo->verified(); } elseif (isset($info['id'])) { $info['userInfo'] = $userInfo; } else { // No point in returning, loadSessionInfoFromStore() will // reject it anyway. return null; } } elseif (isset($info['id'])) { // No UserID cookie, so insist that the session is anonymous. $info['userInfo'] = UserInfo::newAnonymous(); } else { // No session ID and no user is the same as an empty session, so // there's no point. return null; } return new SessionInfo($this->priority, $info); }
public function newSessionInfo($id = null) { return new SessionInfo(SessionInfo::MIN_PRIORITY, array('id' => $id, 'idIsSafe' => true, 'provider' => $this, 'persisted' => false, 'userInfo' => UserInfo::newAnonymous())); }