I assume it’s merely an oversight that the REST API wasn’t added here before, and that there’s no particular reason why it shouldn’t be possible to use bot passwords with the REST API. In the test, apart from checking an additional constant, also add a TODO that the test shouldn’t define the constant at all, since this is a side-effect of the test that affects every other test which happens to run later in the same PHP process, and which cannot be undone. Bug: T284020 Change-Id: I710279a841428d8be177b4b90a6893d4f4f94875
360 lines
11 KiB
PHP
360 lines
11 KiB
PHP
<?php
|
|
|
|
namespace MediaWiki\Session;
|
|
|
|
use MediaWikiIntegrationTestCase;
|
|
use MultiConfig;
|
|
use Psr\Log\LogLevel;
|
|
use Wikimedia\TestingAccessWrapper;
|
|
|
|
/**
|
|
* @group Session
|
|
* @group Database
|
|
* @covers MediaWiki\Session\BotPasswordSessionProvider
|
|
*/
|
|
class BotPasswordSessionProviderTest extends MediaWikiIntegrationTestCase {
|
|
use SessionProviderTestTrait;
|
|
|
|
private $config;
|
|
|
|
private function getProvider( $name = null, $prefix = null ) {
|
|
global $wgSessionProviders;
|
|
|
|
$params = [
|
|
'priority' => 40,
|
|
'sessionCookieName' => $name,
|
|
'sessionCookieOptions' => [],
|
|
];
|
|
if ( $prefix !== null ) {
|
|
$params['sessionCookieOptions']['prefix'] = $prefix;
|
|
}
|
|
|
|
if ( !$this->config ) {
|
|
$this->config = new \HashConfig( [
|
|
'CookiePrefix' => 'wgCookiePrefix',
|
|
'EnableBotPasswords' => true,
|
|
'BotPasswordsDatabase' => false,
|
|
'SessionProviders' => $wgSessionProviders + [
|
|
BotPasswordSessionProvider::class => [
|
|
'class' => BotPasswordSessionProvider::class,
|
|
'args' => [ $params ],
|
|
'services' => [ 'GrantsInfo' ],
|
|
]
|
|
],
|
|
] );
|
|
}
|
|
$manager = new SessionManager( [
|
|
'config' => new MultiConfig( [ $this->config, $this->getServiceContainer()->getMainConfig() ] ),
|
|
'logger' => new \Psr\Log\NullLogger,
|
|
'store' => new TestBagOStuff,
|
|
] );
|
|
|
|
return $manager->getProvider( BotPasswordSessionProvider::class );
|
|
}
|
|
|
|
protected function setUp(): void {
|
|
parent::setUp();
|
|
|
|
$this->setMwGlobals( [
|
|
'wgEnableBotPasswords' => true,
|
|
'wgBotPasswordsDatabase' => false,
|
|
'wgCentralIdLookupProvider' => 'local',
|
|
'wgGrantPermissions' => [
|
|
'test' => [ 'read' => true ],
|
|
],
|
|
] );
|
|
}
|
|
|
|
public function addDBDataOnce() {
|
|
$passwordFactory = $this->getServiceContainer()->getPasswordFactory();
|
|
$passwordHash = $passwordFactory->newFromPlaintext( 'foobaz' );
|
|
|
|
$sysop = static::getTestSysop()->getUser();
|
|
$userId = $this->getServiceContainer()
|
|
->getCentralIdLookupFactory()
|
|
->getLookup( 'local' )
|
|
->centralIdFromName( $sysop->getName() );
|
|
|
|
$dbw = wfGetDB( DB_PRIMARY );
|
|
$dbw->delete(
|
|
'bot_passwords',
|
|
[ 'bp_user' => $userId, 'bp_app_id' => 'BotPasswordSessionProvider' ],
|
|
__METHOD__
|
|
);
|
|
$dbw->insert(
|
|
'bot_passwords',
|
|
[
|
|
'bp_user' => $userId,
|
|
'bp_app_id' => 'BotPasswordSessionProvider',
|
|
'bp_password' => $passwordHash->toString(),
|
|
'bp_token' => 'token!',
|
|
'bp_restrictions' => '{"IPAddresses":["127.0.0.0/8"]}',
|
|
'bp_grants' => '["test"]',
|
|
],
|
|
__METHOD__
|
|
);
|
|
}
|
|
|
|
public function testConstructor() {
|
|
$grantsInfo = $this->getServiceContainer()->getGrantsInfo();
|
|
|
|
try {
|
|
$provider = new BotPasswordSessionProvider( $grantsInfo );
|
|
$this->fail( 'Expected exception not thrown' );
|
|
} catch ( \InvalidArgumentException $ex ) {
|
|
$this->assertSame(
|
|
'MediaWiki\\Session\\BotPasswordSessionProvider::__construct: priority must be specified',
|
|
$ex->getMessage()
|
|
);
|
|
}
|
|
|
|
try {
|
|
$provider = new BotPasswordSessionProvider(
|
|
$grantsInfo,
|
|
[
|
|
'priority' => SessionInfo::MIN_PRIORITY - 1
|
|
]
|
|
);
|
|
$this->fail( 'Expected exception not thrown' );
|
|
} catch ( \InvalidArgumentException $ex ) {
|
|
$this->assertSame(
|
|
'MediaWiki\\Session\\BotPasswordSessionProvider::__construct: Invalid priority',
|
|
$ex->getMessage()
|
|
);
|
|
}
|
|
|
|
try {
|
|
$provider = new BotPasswordSessionProvider(
|
|
$grantsInfo,
|
|
[
|
|
'priority' => SessionInfo::MAX_PRIORITY + 1
|
|
]
|
|
);
|
|
$this->fail( 'Expected exception not thrown' );
|
|
} catch ( \InvalidArgumentException $ex ) {
|
|
$this->assertSame(
|
|
'MediaWiki\\Session\\BotPasswordSessionProvider::__construct: Invalid priority',
|
|
$ex->getMessage()
|
|
);
|
|
}
|
|
|
|
$provider = new BotPasswordSessionProvider(
|
|
$grantsInfo,
|
|
[ 'priority' => 40 ]
|
|
);
|
|
$priv = TestingAccessWrapper::newFromObject( $provider );
|
|
$this->assertSame( 40, $priv->priority );
|
|
$this->assertSame( '_BPsession', $priv->sessionCookieName );
|
|
$this->assertSame( [], $priv->sessionCookieOptions );
|
|
|
|
$provider = new BotPasswordSessionProvider(
|
|
$grantsInfo,
|
|
[
|
|
'priority' => 40,
|
|
'sessionCookieName' => null,
|
|
]
|
|
);
|
|
$priv = TestingAccessWrapper::newFromObject( $provider );
|
|
$this->assertSame( '_BPsession', $priv->sessionCookieName );
|
|
|
|
$provider = new BotPasswordSessionProvider(
|
|
$grantsInfo,
|
|
[
|
|
'priority' => 40,
|
|
'sessionCookieName' => 'Foo',
|
|
'sessionCookieOptions' => [ 'Bar' ],
|
|
]
|
|
);
|
|
$priv = TestingAccessWrapper::newFromObject( $provider );
|
|
$this->assertSame( 'Foo', $priv->sessionCookieName );
|
|
$this->assertSame( [ 'Bar' ], $priv->sessionCookieOptions );
|
|
}
|
|
|
|
public function testBasics() {
|
|
$provider = $this->getProvider();
|
|
|
|
$this->assertTrue( $provider->persistsSessionId() );
|
|
$this->assertFalse( $provider->canChangeUser() );
|
|
|
|
$this->assertNull( $provider->newSessionInfo() );
|
|
$this->assertNull( $provider->newSessionInfo( 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' ) );
|
|
}
|
|
|
|
public function testProvideSessionInfo() {
|
|
$provider = $this->getProvider();
|
|
$request = new \FauxRequest;
|
|
$request->setCookie( '_BPsession', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'wgCookiePrefix' );
|
|
|
|
if ( !defined( 'MW_API' ) && !defined( 'MW_REST_API' ) ) {
|
|
$this->assertNull( $provider->provideSessionInfo( $request ) );
|
|
define( 'MW_API', 1 ); // TODO this irreversibly affects all future tests!
|
|
}
|
|
|
|
$info = $provider->provideSessionInfo( $request );
|
|
$this->assertInstanceOf( SessionInfo::class, $info );
|
|
$this->assertSame( 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', $info->getId() );
|
|
|
|
$this->config->set( 'EnableBotPasswords', false );
|
|
$this->assertNull( $provider->provideSessionInfo( $request ) );
|
|
$this->config->set( 'EnableBotPasswords', true );
|
|
|
|
$this->assertNull( $provider->provideSessionInfo( new \FauxRequest ) );
|
|
}
|
|
|
|
public function testNewSessionInfoForRequest() {
|
|
$provider = $this->getProvider();
|
|
$user = static::getTestSysop()->getUser();
|
|
$request = $this->getMockBuilder( \FauxRequest::class )
|
|
->onlyMethods( [ 'getIP' ] )->getMock();
|
|
$request->method( 'getIP' )
|
|
->willReturn( '127.0.0.1' );
|
|
$bp = \BotPassword::newFromUser( $user, 'BotPasswordSessionProvider' );
|
|
|
|
$session = $provider->newSessionForRequest( $user, $bp, $request );
|
|
$this->assertInstanceOf( Session::class, $session );
|
|
|
|
$this->assertEquals( $session->getId(), $request->getSession()->getId() );
|
|
$this->assertEquals( $user->getName(), $session->getUser()->getName() );
|
|
|
|
$this->assertEquals( [
|
|
'centralId' => $bp->getUserCentralId(),
|
|
'appId' => $bp->getAppId(),
|
|
'token' => $bp->getToken(),
|
|
'rights' => [ 'read' ],
|
|
], $session->getProviderMetadata() );
|
|
|
|
$this->assertEquals( [ 'read' ], $session->getAllowedUserRights() );
|
|
}
|
|
|
|
public function testCheckSessionInfo() {
|
|
$logger = new \TestLogger( true );
|
|
$provider = $this->getProvider();
|
|
$this->initProvider( $provider, $logger );
|
|
|
|
$user = static::getTestSysop()->getUser();
|
|
$request = $this->getMockBuilder( \FauxRequest::class )
|
|
->onlyMethods( [ 'getIP' ] )->getMock();
|
|
$request->method( 'getIP' )
|
|
->willReturn( '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->getMockBuilder( \FauxRequest::class )
|
|
->onlyMethods( [ 'getIP' ] )->getMock();
|
|
$request2->method( 'getIP' )
|
|
->willReturn( '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 testGetAllowedUserRights() {
|
|
$logger = new \TestLogger( true );
|
|
$provider = $this->getProvider();
|
|
$this->initProvider( $provider, $logger );
|
|
|
|
$backend = TestUtils::getDummySessionBackend();
|
|
$backendPriv = TestingAccessWrapper::newFromObject( $backend );
|
|
|
|
try {
|
|
$provider->getAllowedUserRights( $backend );
|
|
$this->fail( 'Expected exception not thrown' );
|
|
} catch ( \InvalidArgumentException $ex ) {
|
|
$this->assertSame( 'Backend\'s provider isn\'t $this', $ex->getMessage() );
|
|
}
|
|
|
|
$backendPriv->provider = $provider;
|
|
$backendPriv->providerMetadata = [ 'rights' => [ 'foo', 'bar', 'baz' ] ];
|
|
$this->assertSame( [ 'foo', 'bar', 'baz' ], $provider->getAllowedUserRights( $backend ) );
|
|
$this->assertSame( [], $logger->getBuffer() );
|
|
|
|
$backendPriv->providerMetadata = [ 'foo' => 'bar' ];
|
|
$this->assertSame( [], $provider->getAllowedUserRights( $backend ) );
|
|
$this->assertSame( [
|
|
[
|
|
LogLevel::DEBUG,
|
|
'MediaWiki\\Session\\BotPasswordSessionProvider::getAllowedUserRights: ' .
|
|
'No provider metadata, returning no rights allowed'
|
|
]
|
|
], $logger->getBuffer() );
|
|
$logger->clearBuffer();
|
|
|
|
$backendPriv->providerMetadata = [ 'rights' => 'bar' ];
|
|
$this->assertSame( [], $provider->getAllowedUserRights( $backend ) );
|
|
$this->assertSame( [
|
|
[
|
|
LogLevel::DEBUG,
|
|
'MediaWiki\\Session\\BotPasswordSessionProvider::getAllowedUserRights: ' .
|
|
'No provider metadata, returning no rights allowed'
|
|
]
|
|
], $logger->getBuffer() );
|
|
$logger->clearBuffer();
|
|
|
|
$backendPriv->providerMetadata = null;
|
|
$this->assertSame( [], $provider->getAllowedUserRights( $backend ) );
|
|
$this->assertSame( [
|
|
[
|
|
LogLevel::DEBUG,
|
|
'MediaWiki\\Session\\BotPasswordSessionProvider::getAllowedUserRights: ' .
|
|
'No provider metadata, returning no rights allowed'
|
|
]
|
|
], $logger->getBuffer() );
|
|
$logger->clearBuffer();
|
|
}
|
|
}
|