checkPasswordValidity is mocked to return $this->validity, * because we don't need to test that here. * * @param array $params * @return TemporaryPasswordPrimaryAuthenticationProvider */ protected function getProvider( $params = [] ) { $mwServices = $this->getServiceContainer(); if ( !$this->config ) { $this->config = new HashConfig( [ MainConfigNames::EnableEmail ] ); } $config = new MultiConfig( [ $this->config, $mwServices->getMainConfig() ] ); $hookContainer = $this->createHookContainer(); if ( !$this->manager ) { $userNameUtils = $this->createNoOpMock( UserNameUtils::class ); $this->manager = new AuthManager( new \MediaWiki\Request\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 = \MediaWiki\Status\Status::newGood(); $mockedMethods[] = 'checkPasswordValidity'; $provider = $this->getMockBuilder( TemporaryPasswordPrimaryAuthenticationProvider::class ) ->onlyMethods( $mockedMethods ) ->setConstructorArgs( [ $mwServices->getDBLoadBalancerFactory(), $mwServices->getUserOptionsLookup(), $params ] ) ->getMock(); $provider->method( 'checkPasswordValidity' ) ->willReturnCallback( function () { return $this->validity; } ); $this->initProvider( $provider, $config, null, $this->manager, null, $this->getServiceContainer()->getUserNameUtils() ); return $provider; } protected function hookMailer( $func = null ) { $hookContainer = $this->getServiceContainer()->getHookContainer(); $this->clearHook( 'AlternateUserMailer' ); if ( $func ) { $reset = $hookContainer->scopedRegister( 'AlternateUserMailer', $func ); } else { $reset = $hookContainer->scopedRegister( 'AlternateUserMailer', function () { $this->fail( 'AlternateUserMailer hook called unexpectedly' ); return false; } ); } return $reset; } public function testBasics() { $provider = $this->getProvider(); $this->assertSame( PrimaryAuthenticationProvider::TYPE_CREATE, $provider->accountCreationType() ); $existingUserName = $this->getTestUser()->getUserIdentity()->getName(); $this->assertTrue( $provider->testUserExists( $existingUserName ) ); $this->assertTrue( $provider->testUserExists( lcfirst( $existingUserName ) ) ); $this->assertFalse( $provider->testUserExists( 'DoesNotExist' ) ); $this->assertFalse( $provider->testUserExists( '' ) ); $req = new PasswordAuthenticationRequest; $req->action = AuthManager::ACTION_CHANGE; $req->username = ''; $provider->providerChangeAuthenticationData( $req ); } public function testConfig() { $config = new \HashConfig( [ MainConfigNames::EnableEmail => false, MainConfigNames::NewPasswordExpiry => 100, MainConfigNames::PasswordReminderResendTime => 101, MainConfigNames::AllowRequiringEmailForResets => false, ] ); $provider = new TemporaryPasswordPrimaryAuthenticationProvider( $this->getServiceContainer()->getDBLoadBalancerFactory(), $this->getServiceContainer()->getUserOptionsLookup() ); $providerPriv = TestingAccessWrapper::newFromObject( $provider ); $this->initProvider( $provider, $config ); $this->assertSame( false, $providerPriv->emailEnabled ); $this->assertSame( 100, $providerPriv->newPasswordExpiry ); $this->assertSame( 101, $providerPriv->passwordReminderResendTime ); $provider = new TemporaryPasswordPrimaryAuthenticationProvider( $this->getServiceContainer()->getDBLoadBalancerFactory(), $this->getServiceContainer()->getUserOptionsLookup(), [ 'emailEnabled' => true, 'newPasswordExpiry' => 42, 'passwordReminderResendTime' => 43, 'allowRequiringEmailForResets' => true, ] ); $providerPriv = TestingAccessWrapper::newFromObject( $provider ); $this->initProvider( $provider, $config ); $this->assertSame( true, $providerPriv->emailEnabled ); $this->assertSame( 42, $providerPriv->newPasswordExpiry ); $this->assertSame( 43, $providerPriv->passwordReminderResendTime ); $this->assertSame( true, $providerPriv->allowRequiringEmail ); } public function testTestUserCanAuthenticate() { $user = self::getMutableTestUser()->getUser(); $dbw = wfGetDB( DB_PRIMARY ); $config = $this->getServiceContainer()->getMainConfig(); // A is unsalted MD5 (thus fast) ... we don't care about security here, this is test only $passwordFactory = new PasswordFactory( $config->get( MainConfigNames::PasswordConfig ), 'A' ); $pwhash = $passwordFactory->newFromPlaintext( 'password' )->toString(); $provider = $this->getProvider(); $providerPriv = TestingAccessWrapper::newFromObject( $provider ); $this->assertFalse( $provider->testUserCanAuthenticate( '' ) ); $this->assertFalse( $provider->testUserCanAuthenticate( 'DoesNotExist' ) ); $dbw->newUpdateQueryBuilder() ->update( 'user' ) ->set( [ 'user_newpassword' => \PasswordFactory::newInvalidPassword()->toString(), 'user_newpass_time' => null, ] ) ->where( [ 'user_id' => $user->getId() ] ) ->execute(); $this->assertFalse( $provider->testUserCanAuthenticate( $user->getName() ) ); $dbw->newUpdateQueryBuilder() ->update( 'user' ) ->set( [ 'user_newpassword' => $pwhash, 'user_newpass_time' => null, ] ) ->where( [ 'user_id' => $user->getId() ] ) ->execute(); $this->assertTrue( $provider->testUserCanAuthenticate( $user->getName() ) ); $this->assertTrue( $provider->testUserCanAuthenticate( lcfirst( $user->getName() ) ) ); $dbw->newUpdateQueryBuilder() ->update( 'user' ) ->set( [ 'user_newpassword' => $pwhash, 'user_newpass_time' => $dbw->timestamp( time() - 10 ) ] ) ->where( [ 'user_id' => $user->getId() ] ) ->execute(); $providerPriv->newPasswordExpiry = 100; $this->assertTrue( $provider->testUserCanAuthenticate( $user->getName() ) ); $providerPriv->newPasswordExpiry = 1; $this->assertFalse( $provider->testUserCanAuthenticate( $user->getName() ) ); $dbw->newUpdateQueryBuilder() ->update( 'user' ) ->set( [ 'user_newpassword' => \PasswordFactory::newInvalidPassword()->toString(), 'user_newpass_time' => null, ] ) ->where( [ 'user_id' => $user->getId() ] ) ->execute(); } /** * @dataProvider provideGetAuthenticationRequests * @param string $action * @param bool $registered * @param array $expected */ public function testGetAuthenticationRequests( $action, bool $registered, $expected ) { $options = [ 'username' => $registered ? 'TestGetAuthenticationRequests' : null ]; $actual = $this->getProvider( [ 'emailEnabled' => true ] ) ->getAuthenticationRequests( $action, $options ); foreach ( $actual as $req ) { if ( $req instanceof TemporaryPasswordAuthenticationRequest && $req->password !== null ) { $req->password = 'random'; } } $this->assertEquals( $expected, $actual ); } public static function provideGetAuthenticationRequests() { $anon = false; $registered = true; return [ [ AuthManager::ACTION_LOGIN, $anon, [ new PasswordAuthenticationRequest ] ], [ AuthManager::ACTION_LOGIN, $registered, [ new PasswordAuthenticationRequest ] ], [ AuthManager::ACTION_CREATE, $anon, [] ], [ AuthManager::ACTION_CREATE, $registered, [ new TemporaryPasswordAuthenticationRequest( 'random' ) ] ], [ AuthManager::ACTION_LINK, $anon, [] ], [ AuthManager::ACTION_LINK, $registered, [] ], [ AuthManager::ACTION_CHANGE, $anon, [ new TemporaryPasswordAuthenticationRequest( 'random' ) ] ], [ AuthManager::ACTION_CHANGE, $registered, [ new TemporaryPasswordAuthenticationRequest( 'random' ) ] ], [ AuthManager::ACTION_REMOVE, $anon, [ new TemporaryPasswordAuthenticationRequest ] ], [ AuthManager::ACTION_REMOVE, $registered, [ new TemporaryPasswordAuthenticationRequest ] ], ]; } public function testAuthentication() { $user = self::getMutableTestUser()->getUser(); $password = 'TemporaryPassword'; $hash = ':A:' . md5( $password ); $dbw = wfGetDB( DB_PRIMARY ); $dbw->newUpdateQueryBuilder() ->update( 'user' ) ->set( [ 'user_newpassword' => $hash, 'user_newpass_time' => $dbw->timestamp( time() - 10 ) ] ) ->where( [ 'user_id' => $user->getId() ] ) ->execute(); $req = new PasswordAuthenticationRequest(); $req->action = AuthManager::ACTION_LOGIN; $reqs = [ PasswordAuthenticationRequest::class => $req ]; $provider = $this->getProvider(); $providerPriv = TestingAccessWrapper::newFromObject( $provider ); $providerPriv->newPasswordExpiry = 100; // 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 = ''; $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::newAbstain(), $provider->beginPrimaryAuthentication( $reqs ) ); // Validation failure $req->username = $user->getName(); $req->password = $password; $this->validity = \MediaWiki\Status\Status::newFatal( 'arbitrary-failure' ); $ret = $provider->beginPrimaryAuthentication( $reqs ); $this->assertEquals( AuthenticationResponse::FAIL, $ret->status ); $this->assertEquals( 'fatalpassworderror', $ret->message->getKey() ); $this->assertEquals( 'arbitrary-failure', $ret->message->getParams()[0]->getKey() ); // Successful auth $this->manager->removeAuthenticationSessionData( null ); $this->validity = \MediaWiki\Status\Status::newGood(); $this->assertEquals( AuthenticationResponse::newPass( $user->getName() ), $provider->beginPrimaryAuthentication( $reqs ) ); $this->assertNotNull( $this->manager->getAuthenticationSessionData( 'reset-pass' ) ); $this->manager->removeAuthenticationSessionData( null ); $this->validity = \MediaWiki\Status\Status::newGood(); $req->username = lcfirst( $user->getName() ); $this->assertEquals( AuthenticationResponse::newPass( $user->getName() ), $provider->beginPrimaryAuthentication( $reqs ) ); $this->assertNotNull( $this->manager->getAuthenticationSessionData( 'reset-pass' ) ); $req->username = $user->getName(); // Expired password $providerPriv->newPasswordExpiry = 1; $ret = $provider->beginPrimaryAuthentication( $reqs ); $this->assertEquals( AuthenticationResponse::FAIL, $ret->status ); $this->assertEquals( 'wrongpassword', $ret->message->getKey() ); // Bad password $providerPriv->newPasswordExpiry = 100; $this->validity = \MediaWiki\Status\Status::newGood(); $req->password = 'Wrong'; $ret = $provider->beginPrimaryAuthentication( $reqs ); $this->assertEquals( AuthenticationResponse::FAIL, $ret->status ); $this->assertEquals( 'wrongpassword', $ret->message->getKey() ); } /** * @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 \MediaWiki\Status\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, \MediaWiki\Status\Status $validity, \StatusValue $expect1, \StatusValue $expect2 ) { $user = $usernameGetter( $this->getTestSysop()->getUserIdentity()->getName() ); if ( $type === PasswordAuthenticationRequest::class || $type === TemporaryPasswordAuthenticationRequest::class ) { $req = new $type(); } else { $req = $this->createMock( $type ); } $req->action = AuthManager::ACTION_CHANGE; $req->username = $user; $req->password = 'NewPassword'; $provider = $this->getProvider(); $this->validity = $validity; $this->assertEquals( $expect1, $provider->providerAllowsAuthenticationDataChange( $req, false ) ); $this->assertEquals( $expect2, $provider->providerAllowsAuthenticationDataChange( $req, true ) ); } 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( 'ignored' ), StatusValue::newGood( 'ignored' ) ], [ TemporaryPasswordAuthenticationRequest::class, static fn ( $sysopUsername ) => $sysopUsername, Status::newGood(), StatusValue::newGood(), StatusValue::newGood() ], [ TemporaryPasswordAuthenticationRequest::class, static fn ( $sysopUsername ) => lcfirst( $sysopUsername ), Status::newGood(), StatusValue::newGood(), StatusValue::newGood() ], [ TemporaryPasswordAuthenticationRequest::class, static fn ( $sysopUsername ) => $sysopUsername, Status::wrap( $err ), StatusValue::newGood(), $err ], [ TemporaryPasswordAuthenticationRequest::class, static fn ( $sysopUsername ) => $sysopUsername, Status::newFatal( 'arbitrary-error' ), StatusValue::newGood(), StatusValue::newFatal( 'arbitrary-error' ) ], [ TemporaryPasswordAuthenticationRequest::class, static fn () => 'DoesNotExist', Status::newGood(), StatusValue::newGood(), StatusValue::newGood( 'ignored' ) ], [ TemporaryPasswordAuthenticationRequest::class, static fn () => '', Status::newGood(), StatusValue::newGood(), StatusValue::newGood( 'ignored' ) ], ]; } /** * @dataProvider provideProviderChangeAuthenticationData * @param string $type * @param bool $changed */ public function testProviderChangeAuthenticationData( $type, $changed ) { $user = $this->getTestSysop()->getUserIdentity()->getName(); $oldpass = 'OldTempPassword'; $newpass = 'NewTempPassword'; $dbw = wfGetDB( DB_PRIMARY ); $oldHash = $dbw->newSelectQueryBuilder() ->select( 'user_newpassword' ) ->from( 'user' ) ->where( [ 'user_name' => $user ] ) ->fetchField(); $cb = new ScopedCallback( static function () use ( $dbw, $user, $oldHash ) { $dbw->newUpdateQueryBuilder() ->update( 'user' ) ->set( [ 'user_newpassword' => $oldHash ] ) ->where( [ 'user_name' => $user ] ) ->execute(); } ); $hash = ':A:' . md5( $oldpass ); $dbw->newUpdateQueryBuilder() ->update( 'user' ) ->set( [ 'user_newpassword' => $hash, 'user_newpass_time' => $dbw->timestamp( time() + 10 ) ] ) ->where( [ 'user_name' => $user ] ) ->execute(); $provider = $this->getProvider(); $loginReq = new PasswordAuthenticationRequest(); $loginReq->action = AuthManager::ACTION_CHANGE; $loginReq->username = $user; $loginReq->password = $oldpass; $loginReqs = [ PasswordAuthenticationRequest::class => $loginReq ]; $this->assertEquals( AuthenticationResponse::newPass( $user ), $provider->beginPrimaryAuthentication( $loginReqs ) ); if ( $type === PasswordAuthenticationRequest::class || $type === TemporaryPasswordAuthenticationRequest::class ) { $changeReq = new $type(); } else { $changeReq = $this->createMock( $type ); } $changeReq->action = AuthManager::ACTION_CHANGE; $changeReq->username = $user; $changeReq->password = $newpass; $resetMailer = $this->hookMailer(); $provider->providerChangeAuthenticationData( $changeReq ); ScopedCallback::consume( $resetMailer ); $loginReq->password = $oldpass; $ret = $provider->beginPrimaryAuthentication( $loginReqs ); $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 ( $changed ) { $this->assertEquals( AuthenticationResponse::newPass( $user ), $ret, 'new password should pass' ); $this->assertNotNull( $dbw->newSelectQueryBuilder() ->select( 'user_newpass_time' ) ->from( 'user' ) ->where( [ 'user_name' => $user ] ) ->fetchField() ); } else { $this->assertEquals( AuthenticationResponse::FAIL, $ret->status, 'new password should fail' ); $this->assertEquals( 'wrongpassword', $ret->message->getKey(), 'new password should fail' ); $this->assertNull( $dbw->newSelectQueryBuilder() ->select( 'user_newpass_time' ) ->from( 'user' ) ->where( [ 'user_name' => $user ] ) ->fetchField() ); } } public static function provideProviderChangeAuthenticationData() { return [ [ AuthenticationRequest::class, false ], [ PasswordAuthenticationRequest::class, false ], [ TemporaryPasswordAuthenticationRequest::class, true ], ]; } public function testProviderChangeAuthenticationDataEmail() { $user = self::getMutableTestUser()->getUser(); $dbw = wfGetDB( DB_PRIMARY ); $dbw->newUpdateQueryBuilder() ->update( 'user' ) ->set( [ 'user_newpass_time' => $dbw->timestamp( time() - 5 * 3600 ) ] ) ->where( [ 'user_id' => $user->getId() ] ) ->execute(); $req = TemporaryPasswordAuthenticationRequest::newRandom(); $req->username = $user->getName(); $req->mailpassword = true; $provider = $this->getProvider( [ 'emailEnabled' => false ] ); $status = $provider->providerAllowsAuthenticationDataChange( $req, true ); $this->assertEquals( \StatusValue::newFatal( 'passwordreset-emaildisabled' ), $status ); $provider = $this->getProvider( [ 'emailEnabled' => true, 'passwordReminderResendTime' => 10 ] ); $status = $provider->providerAllowsAuthenticationDataChange( $req, true ); $this->assertEquals( \StatusValue::newFatal( 'throttled-mailpassword', 10 ), $status ); $provider = $this->getProvider( [ 'emailEnabled' => true, 'passwordReminderResendTime' => 3 ] ); $status = $provider->providerAllowsAuthenticationDataChange( $req, true ); $this->assertFalse( $status->hasMessage( 'throttled-mailpassword' ) ); $dbw->newUpdateQueryBuilder() ->update( 'user' ) ->set( [ 'user_newpass_time' => $dbw->timestamp( time() + 5 * 3600 ) ] ) ->where( [ 'user_id' => $user->getId() ] ) ->execute(); $provider = $this->getProvider( [ 'emailEnabled' => true, 'passwordReminderResendTime' => 0 ] ); $status = $provider->providerAllowsAuthenticationDataChange( $req, true ); $this->assertFalse( $status->hasMessage( 'throttled-mailpassword' ) ); $req->caller = null; $status = $provider->providerAllowsAuthenticationDataChange( $req, true ); $this->assertEquals( \StatusValue::newFatal( 'passwordreset-nocaller' ), $status ); $req->caller = '127.0.0.256'; $status = $provider->providerAllowsAuthenticationDataChange( $req, true ); $this->assertEquals( \StatusValue::newFatal( 'passwordreset-nosuchcaller', '127.0.0.256' ), $status ); $req->caller = ''; $status = $provider->providerAllowsAuthenticationDataChange( $req, true ); $this->assertEquals( \StatusValue::newFatal( 'passwordreset-nosuchcaller', '' ), $status ); $req->caller = '127.0.0.1'; $status = $provider->providerAllowsAuthenticationDataChange( $req, true ); $this->assertEquals( \StatusValue::newGood(), $status ); $req->caller = $user->getName(); $status = $provider->providerAllowsAuthenticationDataChange( $req, true ); $this->assertEquals( \StatusValue::newGood(), $status ); $mailed = false; $resetMailer = $this->hookMailer( function ( $headers, $to, $from, $subject, $body ) use ( &$mailed, $req, $user ) { $mailed = true; $this->assertSame( $user->getEmail(), $to[0]->address ); $this->assertStringContainsString( $req->password, $body ); return false; } ); $provider->providerChangeAuthenticationData( $req ); ScopedCallback::consume( $resetMailer ); $this->assertTrue( $mailed ); $priv = TestingAccessWrapper::newFromObject( $provider ); $req->username = ''; $status = $priv->sendPasswordResetEmail( $req ); $this->assertEquals( \MediaWiki\Status\Status::newFatal( 'noname' ), $status ); } public function testTestForAccountCreation() { $user = User::newFromName( 'foo' ); $req = new TemporaryPasswordAuthenticationRequest(); $req->username = 'Foo'; $req->password = 'Bar'; $reqs = [ TemporaryPasswordAuthenticationRequest::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' ); $this->validity->error( 'arbitrary warning' ); $expect = \StatusValue::newGood(); $expect->error( 'arbitrary warning' ); $this->assertEquals( $expect, $provider->testForAccountCreation( $user, $user, $reqs ), 'Password request, not validated' ); } public function testAccountCreation() { $resetMailer = $this->hookMailer(); $user = User::newFromName( 'Foo' ); $req = new TemporaryPasswordAuthenticationRequest(); $reqs = [ TemporaryPasswordAuthenticationRequest::class => $req ]; $authreq = new PasswordAuthenticationRequest(); $authreq->action = AuthManager::ACTION_CREATE; $authreqs = [ PasswordAuthenticationRequest::class => $authreq ]; $provider = $this->getProvider(); $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 ) ); $this->assertNull( $this->manager->getAuthenticationSessionData( 'no-email' ) ); $user = self::getMutableTestUser()->getUser(); $req->username = $authreq->username = $user->getName(); $req->password = $authreq->password = 'NewPassword'; $expect = AuthenticationResponse::newPass( $user->getName() ); $expect->createRequest = $req; $res2 = $provider->beginPrimaryAccountCreation( $user, $user, $reqs ); $this->assertEquals( $expect, $res2 ); $ret = $provider->beginPrimaryAuthentication( $authreqs ); $this->assertEquals( AuthenticationResponse::FAIL, $ret->status ); $this->assertSame( null, $provider->finishAccountCreation( $user, $user, $res2 ) ); $ret = $provider->beginPrimaryAuthentication( $authreqs ); $this->assertEquals( AuthenticationResponse::PASS, $ret->status, 'new password is set' ); } public function testAccountCreationEmail() { $creator = User::newFromName( 'Foo' ); $user = self::getMutableTestUser()->getUser(); $user->setEmail( '' ); $req = TemporaryPasswordAuthenticationRequest::newRandom(); $req->username = $user->getName(); $req->mailpassword = true; $provider = $this->getProvider( [ 'emailEnabled' => false ] ); $status = $provider->testForAccountCreation( $user, $creator, [ $req ] ); $this->assertEquals( \StatusValue::newFatal( 'emaildisabled' ), $status ); $provider = $this->getProvider( [ 'emailEnabled' => true ] ); $status = $provider->testForAccountCreation( $user, $creator, [ $req ] ); $this->assertEquals( \StatusValue::newFatal( 'noemailcreate' ), $status ); $user->setEmail( 'test@localhost.localdomain' ); $status = $provider->testForAccountCreation( $user, $creator, [ $req ] ); $this->assertEquals( \StatusValue::newGood(), $status ); $mailed = false; $resetMailer = $this->hookMailer( function ( $headers, $to, $from, $subject, $body ) use ( &$mailed, $req ) { $mailed = true; $this->assertSame( 'test@localhost.localdomain', $to[0]->address ); $this->assertStringContainsString( $req->password, $body ); return false; } ); $expect = AuthenticationResponse::newPass( $user->getName() ); $expect->createRequest = clone $req; $expect->createRequest->username = $user->getName(); $res = $provider->beginPrimaryAccountCreation( $user, $creator, [ $req ] ); $this->assertEquals( $expect, $res ); $this->assertTrue( $this->manager->getAuthenticationSessionData( 'no-email' ) ); $this->assertFalse( $mailed ); $this->assertSame( 'byemail', $provider->finishAccountCreation( $user, $creator, $res ) ); $this->assertTrue( $mailed ); ScopedCallback::consume( $resetMailer ); $this->assertTrue( $mailed ); } }