Instead of having basically every caller do: $pf = new PasswordFactory(); $pf->init( RequestContext::getMain()->getConfig() ); Just create a single PasswordFactory via MediaWikiServices and pass that around. Things that want to use their own config can still pass settings via the new constructor. This will eventually let us remove the init() function, removing the only hard dependency upon MediaWiki, to make it easier to librarize (T89742). Change-Id: I0fc7520dc023b11a7fa66083eff7b88ebfe49c7b
301 lines
8 KiB
PHP
301 lines
8 KiB
PHP
<?php
|
|
|
|
use MediaWiki\MediaWikiServices;
|
|
use Wikimedia\TestingAccessWrapper;
|
|
|
|
/**
|
|
* @group API
|
|
* @group Database
|
|
* @group medium
|
|
*
|
|
* @covers ApiLogin
|
|
*/
|
|
class ApiLoginTest extends ApiTestCase {
|
|
|
|
/**
|
|
* Test result of attempted login with an empty username
|
|
*/
|
|
public function testApiLoginNoName() {
|
|
$session = [
|
|
'wsTokenSecrets' => [ 'login' => 'foobar' ],
|
|
];
|
|
$data = $this->doApiRequest( [ 'action' => 'login',
|
|
'lgname' => '', 'lgpassword' => self::$users['sysop']->getPassword(),
|
|
'lgtoken' => (string)( new MediaWiki\Session\Token( 'foobar', '' ) )
|
|
], $session );
|
|
$this->assertEquals( 'Failed', $data[0]['login']['result'] );
|
|
}
|
|
|
|
public function testApiLoginBadPass() {
|
|
global $wgServer;
|
|
|
|
$user = self::$users['sysop'];
|
|
$userName = $user->getUser()->getName();
|
|
$user->getUser()->logout();
|
|
|
|
if ( !isset( $wgServer ) ) {
|
|
$this->markTestIncomplete( 'This test needs $wgServer to be set in LocalSettings.php' );
|
|
}
|
|
$ret = $this->doApiRequest( [
|
|
"action" => "login",
|
|
"lgname" => $userName,
|
|
"lgpassword" => "bad",
|
|
] );
|
|
|
|
$result = $ret[0];
|
|
|
|
$this->assertNotInternalType( "bool", $result );
|
|
$a = $result["login"]["result"];
|
|
$this->assertEquals( "NeedToken", $a );
|
|
|
|
$token = $result["login"]["token"];
|
|
|
|
$ret = $this->doApiRequest(
|
|
[
|
|
"action" => "login",
|
|
"lgtoken" => $token,
|
|
"lgname" => $userName,
|
|
"lgpassword" => "badnowayinhell",
|
|
],
|
|
$ret[2]
|
|
);
|
|
|
|
$result = $ret[0];
|
|
|
|
$this->assertNotInternalType( "bool", $result );
|
|
$a = $result["login"]["result"];
|
|
|
|
$this->assertEquals( 'Failed', $a );
|
|
}
|
|
|
|
public function testApiLoginGoodPass() {
|
|
global $wgServer;
|
|
|
|
if ( !isset( $wgServer ) ) {
|
|
$this->markTestIncomplete( 'This test needs $wgServer to be set in LocalSettings.php' );
|
|
}
|
|
|
|
$user = self::$users['sysop'];
|
|
$userName = $user->getUser()->getName();
|
|
$password = $user->getPassword();
|
|
$user->getUser()->logout();
|
|
|
|
$ret = $this->doApiRequest( [
|
|
"action" => "login",
|
|
"lgname" => $userName,
|
|
"lgpassword" => $password,
|
|
]
|
|
);
|
|
|
|
$result = $ret[0];
|
|
$this->assertNotInternalType( "bool", $result );
|
|
$this->assertNotInternalType( "null", $result["login"] );
|
|
|
|
$a = $result["login"]["result"];
|
|
$this->assertEquals( "NeedToken", $a );
|
|
$token = $result["login"]["token"];
|
|
|
|
$ret = $this->doApiRequest(
|
|
[
|
|
"action" => "login",
|
|
"lgtoken" => $token,
|
|
"lgname" => $userName,
|
|
"lgpassword" => $password,
|
|
],
|
|
$ret[2]
|
|
);
|
|
|
|
$result = $ret[0];
|
|
|
|
$this->assertNotInternalType( "bool", $result );
|
|
$a = $result["login"]["result"];
|
|
|
|
$this->assertEquals( "Success", $a );
|
|
}
|
|
|
|
/**
|
|
* @group Broken
|
|
*/
|
|
public function testApiLoginGotCookie() {
|
|
$this->markTestIncomplete( "The server can't do external HTTP requests, "
|
|
. "and the internal one won't give cookies" );
|
|
|
|
global $wgServer, $wgScriptPath;
|
|
|
|
if ( !isset( $wgServer ) ) {
|
|
$this->markTestIncomplete( 'This test needs $wgServer to be set in LocalSettings.php' );
|
|
}
|
|
$user = self::$users['sysop'];
|
|
$userName = $user->getUser()->getName();
|
|
$password = $user->getPassword();
|
|
|
|
$req = MWHttpRequest::factory( self::$apiUrl . "?action=login&format=xml",
|
|
[ "method" => "POST",
|
|
"postData" => [
|
|
"lgname" => $userName,
|
|
"lgpassword" => $password
|
|
]
|
|
],
|
|
__METHOD__
|
|
);
|
|
$req->execute();
|
|
|
|
libxml_use_internal_errors( true );
|
|
$sxe = simplexml_load_string( $req->getContent() );
|
|
$this->assertNotInternalType( "bool", $sxe );
|
|
$this->assertThat( $sxe, $this->isInstanceOf( SimpleXMLElement::class ) );
|
|
$this->assertNotInternalType( "null", $sxe->login[0] );
|
|
|
|
$a = $sxe->login[0]->attributes()->result[0];
|
|
$this->assertEquals( ' result="NeedToken"', $a->asXML() );
|
|
$token = (string)$sxe->login[0]->attributes()->token;
|
|
|
|
$req->setData( [
|
|
"lgtoken" => $token,
|
|
"lgname" => $userName,
|
|
"lgpassword" => $password ] );
|
|
$req->execute();
|
|
|
|
$cj = $req->getCookieJar();
|
|
$serverName = parse_url( $wgServer, PHP_URL_HOST );
|
|
$this->assertNotEquals( false, $serverName );
|
|
$serializedCookie = $cj->serializeToHttpRequest( $wgScriptPath, $serverName );
|
|
$this->assertNotEquals( '', $serializedCookie );
|
|
$this->assertRegExp(
|
|
'/_session=[^;]*; .*UserID=[0-9]*; .*UserName=' . $user->userName . '; .*Token=/',
|
|
$serializedCookie
|
|
);
|
|
}
|
|
|
|
public function testRunLogin() {
|
|
$user = self::$users['sysop'];
|
|
$userName = $user->getUser()->getName();
|
|
$password = $user->getPassword();
|
|
|
|
$data = $this->doApiRequest( [
|
|
'action' => 'login',
|
|
'lgname' => $userName,
|
|
'lgpassword' => $password ] );
|
|
|
|
$this->assertArrayHasKey( "login", $data[0] );
|
|
$this->assertArrayHasKey( "result", $data[0]['login'] );
|
|
$this->assertEquals( "NeedToken", $data[0]['login']['result'] );
|
|
$token = $data[0]['login']['token'];
|
|
|
|
$data = $this->doApiRequest( [
|
|
'action' => 'login',
|
|
"lgtoken" => $token,
|
|
"lgname" => $userName,
|
|
"lgpassword" => $password ], $data[2] );
|
|
|
|
$this->assertArrayHasKey( "login", $data[0] );
|
|
$this->assertArrayHasKey( "result", $data[0]['login'] );
|
|
$this->assertEquals( "Success", $data[0]['login']['result'] );
|
|
}
|
|
|
|
public function testBotPassword() {
|
|
global $wgServer, $wgSessionProviders;
|
|
|
|
if ( !isset( $wgServer ) ) {
|
|
$this->markTestIncomplete( 'This test needs $wgServer to be set in LocalSettings.php' );
|
|
}
|
|
|
|
$this->setMwGlobals( [
|
|
'wgSessionProviders' => array_merge( $wgSessionProviders, [
|
|
[
|
|
'class' => MediaWiki\Session\BotPasswordSessionProvider::class,
|
|
'args' => [ [ 'priority' => 40 ] ],
|
|
]
|
|
] ),
|
|
'wgEnableBotPasswords' => true,
|
|
'wgBotPasswordsDatabase' => false,
|
|
'wgCentralIdLookupProvider' => 'local',
|
|
'wgGrantPermissions' => [
|
|
'test' => [ 'read' => true ],
|
|
],
|
|
] );
|
|
|
|
// Make sure our session provider is present
|
|
$manager = TestingAccessWrapper::newFromObject( MediaWiki\Session\SessionManager::singleton() );
|
|
if ( !isset( $manager->sessionProviders[MediaWiki\Session\BotPasswordSessionProvider::class] ) ) {
|
|
$tmp = $manager->sessionProviders;
|
|
$manager->sessionProviders = null;
|
|
$manager->sessionProviders = $tmp + $manager->getProviders();
|
|
}
|
|
$this->assertNotNull(
|
|
MediaWiki\Session\SessionManager::singleton()->getProvider(
|
|
MediaWiki\Session\BotPasswordSessionProvider::class
|
|
),
|
|
'sanity check'
|
|
);
|
|
|
|
$user = self::$users['sysop'];
|
|
$centralId = CentralIdLookup::factory()->centralIdFromLocalUser( $user->getUser() );
|
|
$this->assertNotEquals( 0, $centralId, 'sanity check' );
|
|
|
|
$password = 'ngfhmjm64hv0854493hsj5nncjud2clk';
|
|
$passwordFactory = MediaWikiServices::getInstance()->getPasswordFactory();
|
|
// A is unsalted MD5 (thus fast) ... we don't care about security here, this is test only
|
|
$passwordHash = $passwordFactory->newFromPlaintext( $password );
|
|
|
|
$dbw = wfGetDB( DB_MASTER );
|
|
$dbw->insert(
|
|
'bot_passwords',
|
|
[
|
|
'bp_user' => $centralId,
|
|
'bp_app_id' => 'foo',
|
|
'bp_password' => $passwordHash->toString(),
|
|
'bp_token' => '',
|
|
'bp_restrictions' => MWRestrictions::newDefault()->toJson(),
|
|
'bp_grants' => '["test"]',
|
|
],
|
|
__METHOD__
|
|
);
|
|
|
|
$lgName = $user->getUser()->getName() . BotPassword::getSeparator() . 'foo';
|
|
|
|
$ret = $this->doApiRequest( [
|
|
'action' => 'login',
|
|
'lgname' => $lgName,
|
|
'lgpassword' => $password,
|
|
] );
|
|
|
|
$result = $ret[0];
|
|
$this->assertNotInternalType( 'bool', $result );
|
|
$this->assertNotInternalType( 'null', $result['login'] );
|
|
|
|
$a = $result['login']['result'];
|
|
$this->assertEquals( 'NeedToken', $a );
|
|
$token = $result['login']['token'];
|
|
|
|
$ret = $this->doApiRequest( [
|
|
'action' => 'login',
|
|
'lgtoken' => $token,
|
|
'lgname' => $lgName,
|
|
'lgpassword' => $password,
|
|
], $ret[2] );
|
|
|
|
$result = $ret[0];
|
|
$this->assertNotInternalType( 'bool', $result );
|
|
$a = $result['login']['result'];
|
|
|
|
$this->assertEquals( 'Success', $a );
|
|
}
|
|
|
|
public function testLoginWithNoSameOriginSecurity() {
|
|
$this->setTemporaryHook( 'RequestHasSameOriginSecurity',
|
|
function () {
|
|
return false;
|
|
}
|
|
);
|
|
|
|
$result = $this->doApiRequest( [
|
|
'action' => 'login',
|
|
] )[0]['login'];
|
|
|
|
$this->assertSame( [
|
|
'result' => 'Aborted',
|
|
'reason' => 'Cannot log in when the same-origin policy is not applied.',
|
|
], $result );
|
|
}
|
|
}
|