namespace MediaWiki\Session;
use Psr\Log\LoggerInterface;
-use Psr\Log\LogLevel;
use BagOStuff;
use CachedBagOStuff;
use Config;
self::$globalSessionRequest = null;
}
- /**
- * Do a sanity check to make sure the session is not used from many different IP addresses
- * and store some data for later sanity checks.
- * FIXME remove this once SessionManager is considered stable
- * @private For use in Setup.php only
- * @param Session $session Defaults to the global session.
- */
- public function checkIpLimits( Session $session = null ) {
- $session = $session ?: self::getGlobalSession();
-
- try {
- $ip = $session->getRequest()->getIP();
- } catch ( \MWException $e ) {
- return;
- }
- if ( $ip === '127.0.0.1' || \IP::isConfiguredProxy( $ip ) ) {
- return;
- }
- $now = time();
-
- // Record (and possibly log) that the IP is using the current session.
- // Don't touch the stored data unless we are adding a new IP or re-adding an expired one.
- // This is slightly inaccurate (when an existing IP is seen again, the expiry is not
- // extended) but that shouldn't make much difference and limits the session write frequency
- // to # of IPs / $wgSuspiciousIpExpiry.
- $data = $session->get( 'SessionManager-ip', [] );
- if (
- !isset( $data[$ip] )
- || $data[$ip] < $now
- ) {
- $data[$ip] = time() + $this->config->get( 'SuspiciousIpExpiry' );
- foreach ( $data as $key => $expires ) {
- if ( $expires < $now ) {
- unset( $data[$key] );
- }
- }
- $session->set( 'SessionManager-ip', $data );
-
- $logger = \MediaWiki\Logger\LoggerFactory::getInstance( 'session-ip' );
- $logLevel = count( $data ) >= $this->config->get( 'SuspiciousIpPerSessionLimit' )
- ? LogLevel::WARNING : ( count( $data ) === 1 ? LogLevel::DEBUG : LogLevel::INFO );
- $logger->log(
- $logLevel,
- 'Same session used from {count} IPs',
- [
- 'count' => count( $data ),
- 'ips' => $data,
- 'session' => $session->getId(),
- 'user' => $session->getUser()->getName(),
- 'persistent' => $session->isPersistent(),
- ]
- );
- }
-
- // Now do the same thing globally for the current user.
- // We are using the object cache and assume it is shared between all wikis of a farm,
- // and further assume that the same name belongs to the same user on all wikis. (It's either
- // that or a central ID lookup which would mean an extra SQL query on every request.)
- if ( $session->getUser()->isLoggedIn() ) {
- $userKey = 'SessionManager-ip:' . md5( $session->getUser()->getName() );
- $data = $this->store->get( $userKey ) ?: [];
- if (
- !isset( $data[$ip] )
- || $data[$ip] < $now
- ) {
- $data[$ip] = time() + $this->config->get( 'SuspiciousIpExpiry' );
- foreach ( $data as $key => $expires ) {
- if ( $expires < $now ) {
- unset( $data[$key] );
- }
- }
- $this->store->set( $userKey, $data, $this->config->get( 'SuspiciousIpExpiry' ) );
- $logger = \MediaWiki\Logger\LoggerFactory::getInstance( 'session-ip' );
- $logLevel = count( $data ) >= $this->config->get( 'SuspiciousIpPerUserLimit' )
- ? LogLevel::WARNING : ( count( $data ) === 1 ? LogLevel::DEBUG : LogLevel::INFO );
- $logger->log(
- $logLevel,
- 'Same user had sessions from {count} IPs',
- [
- 'count' => count( $data ),
- 'ips' => $data,
- 'session' => $session->getId(),
- 'user' => $session->getUser()->getName(),
- 'persistent' => $session->isPersistent(),
- ]
- );
- }
- }
- }
-
/**@}*/
}
namespace MediaWiki\Session;
use AuthPlugin;
-use MediaWiki\Logger\LoggerFactory;
use MediaWikiTestCase;
use Psr\Log\LogLevel;
use User;
], $logger->getBuffer() );
$logger->clearBuffer();
}
-
- /**
- * @dataProvider provideCheckIpLimits
- */
- public function testCheckIpLimits( $ip, $sessionData, $userData, $logLevel1, $logLevel2 ) {
- $this->setMwGlobals( [
- 'wgSuspiciousIpPerSessionLimit' => 5,
- 'wgSuspiciousIpPerUserLimit' => 10,
- 'wgSuspiciousIpExpiry' => 600,
- 'wgSquidServers' => [ '11.22.33.44' ],
- ] );
- $manager = new SessionManager();
- $logger = $this->getMock( '\Psr\Log\LoggerInterface' );
- $this->setLogger( 'session-ip', $logger );
- $request = new \FauxRequest();
- $request->setIP( $ip );
-
- $session = $manager->getSessionForRequest( $request );
- /** @var SessionBackend $backend */
- $backend = \TestingAccessWrapper::newFromObject( $session )->backend;
- $data = &$backend->getData();
- $data = [ 'SessionManager-ip' => $sessionData ];
- $backend->setUser( User::newFromName( 'UTSysop' ) );
- $manager = \TestingAccessWrapper::newFromObject( $manager );
- $manager->store->set( 'SessionManager-ip:' . md5( 'UTSysop' ), $userData );
-
- $logger->expects( $this->exactly( isset( $logLevel1 ) + isset( $logLevel2 ) ) )->method( 'log' );
- if ( $logLevel1 ) {
- $logger->expects( $this->at( 0 ) )->method( 'log' )->with( $logLevel1,
- 'Same session used from {count} IPs', $this->isType( 'array' ) );
- }
- if ( $logLevel2 ) {
- $logger->expects( $this->at( isset( $logLevel1 ) ) )->method( 'log' )->with( $logLevel2,
- 'Same user had sessions from {count} IPs', $this->isType( 'array' ) );
- }
-
- $manager->checkIpLimits( $session );
- }
-
- public function provideCheckIpLimits() {
- $future = time() + 1000;
- $past = time() - 1000;
- return [
- // DEBUG log for first new IP
- [ '1.2.3.4', [], [], LogLevel::DEBUG, LogLevel::DEBUG ],
- // no log for same IP
- [ '1.2.3.4', [ '1.2.3.4' => $future ], [ '1.2.3.4' => $future ],
- null, null ],
- [ '1.2.3.4', [], [ '1.2.3.4' => $future ],
- LogLevel::DEBUG, null ],
- // INFO log for second new IP
- [ '1.2.3.4', [ '10.20.30.40' => $future ], [ '10.20.30.40' => $future ],
- LogLevel::INFO, LogLevel::INFO ],
- // WARNING above $wgSuspiciousIpPerSessionLimit
- [ '1.2.3.4', array_fill_keys( range( 1, 5 ), $future ),
- array_fill_keys( range( 1, 5 ), $future ), LogLevel::WARNING, LogLevel::INFO ],
- // WARNING above $wgSuspiciousIpPerUserLimit
-
- [ '1.2.3.4', array_fill_keys( range( 1, 2 ), $future ),
- array_fill_keys( range( 1, 12 ), $future ), LogLevel::INFO, LogLevel::WARNING ],
- // expired keys ignored
- [ '1.2.3.4', [ '1.2.3.4' => $past ], [ '1.2.3.4' => $past ],
- LogLevel::DEBUG, LogLevel::DEBUG ],
- [ '1.2.3.4', array_fill_keys( range( 1, 5 ), $past ),
- array_fill_keys( range( 1, 5 ), $past ), LogLevel::DEBUG, LogLevel::DEBUG ],
- // special IPs are ignored
- [ '127.0.0.1', [], [], null, null ],
- [ '11.22.33.44', [], [], null, null ],
- ];
- }
}