768 lines
24 KiB
PHP
768 lines
24 KiB
PHP
<?php
|
|
|
|
namespace MediaWiki\Tests\Auth;
|
|
|
|
use BadMethodCallException;
|
|
use MediaWiki\Auth\AuthenticationRequest;
|
|
use MediaWiki\Auth\AuthenticationResponse;
|
|
use MediaWiki\Auth\AuthManager;
|
|
use MediaWiki\Auth\LocalPasswordPrimaryAuthenticationProvider;
|
|
use MediaWiki\Auth\PasswordAuthenticationRequest;
|
|
use MediaWiki\Auth\PasswordDomainAuthenticationRequest;
|
|
use MediaWiki\Auth\PrimaryAuthenticationProvider;
|
|
use MediaWiki\Config\HashConfig;
|
|
use MediaWiki\Config\MultiConfig;
|
|
use MediaWiki\MainConfigNames;
|
|
use MediaWiki\Request\FauxRequest;
|
|
use MediaWiki\Status\Status;
|
|
use MediaWiki\Tests\Unit\Auth\AuthenticationProviderTestTrait;
|
|
use MediaWiki\Tests\Unit\DummyServicesTrait;
|
|
use MediaWiki\User\User;
|
|
use MediaWiki\User\UserNameUtils;
|
|
use MediaWikiIntegrationTestCase;
|
|
use PasswordFactory;
|
|
use StatusValue;
|
|
use Wikimedia\TestingAccessWrapper;
|
|
|
|
/**
|
|
* @group AuthManager
|
|
* @group Database
|
|
* @covers \MediaWiki\Auth\LocalPasswordPrimaryAuthenticationProvider
|
|
*/
|
|
class LocalPasswordPrimaryAuthenticationProviderTest extends MediaWikiIntegrationTestCase {
|
|
use AuthenticationProviderTestTrait;
|
|
use DummyServicesTrait;
|
|
|
|
private $manager = null;
|
|
private $config = null;
|
|
private $validity = null;
|
|
|
|
/**
|
|
* Get an instance of the provider
|
|
*
|
|
* $provider->checkPasswordValidity is mocked to return $this->validity,
|
|
* because we don't need to test that here.
|
|
*
|
|
* @param bool $loginOnly
|
|
* @return LocalPasswordPrimaryAuthenticationProvider
|
|
*/
|
|
protected function getProvider( $loginOnly = false ) {
|
|
$mwServices = $this->getServiceContainer();
|
|
if ( !$this->config ) {
|
|
$this->config = new HashConfig();
|
|
}
|
|
$config = new MultiConfig( [
|
|
$this->config,
|
|
$mwServices->getMainConfig()
|
|
] );
|
|
|
|
// We need a real HookContainer since testProviderChangeAuthenticationData()
|
|
// modifies $wgHooks
|
|
$hookContainer = $mwServices->getHookContainer();
|
|
|
|
if ( !$this->manager ) {
|
|
$userNameUtils = $this->createNoOpMock( UserNameUtils::class );
|
|
|
|
$this->manager = new AuthManager(
|
|
new FauxRequest(),
|
|
$config,
|
|
$this->getDummyObjectFactory(),
|
|
$hookContainer,
|
|
$mwServices->getReadOnlyMode(),
|
|
$userNameUtils,
|
|
$mwServices->getBlockManager(),
|
|
$mwServices->getWatchlistManager(),
|
|
$mwServices->getDBLoadBalancer(),
|
|
$mwServices->getContentLanguage(),
|
|
$mwServices->getLanguageConverterFactory(),
|
|
$mwServices->getBotPasswordStore(),
|
|
$mwServices->getUserFactory(),
|
|
$mwServices->getUserIdentityLookup(),
|
|
$mwServices->getUserOptionsManager()
|
|
);
|
|
}
|
|
$this->validity = Status::newGood();
|
|
$provider = $this->getMockBuilder( LocalPasswordPrimaryAuthenticationProvider::class )
|
|
->onlyMethods( [ 'checkPasswordValidity' ] )
|
|
->setConstructorArgs( [
|
|
$mwServices->getConnectionProvider(),
|
|
[ 'loginOnly' => $loginOnly ]
|
|
] )
|
|
->getMock();
|
|
|
|
$provider->method( 'checkPasswordValidity' )
|
|
->willReturnCallback( function () {
|
|
return $this->validity;
|
|
} );
|
|
$this->initProvider(
|
|
$provider, $config, null, $this->manager, $hookContainer, $this->getServiceContainer()->getUserNameUtils()
|
|
);
|
|
|
|
return $provider;
|
|
}
|
|
|
|
public function testBasics() {
|
|
$user = $this->getMutableTestUser()->getUser();
|
|
$userName = $user->getName();
|
|
$lowerInitialUserName = mb_strtolower( $userName[0] ) . substr( $userName, 1 );
|
|
|
|
$provider = $this->getProvider();
|
|
|
|
$this->assertSame(
|
|
PrimaryAuthenticationProvider::TYPE_CREATE,
|
|
$provider->accountCreationType()
|
|
);
|
|
|
|
$this->assertTrue( $provider->testUserExists( $userName ) );
|
|
$this->assertTrue( $provider->testUserExists( $lowerInitialUserName ) );
|
|
$this->assertFalse( $provider->testUserExists( 'DoesNotExist' ) );
|
|
$this->assertFalse( $provider->testUserExists( '<invalid>' ) );
|
|
|
|
$provider = $this->getProvider( [ 'loginOnly' => true ] );
|
|
|
|
$this->assertSame(
|
|
PrimaryAuthenticationProvider::TYPE_NONE,
|
|
$provider->accountCreationType()
|
|
);
|
|
|
|
$this->assertTrue( $provider->testUserExists( $userName ) );
|
|
$this->assertFalse( $provider->testUserExists( 'DoesNotExist' ) );
|
|
|
|
$req = new PasswordAuthenticationRequest;
|
|
$req->action = AuthManager::ACTION_CHANGE;
|
|
$req->username = '<invalid>';
|
|
$provider->providerChangeAuthenticationData( $req );
|
|
}
|
|
|
|
public function testTestUserCanAuthenticate() {
|
|
$user = $this->getMutableTestUser()->getUser();
|
|
$userName = $user->getName();
|
|
$dbw = $this->getDb();
|
|
|
|
$provider = $this->getProvider();
|
|
|
|
$this->assertFalse( $provider->testUserCanAuthenticate( '<invalid>' ) );
|
|
|
|
$this->assertFalse( $provider->testUserCanAuthenticate( 'DoesNotExist' ) );
|
|
|
|
$this->assertTrue( $provider->testUserCanAuthenticate( $userName ) );
|
|
$lowerInitialUserName = mb_strtolower( $userName[0] ) . substr( $userName, 1 );
|
|
$this->assertTrue( $provider->testUserCanAuthenticate( $lowerInitialUserName ) );
|
|
|
|
$dbw->newUpdateQueryBuilder()
|
|
->update( 'user' )
|
|
->set( [ 'user_password' => PasswordFactory::newInvalidPassword()->toString() ] )
|
|
->where( [ 'user_name' => $userName ] )
|
|
->caller( __METHOD__ )
|
|
->execute();
|
|
$this->assertFalse( $provider->testUserCanAuthenticate( $userName ) );
|
|
|
|
// Really old format
|
|
$dbw->newUpdateQueryBuilder()
|
|
->update( 'user' )
|
|
->set( [ 'user_password' => '0123456789abcdef0123456789abcdef' ] )
|
|
->where( [ 'user_name' => $userName ] )
|
|
->caller( __METHOD__ )
|
|
->execute();
|
|
$this->assertTrue( $provider->testUserCanAuthenticate( $userName ) );
|
|
}
|
|
|
|
public function testSetPasswordResetFlag() {
|
|
// Set instance vars
|
|
$this->getProvider();
|
|
|
|
// @todo: Because we're currently using User, which uses the global config...
|
|
$this->overrideConfigValue( MainConfigNames::PasswordExpireGrace, 100 );
|
|
|
|
$this->config->set( MainConfigNames::PasswordExpireGrace, 100 );
|
|
$this->config->set( MainConfigNames::InvalidPasswordReset, true );
|
|
|
|
$provider = new LocalPasswordPrimaryAuthenticationProvider(
|
|
$this->getServiceContainer()->getConnectionProvider()
|
|
);
|
|
$this->initProvider( $provider, $this->config, null, $this->manager );
|
|
$providerPriv = TestingAccessWrapper::newFromObject( $provider );
|
|
|
|
$user = $this->getMutableTestUser()->getUser();
|
|
$userName = $user->getName();
|
|
$row = $this->getDb()->newSelectQueryBuilder()
|
|
->select( '*' )
|
|
->from( 'user' )
|
|
->where( [ 'user_name' => $userName ] )
|
|
->caller( __METHOD__ )->fetchRow();
|
|
|
|
$this->manager->removeAuthenticationSessionData( null );
|
|
$row->user_password_expires = wfTimestamp( TS_MW, time() + 200 );
|
|
$providerPriv->setPasswordResetFlag( $userName, Status::newGood(), $row );
|
|
$this->assertNull( $this->manager->getAuthenticationSessionData( 'reset-pass' ) );
|
|
|
|
$this->manager->removeAuthenticationSessionData( null );
|
|
$row->user_password_expires = wfTimestamp( TS_MW, time() - 200 );
|
|
$providerPriv->setPasswordResetFlag( $userName, Status::newGood(), $row );
|
|
$ret = $this->manager->getAuthenticationSessionData( 'reset-pass' );
|
|
$this->assertNotNull( $ret );
|
|
$this->assertSame( 'resetpass-expired', $ret->msg->getKey() );
|
|
$this->assertTrue( $ret->hard );
|
|
|
|
$this->manager->removeAuthenticationSessionData( null );
|
|
$row->user_password_expires = wfTimestamp( TS_MW, time() - 1 );
|
|
$providerPriv->setPasswordResetFlag( $userName, Status::newGood(), $row );
|
|
$ret = $this->manager->getAuthenticationSessionData( 'reset-pass' );
|
|
$this->assertNotNull( $ret );
|
|
$this->assertSame( 'resetpass-expired-soft', $ret->msg->getKey() );
|
|
$this->assertFalse( $ret->hard );
|
|
|
|
$this->manager->removeAuthenticationSessionData( null );
|
|
$row->user_password_expires = null;
|
|
$status = Status::newGood( [ 'suggestChangeOnLogin' => true ] );
|
|
$status->error( 'testing' );
|
|
$providerPriv->setPasswordResetFlag( $userName, $status, $row );
|
|
$ret = $this->manager->getAuthenticationSessionData( 'reset-pass' );
|
|
$this->assertNotNull( $ret );
|
|
$this->assertSame( 'resetpass-validity-soft', $ret->msg->getKey() );
|
|
$this->assertFalse( $ret->hard );
|
|
|
|
$this->manager->removeAuthenticationSessionData( null );
|
|
$row->user_password_expires = null;
|
|
$status = Status::newGood( [ 'forceChange' => true ] );
|
|
$status->error( 'testing' );
|
|
$providerPriv->setPasswordResetFlag( $userName, $status, $row );
|
|
$ret = $this->manager->getAuthenticationSessionData( 'reset-pass' );
|
|
$this->assertNotNull( $ret );
|
|
$this->assertSame( 'resetpass-validity', $ret->msg->getKey() );
|
|
$this->assertTrue( $ret->hard );
|
|
|
|
$this->manager->removeAuthenticationSessionData( null );
|
|
$row->user_password_expires = null;
|
|
$status = Status::newGood( [ 'suggestChangeOnLogin' => false, ] );
|
|
$status->error( 'testing' );
|
|
$providerPriv->setPasswordResetFlag( $userName, $status, $row );
|
|
$ret = $this->manager->getAuthenticationSessionData( 'reset-pass' );
|
|
$this->assertNull( $ret );
|
|
}
|
|
|
|
public function testAuthentication() {
|
|
$testUser = $this->getMutableTestUser();
|
|
$userName = $testUser->getUser()->getName();
|
|
|
|
$dbw = $this->getDb();
|
|
$id = $testUser->getUser()->getId();
|
|
|
|
$req = new PasswordAuthenticationRequest();
|
|
$req->action = AuthManager::ACTION_LOGIN;
|
|
$reqs = [ PasswordAuthenticationRequest::class => $req ];
|
|
|
|
$provider = $this->getProvider();
|
|
|
|
// General failures
|
|
$this->assertEquals(
|
|
AuthenticationResponse::newAbstain(),
|
|
$provider->beginPrimaryAuthentication( [] )
|
|
);
|
|
|
|
$req->username = 'foo';
|
|
$req->password = null;
|
|
$this->assertEquals(
|
|
AuthenticationResponse::newAbstain(),
|
|
$provider->beginPrimaryAuthentication( $reqs )
|
|
);
|
|
|
|
$req->username = null;
|
|
$req->password = 'bar';
|
|
$this->assertEquals(
|
|
AuthenticationResponse::newAbstain(),
|
|
$provider->beginPrimaryAuthentication( $reqs )
|
|
);
|
|
|
|
$req->username = '<invalid>';
|
|
$req->password = 'WhoCares';
|
|
$ret = $provider->beginPrimaryAuthentication( $reqs );
|
|
$this->assertEquals(
|
|
AuthenticationResponse::newAbstain(),
|
|
$provider->beginPrimaryAuthentication( $reqs )
|
|
);
|
|
|
|
$req->username = 'DoesNotExist';
|
|
$req->password = 'DoesNotExist';
|
|
$ret = $provider->beginPrimaryAuthentication( $reqs );
|
|
$this->assertEquals(
|
|
AuthenticationResponse::FAIL,
|
|
$ret->status
|
|
);
|
|
$this->assertEquals(
|
|
'wrongpassword',
|
|
$ret->message->getKey()
|
|
);
|
|
|
|
// Validation failure
|
|
$req->username = $userName;
|
|
$req->password = $testUser->getPassword();
|
|
$this->validity = Status::newFatal( 'arbitrary-failure' );
|
|
$ret = $provider->beginPrimaryAuthentication( $reqs );
|
|
$this->assertEquals(
|
|
AuthenticationResponse::FAIL,
|
|
$ret->status
|
|
);
|
|
// AbstractPasswordPrimaryAuthenticationProvider::getFatalPasswordErrorResponse() will
|
|
// wrap the original message in 'fatalpassworderror'
|
|
$this->assertEquals(
|
|
'fatalpassworderror',
|
|
$ret->message->getKey()
|
|
);
|
|
|
|
// Successful auth
|
|
$this->manager->removeAuthenticationSessionData( null );
|
|
$this->validity = Status::newGood();
|
|
$this->assertEquals(
|
|
AuthenticationResponse::newPass( $userName ),
|
|
$provider->beginPrimaryAuthentication( $reqs )
|
|
);
|
|
$this->assertNull( $this->manager->getAuthenticationSessionData( 'reset-pass' ) );
|
|
|
|
// Successful auth after normalizing name
|
|
$this->manager->removeAuthenticationSessionData( null );
|
|
$this->validity = Status::newGood();
|
|
$req->username = mb_strtolower( $userName[0] ) . substr( $userName, 1 );
|
|
$this->assertEquals(
|
|
AuthenticationResponse::newPass( $userName ),
|
|
$provider->beginPrimaryAuthentication( $reqs )
|
|
);
|
|
$this->assertNull( $this->manager->getAuthenticationSessionData( 'reset-pass' ) );
|
|
$req->username = $userName;
|
|
|
|
// Successful auth with reset
|
|
$this->manager->removeAuthenticationSessionData( null );
|
|
$this->validity = Status::newGood( [ 'suggestChangeOnLogin' => true ] );
|
|
$this->validity->error( 'arbitrary-warning' );
|
|
$this->assertEquals(
|
|
AuthenticationResponse::newPass( $userName ),
|
|
$provider->beginPrimaryAuthentication( $reqs )
|
|
);
|
|
$this->assertNotNull( $this->manager->getAuthenticationSessionData( 'reset-pass' ) );
|
|
|
|
// Wrong password
|
|
$this->validity = Status::newGood();
|
|
$req->password = 'Wrong';
|
|
$ret = $provider->beginPrimaryAuthentication( $reqs );
|
|
$this->assertEquals(
|
|
AuthenticationResponse::FAIL,
|
|
$ret->status
|
|
);
|
|
$this->assertEquals(
|
|
'wrongpassword',
|
|
$ret->message->getKey()
|
|
);
|
|
|
|
// Correct handling of legacy encodings
|
|
$password = ':B:salt:' . md5( 'salt-' . md5( "\xe1\xe9\xed\xf3\xfa" ) );
|
|
$dbw->newUpdateQueryBuilder()
|
|
->update( 'user' )
|
|
->set( [ 'user_password' => $password ] )
|
|
->where( [ 'user_name' => $userName ] )
|
|
->caller( __METHOD__ )
|
|
->execute();
|
|
$req->password = 'áéíóú';
|
|
$ret = $provider->beginPrimaryAuthentication( $reqs );
|
|
$this->assertEquals(
|
|
AuthenticationResponse::FAIL,
|
|
$ret->status
|
|
);
|
|
$this->assertEquals(
|
|
'wrongpassword',
|
|
$ret->message->getKey()
|
|
);
|
|
|
|
$this->config->set( MainConfigNames::LegacyEncoding, true );
|
|
$this->assertEquals(
|
|
AuthenticationResponse::newPass( $userName ),
|
|
$provider->beginPrimaryAuthentication( $reqs )
|
|
);
|
|
|
|
$req->password = 'áéíóú Wrong';
|
|
$ret = $provider->beginPrimaryAuthentication( $reqs );
|
|
$this->assertEquals(
|
|
AuthenticationResponse::FAIL,
|
|
$ret->status
|
|
);
|
|
$this->assertEquals(
|
|
'wrongpassword',
|
|
$ret->message->getKey()
|
|
);
|
|
|
|
// Correct handling of really old password hashes
|
|
$password = md5( "$id-" . md5( 'FooBar' ) );
|
|
$dbw->newUpdateQueryBuilder()
|
|
->update( 'user' )
|
|
->set( [ 'user_password' => $password ] )
|
|
->where( [ 'user_name' => $userName ] )
|
|
->caller( __METHOD__ )
|
|
->execute();
|
|
$req->password = 'FooBar';
|
|
$this->assertEquals(
|
|
AuthenticationResponse::newPass( $userName ),
|
|
$provider->beginPrimaryAuthentication( $reqs )
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideProviderAllowsAuthenticationDataChange
|
|
*
|
|
* @param string $type
|
|
* @param callable $usernameGetter Function that takes the username of a sysop user and returns the username to
|
|
* use for testing.
|
|
* @param Status $validity Result of the password validity check
|
|
* @param StatusValue $expect1 Expected result with $checkData = false
|
|
* @param StatusValue $expect2 Expected result with $checkData = true
|
|
*/
|
|
public function testProviderAllowsAuthenticationDataChange( $type, callable $usernameGetter, Status $validity,
|
|
StatusValue $expect1,
|
|
StatusValue $expect2
|
|
) {
|
|
$user = $usernameGetter( $this->getTestSysop()->getUserIdentity()->getName() );
|
|
if ( $type === PasswordAuthenticationRequest::class ) {
|
|
$req = new $type();
|
|
$req->password = 'NewPassword';
|
|
$req->retype = 'NewPassword';
|
|
} elseif ( $type === PasswordDomainAuthenticationRequest::class ) {
|
|
$req = new $type( [] );
|
|
} else {
|
|
$req = $this->createMock( $type );
|
|
}
|
|
$req->action = AuthManager::ACTION_CHANGE;
|
|
$req->username = $user;
|
|
|
|
$provider = $this->getProvider();
|
|
$this->validity = $validity;
|
|
$this->assertEquals( $expect1, $provider->providerAllowsAuthenticationDataChange( $req, false ) );
|
|
$this->assertEquals( $expect2, $provider->providerAllowsAuthenticationDataChange( $req, true ) );
|
|
|
|
if ( $req instanceof PasswordAuthenticationRequest ) {
|
|
$req->retype = 'BadRetype';
|
|
}
|
|
$this->assertEquals(
|
|
$expect1,
|
|
$provider->providerAllowsAuthenticationDataChange( $req, false )
|
|
);
|
|
$this->assertEquals(
|
|
$expect2->getValue() === 'ignored' ? $expect2 : StatusValue::newFatal( 'badretype' ),
|
|
$provider->providerAllowsAuthenticationDataChange( $req, true )
|
|
);
|
|
|
|
$provider = $this->getProvider( true );
|
|
$this->assertEquals(
|
|
StatusValue::newGood( 'ignored' ),
|
|
$provider->providerAllowsAuthenticationDataChange( $req, true ),
|
|
'loginOnly mode should claim to ignore all changes'
|
|
);
|
|
}
|
|
|
|
public static function provideProviderAllowsAuthenticationDataChange() {
|
|
$err = StatusValue::newGood();
|
|
$err->error( 'arbitrary-warning' );
|
|
|
|
return [
|
|
[
|
|
AuthenticationRequest::class,
|
|
static fn ( $sysopUsername ) => $sysopUsername,
|
|
Status::newGood(),
|
|
StatusValue::newGood( 'ignored' ),
|
|
StatusValue::newGood( 'ignored' )
|
|
],
|
|
[
|
|
PasswordAuthenticationRequest::class,
|
|
static fn ( $sysopUsername ) => $sysopUsername,
|
|
Status::newGood(),
|
|
StatusValue::newGood(),
|
|
StatusValue::newGood()
|
|
],
|
|
[
|
|
PasswordAuthenticationRequest::class,
|
|
'lcfirst',
|
|
Status::newGood(),
|
|
StatusValue::newGood(),
|
|
StatusValue::newGood()
|
|
],
|
|
[
|
|
PasswordAuthenticationRequest::class,
|
|
static fn ( $sysopUsername ) => $sysopUsername,
|
|
Status::wrap( $err ),
|
|
StatusValue::newGood(),
|
|
$err
|
|
],
|
|
[
|
|
PasswordAuthenticationRequest::class,
|
|
static fn ( $sysopUsername ) => $sysopUsername,
|
|
Status::newFatal( 'arbitrary-error' ),
|
|
StatusValue::newGood(),
|
|
StatusValue::newFatal( 'arbitrary-error' )
|
|
],
|
|
[
|
|
PasswordAuthenticationRequest::class,
|
|
static fn () => 'DoesNotExist',
|
|
Status::newGood(),
|
|
StatusValue::newGood(),
|
|
StatusValue::newGood( 'ignored' )
|
|
],
|
|
[
|
|
PasswordDomainAuthenticationRequest::class,
|
|
static fn ( $sysopUsername ) => $sysopUsername,
|
|
Status::newGood(),
|
|
StatusValue::newGood( 'ignored' ),
|
|
StatusValue::newGood( 'ignored' )
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideProviderChangeAuthenticationData
|
|
* @param callable|false $usernameTransform
|
|
* @param string $type
|
|
* @param bool $loginOnly
|
|
* @param bool $changed
|
|
*/
|
|
public function testProviderChangeAuthenticationData(
|
|
$usernameTransform, $type, $loginOnly, $changed ) {
|
|
$testUser = $this->getMutableTestUser();
|
|
$user = $testUser->getUser()->getName();
|
|
if ( is_callable( $usernameTransform ) ) {
|
|
$user = $usernameTransform( $user );
|
|
}
|
|
$cuser = ucfirst( $user );
|
|
$oldpass = $testUser->getPassword();
|
|
$newpass = 'NewPassword';
|
|
|
|
$dbw = $this->getDb();
|
|
$oldExpiry = $dbw->newSelectQueryBuilder()
|
|
->select( 'user_password_expires' )
|
|
->from( 'user' )
|
|
->where( [ 'user_name' => $cuser ] )
|
|
->fetchField();
|
|
|
|
$this->mergeMwGlobalArrayValue( 'wgHooks', [
|
|
'ResetPasswordExpiration' => [ static function ( $user, &$expires ) {
|
|
$expires = '30001231235959';
|
|
} ]
|
|
] );
|
|
|
|
$provider = $this->getProvider( $loginOnly );
|
|
|
|
$loginReq = new PasswordAuthenticationRequest();
|
|
$loginReq->action = AuthManager::ACTION_LOGIN;
|
|
$loginReq->username = $user;
|
|
$loginReq->password = $oldpass;
|
|
$loginReqs = [ PasswordAuthenticationRequest::class => $loginReq ];
|
|
$this->assertEquals(
|
|
AuthenticationResponse::newPass( $cuser ),
|
|
$provider->beginPrimaryAuthentication( $loginReqs )
|
|
);
|
|
|
|
if ( $type === PasswordAuthenticationRequest::class ) {
|
|
$changeReq = new $type();
|
|
$changeReq->password = $newpass;
|
|
} else {
|
|
$changeReq = $this->createMock( $type );
|
|
}
|
|
$changeReq->action = AuthManager::ACTION_CHANGE;
|
|
$changeReq->username = $user;
|
|
$provider->providerChangeAuthenticationData( $changeReq );
|
|
|
|
if ( $loginOnly && $changed ) {
|
|
$old = 'fail';
|
|
$new = 'fail';
|
|
$expectExpiry = null;
|
|
} elseif ( $changed ) {
|
|
$old = 'fail';
|
|
$new = 'pass';
|
|
$expectExpiry = '30001231235959';
|
|
} else {
|
|
$old = 'pass';
|
|
$new = 'fail';
|
|
$expectExpiry = $oldExpiry;
|
|
}
|
|
|
|
$loginReq->password = $oldpass;
|
|
$ret = $provider->beginPrimaryAuthentication( $loginReqs );
|
|
if ( $old === 'pass' ) {
|
|
$this->assertEquals(
|
|
AuthenticationResponse::newPass( $cuser ),
|
|
$ret,
|
|
'old password should pass'
|
|
);
|
|
} else {
|
|
$this->assertEquals(
|
|
AuthenticationResponse::FAIL,
|
|
$ret->status,
|
|
'old password should fail'
|
|
);
|
|
$this->assertEquals(
|
|
'wrongpassword',
|
|
$ret->message->getKey(),
|
|
'old password should fail'
|
|
);
|
|
}
|
|
|
|
$loginReq->password = $newpass;
|
|
$ret = $provider->beginPrimaryAuthentication( $loginReqs );
|
|
if ( $new === 'pass' ) {
|
|
$this->assertEquals(
|
|
AuthenticationResponse::newPass( $cuser ),
|
|
$ret,
|
|
'new password should pass'
|
|
);
|
|
} else {
|
|
$this->assertEquals(
|
|
AuthenticationResponse::FAIL,
|
|
$ret->status,
|
|
'new password should fail'
|
|
);
|
|
$this->assertEquals(
|
|
'wrongpassword',
|
|
$ret->message->getKey(),
|
|
'new password should fail'
|
|
);
|
|
}
|
|
|
|
$this->assertSame(
|
|
$expectExpiry,
|
|
wfTimestampOrNull(
|
|
TS_MW,
|
|
$dbw->newSelectQueryBuilder()
|
|
->select( 'user_password_expires' )
|
|
->from( 'user' )
|
|
->where( [ 'user_name' => $cuser ] )
|
|
->fetchField()
|
|
)
|
|
);
|
|
}
|
|
|
|
public static function provideProviderChangeAuthenticationData() {
|
|
return [
|
|
[ false, AuthenticationRequest::class, false, false ],
|
|
[ false, PasswordAuthenticationRequest::class, false, true ],
|
|
[ false, AuthenticationRequest::class, true, false ],
|
|
[ false, PasswordAuthenticationRequest::class, true, true ],
|
|
[ 'ucfirst', PasswordAuthenticationRequest::class, false, true ],
|
|
[ 'ucfirst', PasswordAuthenticationRequest::class, true, true ],
|
|
];
|
|
}
|
|
|
|
public function testTestForAccountCreation() {
|
|
$user = User::newFromName( 'foo' );
|
|
$req = new PasswordAuthenticationRequest();
|
|
$req->action = AuthManager::ACTION_CREATE;
|
|
$req->username = 'Foo';
|
|
$req->password = 'Bar';
|
|
$req->retype = 'Bar';
|
|
$reqs = [ PasswordAuthenticationRequest::class => $req ];
|
|
|
|
$provider = $this->getProvider();
|
|
$this->assertEquals(
|
|
StatusValue::newGood(),
|
|
$provider->testForAccountCreation( $user, $user, [] ),
|
|
'No password request'
|
|
);
|
|
|
|
$this->assertEquals(
|
|
StatusValue::newGood(),
|
|
$provider->testForAccountCreation( $user, $user, $reqs ),
|
|
'Password request, validated'
|
|
);
|
|
|
|
$req->retype = 'Baz';
|
|
$this->assertEquals(
|
|
StatusValue::newFatal( 'badretype' ),
|
|
$provider->testForAccountCreation( $user, $user, $reqs ),
|
|
'Password request, bad retype'
|
|
);
|
|
$req->retype = 'Bar';
|
|
|
|
$this->validity->error( 'arbitrary warning' );
|
|
$expect = StatusValue::newGood();
|
|
$expect->error( 'arbitrary warning' );
|
|
$this->assertEquals(
|
|
$expect,
|
|
$provider->testForAccountCreation( $user, $user, $reqs ),
|
|
'Password request, not validated'
|
|
);
|
|
|
|
$provider = $this->getProvider( true );
|
|
$this->validity->error( 'arbitrary warning' );
|
|
$this->assertEquals(
|
|
StatusValue::newGood(),
|
|
$provider->testForAccountCreation( $user, $user, $reqs ),
|
|
'Password request, not validated, loginOnly'
|
|
);
|
|
}
|
|
|
|
public function testAccountCreation() {
|
|
$user = User::newFromName( 'Foo' );
|
|
|
|
$req = new PasswordAuthenticationRequest();
|
|
$req->action = AuthManager::ACTION_CREATE;
|
|
$reqs = [ PasswordAuthenticationRequest::class => $req ];
|
|
|
|
$provider = $this->getProvider( true );
|
|
try {
|
|
$provider->beginPrimaryAccountCreation( $user, $user, [] );
|
|
$this->fail( 'Expected exception was not thrown' );
|
|
} catch ( BadMethodCallException $ex ) {
|
|
$this->assertSame(
|
|
'Shouldn\'t call this when accountCreationType() is NONE', $ex->getMessage()
|
|
);
|
|
}
|
|
|
|
try {
|
|
$provider->finishAccountCreation( $user, $user, AuthenticationResponse::newPass() );
|
|
$this->fail( 'Expected exception was not thrown' );
|
|
} catch ( BadMethodCallException $ex ) {
|
|
$this->assertSame(
|
|
'Shouldn\'t call this when accountCreationType() is NONE', $ex->getMessage()
|
|
);
|
|
}
|
|
|
|
$provider = $this->getProvider( false );
|
|
|
|
$this->assertEquals(
|
|
AuthenticationResponse::newAbstain(),
|
|
$provider->beginPrimaryAccountCreation( $user, $user, [] )
|
|
);
|
|
|
|
$req->username = 'foo';
|
|
$req->password = null;
|
|
$this->assertEquals(
|
|
AuthenticationResponse::newAbstain(),
|
|
$provider->beginPrimaryAccountCreation( $user, $user, $reqs )
|
|
);
|
|
|
|
$req->username = null;
|
|
$req->password = 'bar';
|
|
$this->assertEquals(
|
|
AuthenticationResponse::newAbstain(),
|
|
$provider->beginPrimaryAccountCreation( $user, $user, $reqs )
|
|
);
|
|
|
|
$req->username = 'foo';
|
|
$req->password = 'bar';
|
|
|
|
$expect = AuthenticationResponse::newPass( 'Foo' );
|
|
$expect->createRequest = clone $req;
|
|
$expect->createRequest->username = 'Foo';
|
|
$this->assertEquals( $expect, $provider->beginPrimaryAccountCreation( $user, $user, $reqs ) );
|
|
|
|
$user = $this->getTestSysop()->getUser();
|
|
$req->username = $user->getName();
|
|
$req->password = 'NewPassword';
|
|
$expect = AuthenticationResponse::newPass( $user->getName() );
|
|
$expect->createRequest = $req;
|
|
|
|
$res2 = $provider->beginPrimaryAccountCreation( $user, $user, $reqs );
|
|
$this->assertEquals( $expect, $res2 );
|
|
|
|
$ret = $provider->beginPrimaryAuthentication( $reqs );
|
|
$this->assertEquals( AuthenticationResponse::FAIL, $ret->status );
|
|
|
|
$this->assertNull( $provider->finishAccountCreation( $user, $user, $res2 ) );
|
|
$ret = $provider->beginPrimaryAuthentication( $reqs );
|
|
$this->assertEquals( AuthenticationResponse::PASS, $ret->status, 'new password is set' );
|
|
}
|
|
}
|