These tests are all making two assumptions: - That the sysop test account exists - That its name is UTSysop Both assumptions happen to be true right now, but the first one will no longer be after change I30861742. The second one will probably remain true for a while, but still, tests shouldn't rely on this implementation detail when possible. If a test needs an exiting test user, it should call getTestUser / getTestSysop. Use mocks or different usernames where the user actually doesn't matter, e.g. in non-Database tests where UTSysop already doesn't exist. Bug: T342428 Change-Id: Ie77e72f5a5ee6a2ef4ec9dceaa9044bb690f68b2
331 lines
11 KiB
PHP
331 lines
11 KiB
PHP
<?php
|
|
|
|
namespace MediaWiki\Auth;
|
|
|
|
use MediaWiki\MainConfigNames;
|
|
use MediaWiki\Tests\Unit\Auth\AuthenticationProviderTestTrait;
|
|
use MediaWiki\Tests\Unit\DummyServicesTrait;
|
|
use MediaWiki\User\UserNameUtils;
|
|
use User;
|
|
use Wikimedia\TestingAccessWrapper;
|
|
|
|
/**
|
|
* @group AuthManager
|
|
* @covers \MediaWiki\Auth\ConfirmLinkSecondaryAuthenticationProvider
|
|
*/
|
|
class ConfirmLinkSecondaryAuthenticationProviderTest extends \MediaWikiIntegrationTestCase {
|
|
use AuthenticationProviderTestTrait;
|
|
use DummyServicesTrait;
|
|
|
|
/**
|
|
* @dataProvider provideGetAuthenticationRequests
|
|
* @param string $action
|
|
* @param array $response
|
|
*/
|
|
public function testGetAuthenticationRequests( $action, $response ) {
|
|
$provider = new ConfirmLinkSecondaryAuthenticationProvider();
|
|
|
|
$this->assertEquals( $response, $provider->getAuthenticationRequests( $action, [] ) );
|
|
}
|
|
|
|
public static function provideGetAuthenticationRequests() {
|
|
return [
|
|
[ AuthManager::ACTION_LOGIN, [] ],
|
|
[ AuthManager::ACTION_CREATE, [] ],
|
|
[ AuthManager::ACTION_LINK, [] ],
|
|
[ AuthManager::ACTION_CHANGE, [] ],
|
|
[ AuthManager::ACTION_REMOVE, [] ],
|
|
];
|
|
}
|
|
|
|
public function testBeginSecondaryAuthentication() {
|
|
$user = $this->createMock( User::class );
|
|
$obj = new \stdClass;
|
|
|
|
$mock = $this->getMockBuilder( ConfirmLinkSecondaryAuthenticationProvider::class )
|
|
->onlyMethods( [ 'beginLinkAttempt', 'continueLinkAttempt' ] )
|
|
->getMock();
|
|
$mock->expects( $this->once() )->method( 'beginLinkAttempt' )
|
|
->with( $this->identicalTo( $user ), $this->identicalTo( AuthManager::AUTHN_STATE ) )
|
|
->willReturn( $obj );
|
|
$mock->expects( $this->never() )->method( 'continueLinkAttempt' );
|
|
|
|
$this->assertSame( $obj, $mock->beginSecondaryAuthentication( $user, [] ) );
|
|
}
|
|
|
|
public function testContinueSecondaryAuthentication() {
|
|
$user = $this->createMock( User::class );
|
|
$obj = new \stdClass;
|
|
$reqs = [ new \stdClass ];
|
|
|
|
$mock = $this->getMockBuilder( ConfirmLinkSecondaryAuthenticationProvider::class )
|
|
->onlyMethods( [ 'beginLinkAttempt', 'continueLinkAttempt' ] )
|
|
->getMock();
|
|
$mock->expects( $this->never() )->method( 'beginLinkAttempt' );
|
|
$mock->expects( $this->once() )->method( 'continueLinkAttempt' )
|
|
->with(
|
|
$this->identicalTo( $user ),
|
|
$this->identicalTo( AuthManager::AUTHN_STATE ),
|
|
$this->identicalTo( $reqs )
|
|
)
|
|
->willReturn( $obj );
|
|
|
|
$this->assertSame( $obj, $mock->continueSecondaryAuthentication( $user, $reqs ) );
|
|
}
|
|
|
|
public function testBeginSecondaryAccountCreation() {
|
|
$user = $this->createMock( User::class );
|
|
$obj = new \stdClass;
|
|
|
|
$mock = $this->getMockBuilder( ConfirmLinkSecondaryAuthenticationProvider::class )
|
|
->onlyMethods( [ 'beginLinkAttempt', 'continueLinkAttempt' ] )
|
|
->getMock();
|
|
$mock->expects( $this->once() )->method( 'beginLinkAttempt' )
|
|
->with( $this->identicalTo( $user ), $this->identicalTo( AuthManager::ACCOUNT_CREATION_STATE ) )
|
|
->willReturn( $obj );
|
|
$mock->expects( $this->never() )->method( 'continueLinkAttempt' );
|
|
|
|
$this->assertSame( $obj, $mock->beginSecondaryAccountCreation( $user, $user, [] ) );
|
|
}
|
|
|
|
public function testContinueSecondaryAccountCreation() {
|
|
$user = $this->createMock( User::class );
|
|
$obj = new \stdClass;
|
|
$reqs = [ new \stdClass ];
|
|
|
|
$mock = $this->getMockBuilder( ConfirmLinkSecondaryAuthenticationProvider::class )
|
|
->onlyMethods( [ 'beginLinkAttempt', 'continueLinkAttempt' ] )
|
|
->getMock();
|
|
$mock->expects( $this->never() )->method( 'beginLinkAttempt' );
|
|
$mock->expects( $this->once() )->method( 'continueLinkAttempt' )
|
|
->with(
|
|
$this->identicalTo( $user ),
|
|
$this->identicalTo( AuthManager::ACCOUNT_CREATION_STATE ),
|
|
$this->identicalTo( $reqs )
|
|
)
|
|
->willReturn( $obj );
|
|
|
|
$this->assertSame( $obj, $mock->continueSecondaryAccountCreation( $user, $user, $reqs ) );
|
|
}
|
|
|
|
/**
|
|
* Get requests for testing
|
|
* @return AuthenticationRequest[]
|
|
*/
|
|
private function getLinkRequests() {
|
|
$reqs = [];
|
|
|
|
$mb = $this->getMockBuilder( AuthenticationRequest::class )
|
|
->onlyMethods( [ 'getUniqueId' ] );
|
|
for ( $i = 1; $i <= 3; $i++ ) {
|
|
$uid = "Request$i";
|
|
$req = $mb->getMockForAbstractClass();
|
|
$req->method( 'getUniqueId' )->willReturn( $uid );
|
|
$req->id = $i - 1;
|
|
$reqs[$uid] = $req;
|
|
}
|
|
|
|
return $reqs;
|
|
}
|
|
|
|
public function testBeginLinkAttempt() {
|
|
$badReq = $this->getMockBuilder( AuthenticationRequest::class )
|
|
->onlyMethods( [ 'getUniqueId' ] )
|
|
->getMockForAbstractClass();
|
|
$badReq->method( 'getUniqueId' )
|
|
->willReturn( "BadReq" );
|
|
|
|
$user = $this->createMock( User::class );
|
|
$provider = new ConfirmLinkSecondaryAuthenticationProvider;
|
|
$providerPriv = TestingAccessWrapper::newFromObject( $provider );
|
|
$request = new \MediaWiki\Request\FauxRequest();
|
|
$mwServices = $this->getServiceContainer();
|
|
|
|
$manager = $this->getMockBuilder( AuthManager::class )
|
|
->onlyMethods( [ 'allowsAuthenticationDataChange' ] )
|
|
->setConstructorArgs( [
|
|
$request,
|
|
$mwServices->getMainConfig(),
|
|
$mwServices->getObjectFactory(),
|
|
$mwServices->getHookContainer(),
|
|
$mwServices->getReadOnlyMode(),
|
|
$this->createNoOpMock( UserNameUtils::class ),
|
|
$mwServices->getBlockManager(),
|
|
$mwServices->getWatchlistManager(),
|
|
$mwServices->getDBLoadBalancer(),
|
|
$mwServices->getContentLanguage(),
|
|
$mwServices->getLanguageConverterFactory(),
|
|
$mwServices->getBotPasswordStore(),
|
|
$mwServices->getUserFactory(),
|
|
$mwServices->getUserIdentityLookup(),
|
|
$mwServices->getUserOptionsManager()
|
|
] )
|
|
->getMock();
|
|
$manager->method( 'allowsAuthenticationDataChange' )
|
|
->willReturnCallback( static function ( $req ) {
|
|
return $req->getUniqueId() !== 'BadReq'
|
|
? \StatusValue::newGood()
|
|
: \StatusValue::newFatal( 'no' );
|
|
} );
|
|
$this->initProvider( $provider, null, null, $manager );
|
|
|
|
$this->assertEquals(
|
|
AuthenticationResponse::newAbstain(),
|
|
$providerPriv->beginLinkAttempt( $user, 'state' )
|
|
);
|
|
|
|
$request->getSession()->setSecret( 'state', [
|
|
'maybeLink' => [],
|
|
] );
|
|
$this->assertEquals(
|
|
AuthenticationResponse::newAbstain(),
|
|
$providerPriv->beginLinkAttempt( $user, 'state' )
|
|
);
|
|
|
|
$reqs = $this->getLinkRequests();
|
|
$request->getSession()->setSecret( 'state', [
|
|
'maybeLink' => $reqs + [ 'BadReq' => $badReq ]
|
|
] );
|
|
$res = $providerPriv->beginLinkAttempt( $user, 'state' );
|
|
$this->assertInstanceOf( AuthenticationResponse::class, $res );
|
|
$this->assertSame( AuthenticationResponse::UI, $res->status );
|
|
$this->assertSame( 'authprovider-confirmlink-message', $res->message->getKey() );
|
|
$this->assertCount( 1, $res->neededRequests );
|
|
$req = $res->neededRequests[0];
|
|
$this->assertInstanceOf( ConfirmLinkAuthenticationRequest::class, $req );
|
|
$expectReqs = $this->getLinkRequests();
|
|
foreach ( $expectReqs as $r ) {
|
|
$r->action = AuthManager::ACTION_CHANGE;
|
|
$r->username = $user->getName();
|
|
}
|
|
$this->assertEquals( $expectReqs, TestingAccessWrapper::newFromObject( $req )->linkRequests );
|
|
}
|
|
|
|
public function testContinueLinkAttempt() {
|
|
$user = $this->createMock( User::class );
|
|
$obj = new \stdClass;
|
|
$reqs = $this->getLinkRequests();
|
|
|
|
$done = [ false, false, false ];
|
|
|
|
// First, test the pass-through for not containing the ConfirmLinkAuthenticationRequest
|
|
$mock = $this->getMockBuilder( ConfirmLinkSecondaryAuthenticationProvider::class )
|
|
->onlyMethods( [ 'beginLinkAttempt' ] )
|
|
->getMock();
|
|
$mock->expects( $this->once() )->method( 'beginLinkAttempt' )
|
|
->with( $this->identicalTo( $user ), $this->identicalTo( 'state' ) )
|
|
->willReturn( $obj );
|
|
$this->assertSame(
|
|
$obj,
|
|
TestingAccessWrapper::newFromObject( $mock )->continueLinkAttempt( $user, 'state', $reqs )
|
|
);
|
|
|
|
// Now test the actual functioning
|
|
$provider = $this->getMockBuilder( ConfirmLinkSecondaryAuthenticationProvider::class )
|
|
->onlyMethods( [
|
|
'beginLinkAttempt', 'providerAllowsAuthenticationDataChange',
|
|
'providerChangeAuthenticationData'
|
|
] )
|
|
->getMock();
|
|
$provider->expects( $this->never() )->method( 'beginLinkAttempt' );
|
|
$provider->method( 'providerAllowsAuthenticationDataChange' )
|
|
->willReturnCallback( static function ( $req ) {
|
|
return $req->getUniqueId() === 'Request3'
|
|
? \StatusValue::newFatal( 'foo' ) : \StatusValue::newGood();
|
|
} );
|
|
$provider->method( 'providerChangeAuthenticationData' )
|
|
->willReturnCallback( static function ( $req ) use ( &$done ) {
|
|
$done[$req->id] = true;
|
|
} );
|
|
$config = new \HashConfig( [
|
|
MainConfigNames::AuthManagerConfig => [
|
|
'preauth' => [],
|
|
'primaryauth' => [],
|
|
'secondaryauth' => [
|
|
[ 'factory' => static function () use ( $provider ) {
|
|
return $provider;
|
|
} ],
|
|
],
|
|
],
|
|
] );
|
|
$request = new \MediaWiki\Request\FauxRequest();
|
|
$mwServices = $this->getServiceContainer();
|
|
$manager = new AuthManager(
|
|
$request,
|
|
$config,
|
|
$this->getDummyObjectFactory(),
|
|
$mwServices->getHookContainer(),
|
|
$mwServices->getReadOnlyMode(),
|
|
$mwServices->getUserNameUtils(),
|
|
$mwServices->getBlockManager(),
|
|
$mwServices->getWatchlistManager(),
|
|
$mwServices->getDBLoadBalancer(),
|
|
$mwServices->getContentLanguage(),
|
|
$mwServices->getLanguageConverterFactory(),
|
|
$mwServices->getBotPasswordStore(),
|
|
$mwServices->getUserFactory(),
|
|
$mwServices->getUserIdentityLookup(),
|
|
$mwServices->getUserOptionsManager()
|
|
);
|
|
$this->initProvider( $provider, null, null, $manager );
|
|
$provider = TestingAccessWrapper::newFromObject( $provider );
|
|
|
|
$req = new ConfirmLinkAuthenticationRequest( $reqs );
|
|
|
|
$this->assertEquals(
|
|
AuthenticationResponse::newAbstain(),
|
|
$provider->continueLinkAttempt( $user, 'state', [ $req ] )
|
|
);
|
|
|
|
$request->getSession()->setSecret( 'state', [
|
|
'maybeLink' => [],
|
|
] );
|
|
$this->assertEquals(
|
|
AuthenticationResponse::newAbstain(),
|
|
$provider->continueLinkAttempt( $user, 'state', [ $req ] )
|
|
);
|
|
|
|
$request->getSession()->setSecret( 'state', [
|
|
'maybeLink' => $reqs
|
|
] );
|
|
$this->assertEquals(
|
|
AuthenticationResponse::newPass(),
|
|
$provider->continueLinkAttempt( $user, 'state', [ $req ] )
|
|
);
|
|
$this->assertSame( [ false, false, false ], $done );
|
|
|
|
$request->getSession()->setSecret( 'state', [
|
|
'maybeLink' => [ $reqs['Request2'] ],
|
|
] );
|
|
$req->confirmedLinkIDs = [ 'Request1', 'Request2' ];
|
|
$res = $provider->continueLinkAttempt( $user, 'state', [ $req ] );
|
|
$this->assertEquals( AuthenticationResponse::newPass(), $res );
|
|
$this->assertSame( [ false, true, false ], $done );
|
|
$done = [ false, false, false ];
|
|
|
|
$request->getSession()->setSecret( 'state', [
|
|
'maybeLink' => $reqs,
|
|
] );
|
|
$req->confirmedLinkIDs = [ 'Request1', 'Request2' ];
|
|
$res = $provider->continueLinkAttempt( $user, 'state', [ $req ] );
|
|
$this->assertEquals( AuthenticationResponse::newPass(), $res );
|
|
$this->assertSame( [ true, true, false ], $done );
|
|
$done = [ false, false, false ];
|
|
|
|
$request->getSession()->setSecret( 'state', [
|
|
'maybeLink' => $reqs,
|
|
] );
|
|
$req->confirmedLinkIDs = [ 'Request1', 'Request3' ];
|
|
$res = $provider->continueLinkAttempt( $user, 'state', [ $req ] );
|
|
$this->assertEquals( AuthenticationResponse::UI, $res->status );
|
|
$this->assertCount( 1, $res->neededRequests );
|
|
$this->assertInstanceOf( ButtonAuthenticationRequest::class, $res->neededRequests[0] );
|
|
$this->assertSame( [ true, false, false ], $done );
|
|
$done = [ false, false, false ];
|
|
|
|
$res = $provider->continueLinkAttempt( $user, 'state', [ $res->neededRequests[0] ] );
|
|
$this->assertEquals( AuthenticationResponse::newPass(), $res );
|
|
$this->assertSame( [ false, false, false ], $done );
|
|
}
|
|
|
|
}
|