Add doc-typehints to class properties found by the PropertyDocumentation sniff to improve the documentation. Once the sniff is enabled it avoids that new code is missing type declarations. This is focused on documentation and does not change code. Change-Id: Ib6081f5519d2294bb14f81bf399f9c45315f2b69
771 lines
24 KiB
PHP
771 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\Password\PasswordFactory;
|
|
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 StatusValue;
|
|
use Wikimedia\TestingAccessWrapper;
|
|
|
|
/**
|
|
* @group AuthManager
|
|
* @group Database
|
|
* @covers \MediaWiki\Auth\LocalPasswordPrimaryAuthenticationProvider
|
|
*/
|
|
class LocalPasswordPrimaryAuthenticationProviderTest extends MediaWikiIntegrationTestCase {
|
|
use AuthenticationProviderTestTrait;
|
|
use DummyServicesTrait;
|
|
|
|
/** @var AuthManager|null */
|
|
private $manager = null;
|
|
/** @var HashConfig|null */
|
|
private $config = null;
|
|
/** @var Status|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' );
|
|
}
|
|
}
|