/** * Returns a non-persistent backend that thinks it has at least one session active * @param User|null $user */ protected function getBackend(User $user = null) { if (!$this->config) { $this->config = new \HashConfig(); $this->manager = null; } if (!$this->store) { $this->store = new TestBagOStuff(); $this->manager = null; } $logger = new \Psr\Log\NullLogger(); if (!$this->manager) { $this->manager = new SessionManager(array('store' => $this->store, 'logger' => $logger, 'config' => $this->config)); } if (!$this->provider) { $this->provider = new \DummySessionProvider(); } $this->provider->setLogger($logger); $this->provider->setConfig($this->config); $this->provider->setManager($this->manager); $info = new SessionInfo(SessionInfo::MIN_PRIORITY, array('provider' => $this->provider, 'id' => self::SESSIONID, 'persisted' => true, 'userInfo' => UserInfo::newFromUser($user ?: new User(), true), 'idIsSafe' => true)); $id = new SessionId($info->getId()); $backend = new SessionBackend($id, $info, $this->store, $this->store, $logger, 10); $priv = \TestingAccessWrapper::newFromObject($backend); $priv->persist = false; $priv->requests = array(100 => new \FauxRequest()); $priv->usePhpSessionHandling = false; $manager = \TestingAccessWrapper::newFromObject($this->manager); $manager->allSessionBackends = array($backend->getId() => $backend); $manager->allSessionIds = array($backend->getId() => $id); $manager->sessionProviders = array((string) $this->provider => $this->provider); return $backend; }
/** * @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); }
/** * @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 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()); }
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); }
/** * 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 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); }