public function execute($subPage) { $this->setHeaders(); $this->loadAuth($subPage); $this->outputHeader(); $status = $this->trySubmit(); if ($status === false || !$status->isOK()) { $this->displayForm($status); return; } /** @var AuthenticationResponse $response */ $response = $status->getValue(); if ($response->status === AuthenticationResponse::FAIL) { $this->displayForm(StatusValue::newFatal($response->message)); return; } $status = StatusValue::newGood(); $status->warning(wfMessage('unlinkaccounts-success')); $this->loadAuth($subPage, null, true); // update requests so the unlinked one doesn't show up // Reset sessions - if the user unlinked an account because it was compromised, // log attackers out from sessions obtained via that account. $session = $this->getRequest()->getSession(); $user = $this->getUser(); SessionManager::singleton()->invalidateSessionsForUser($user); $session->setUser($user); $session->resetId(); $this->displayForm($status); }
/** * @dataProvider provideTestForAccountCreation * @param string $creatorname * @param bool $succeed * @param bool $hook */ public function testTestForAccountCreation($creatorname, $succeed, $hook) { $provider = new ThrottlePreAuthenticationProvider(['accountCreationThrottle' => [['count' => 2, 'seconds' => 86400]], 'cache' => new \HashBagOStuff()]); $provider->setLogger(new \Psr\Log\NullLogger()); $provider->setConfig(new \HashConfig(['AccountCreationThrottle' => null, 'PasswordAttemptThrottle' => null])); $provider->setManager(AuthManager::singleton()); $user = \User::newFromName('RandomUser'); $creator = \User::newFromName($creatorname); if ($hook) { $mock = $this->getMock('stdClass', ['onExemptFromAccountCreationThrottle']); $mock->expects($this->any())->method('onExemptFromAccountCreationThrottle')->will($this->returnValue(false)); $this->mergeMwGlobalArrayValue('wgHooks', ['ExemptFromAccountCreationThrottle' => [$mock]]); } $this->assertEquals(\StatusValue::newGood(), $provider->testForAccountCreation($user, $creator, []), 'attempt #1'); $this->assertEquals(\StatusValue::newGood(), $provider->testForAccountCreation($user, $creator, []), 'attempt #2'); $this->assertEquals($succeed ? \StatusValue::newGood() : \StatusValue::newFatal('acct_creation_throttle_hit', 2), $provider->testForAccountCreation($user, $creator, []), 'attempt #3'); }
public function testForAccountCreation($user, $creator, array $reqs) { /** @var TemporaryPasswordAuthenticationRequest $req */ $req = AuthenticationRequest::getRequestByClass($reqs, TemporaryPasswordAuthenticationRequest::class); $ret = \StatusValue::newGood(); if ($req) { if ($req->mailpassword && !$req->hasBackchannel) { if (!$this->emailEnabled) { $ret->merge(\StatusValue::newFatal('emaildisabled')); } elseif (!$user->getEmail()) { $ret->merge(\StatusValue::newFatal('noemailcreate')); } } $ret->merge($this->checkPasswordValidity($user->getName(), $req->password)); } return $ret; }
public static function provideProviderAllowsAuthenticationDataChange() { $domains = ['foo', 'bar']; $reqNoDomain = new PasswordDomainAuthenticationRequest($domains); $reqValidDomain = new PasswordDomainAuthenticationRequest($domains); $reqValidDomain->domain = 'foo'; $reqInvalidDomain = new PasswordDomainAuthenticationRequest($domains); $reqInvalidDomain->domain = 'invalid'; return [[AuthenticationRequest::class, null, \StatusValue::newGood('ignored')], [new PasswordAuthenticationRequest(), true, \StatusValue::newGood()], [new PasswordAuthenticationRequest(), false, \StatusValue::newFatal('authmanager-authplugin-setpass-denied')], [$reqNoDomain, true, \StatusValue::newGood('ignored')], [$reqValidDomain, true, \StatusValue::newGood()], [$reqInvalidDomain, true, \StatusValue::newFatal('authmanager-authplugin-setpass-bad-domain')]]; }
public static function provideTestForAccountCreation() { return ['No hook errors' => [null, null, \StatusValue::newGood()], 'AbortNewAccount, old style' => ['foobar', null, \StatusValue::newFatal(\Message::newFromKey('createaccount-hook-aborted')->rawParams('foobar'))], 'AbortNewAccount, new style' => ['foobar', \Status::newFatal('aborted!', 'param'), \StatusValue::newFatal('aborted!', 'param')]]; }
public static function provideProviderAllowsAuthenticationDataChange() { return [[AuthenticationRequest::class, null, \StatusValue::newGood('ignored')], [PasswordAuthenticationRequest::class, true, \StatusValue::newGood()], [PasswordAuthenticationRequest::class, false, \StatusValue::newFatal('authmanager-authplugin-setpass-denied')]]; }
public function testContinueLinkAttempt() { $user = \User::newFromName('UTSysop'); $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)->setMethods(['beginLinkAttempt'])->getMock(); $mock->expects($this->once())->method('beginLinkAttempt')->with($this->identicalTo($user), $this->identicalTo('state'))->will($this->returnValue($obj)); $this->assertSame($obj, \TestingAccessWrapper::newFromObject($mock)->continueLinkAttempt($user, 'state', $reqs)); // Now test the actual functioning $provider = $this->getMockBuilder(ConfirmLinkSecondaryAuthenticationProvider::class)->setMethods(['beginLinkAttempt', 'providerAllowsAuthenticationDataChange', 'providerChangeAuthenticationData'])->getMock(); $provider->expects($this->never())->method('beginLinkAttempt'); $provider->expects($this->any())->method('providerAllowsAuthenticationDataChange')->will($this->returnCallback(function ($req) use($reqs) { return $req->getUniqueId() === 'Request3' ? \StatusValue::newFatal('foo') : \StatusValue::newGood(); })); $provider->expects($this->any())->method('providerChangeAuthenticationData')->will($this->returnCallback(function ($req) use(&$done) { $done[$req->id] = true; })); $config = new \HashConfig(['AuthManagerConfig' => ['preauth' => [], 'primaryauth' => [], 'secondaryauth' => [['factory' => function () use($provider) { return $provider; }]]]]); $request = new \FauxRequest(); $manager = new AuthManager($request, $config); $provider->setManager($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(), $res = $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); }
/** * Show a success message. */ protected function success() { $this->loadAuth('', AuthManager::ACTION_LINK, true); $this->displayForm(StatusValue::newFatal($this->msg('linkaccounts-success-text'))); }
public function testTestForAccountCreation() { $user = \User::newFromName('foo'); $req = new PasswordAuthenticationRequest(); $req->action = AuthManager::ACTION_CREATE; $req->username = '******'; $req->password = '******'; $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'); }
/** * Do a password reset. Authorization is the caller's responsibility. * * Process the form. At this point we know that the user passes all the criteria in * userCanExecute(), and if the data array contains 'Username', etc, then Username * resets are allowed. * @param User $performingUser The user that does the password reset * @param string $username The user whose password is reset * @param string $email Alternative way to specify the user * @param bool $displayPassword Whether to display the password * @return StatusValue Will contain the passwords as a username => password array if the * $displayPassword flag was set * @throws LogicException When the user is not allowed to perform the action * @throws MWException On unexpected DB errors */ public function execute(User $performingUser, $username = null, $email = null, $displayPassword = false) { if (!$this->isAllowed($performingUser, $displayPassword)->isGood()) { $action = $this->isAllowed($performingUser)->isGood() ? 'display' : 'reset'; throw new LogicException('User ' . $performingUser->getName() . ' is not allowed to ' . $action . ' passwords'); } $resetRoutes = $this->config->get('PasswordResetRoutes') + ['username' => false, 'email' => false]; if ($resetRoutes['username'] && $username) { $method = 'username'; $users = [User::newFromName($username)]; } elseif ($resetRoutes['email'] && $email) { if (!Sanitizer::validateEmail($email)) { return StatusValue::newFatal('passwordreset-invalidemail'); } $method = 'email'; $users = $this->getUsersByEmail($email); } else { // The user didn't supply any data return StatusValue::newFatal('passwordreset-nodata'); } // Check for hooks (captcha etc), and allow them to modify the users list $error = []; $data = ['Username' => $username, 'Email' => $email, 'Capture' => $displayPassword ? '1' : null]; if (!Hooks::run('SpecialPasswordResetOnSubmit', [&$users, $data, &$error])) { return StatusValue::newFatal(Message::newFromSpecifier($error)); } if (!$users) { if ($method === 'email') { // Don't reveal whether or not an email address is in use return StatusValue::newGood([]); } else { return StatusValue::newFatal('noname'); } } $firstUser = $users[0]; if (!$firstUser instanceof User || !$firstUser->getId()) { // Don't parse username as wikitext (bug 65501) return StatusValue::newFatal(wfMessage('nosuchuser', wfEscapeWikiText($username))); } // Check against the rate limiter if ($performingUser->pingLimiter('mailpassword')) { return StatusValue::newFatal('actionthrottledtext'); } // All the users will have the same email address if (!$firstUser->getEmail()) { // This won't be reachable from the email route, so safe to expose the username return StatusValue::newFatal(wfMessage('noemail', wfEscapeWikiText($firstUser->getName()))); } // We need to have a valid IP address for the hook, but per bug 18347, we should // send the user's name if they're logged in. $ip = $performingUser->getRequest()->getIP(); if (!$ip) { return StatusValue::newFatal('badipaddress'); } Hooks::run('User::mailPasswordInternal', [&$performingUser, &$ip, &$firstUser]); $result = StatusValue::newGood(); $reqs = []; foreach ($users as $user) { $req = TemporaryPasswordAuthenticationRequest::newRandom(); $req->username = $user->getName(); $req->mailpassword = true; $req->hasBackchannel = $displayPassword; $req->caller = $performingUser->getName(); $status = $this->authManager->allowsAuthenticationDataChange($req, true); if ($status->isGood() && $status->getValue() !== 'ignored') { $reqs[] = $req; } elseif ($result->isGood()) { // only record the first error, to avoid exposing the number of users having the // same email address if ($status->getValue() === 'ignored') { $status = StatusValue::newFatal('passwordreset-ignored'); } $result->merge($status); } } if (!$result->isGood()) { return $result; } $passwords = []; foreach ($reqs as $req) { $this->authManager->changeAuthenticationData($req); // TODO record mail sending errors if ($displayPassword) { $passwords[$req->username] = $req->password; } } return StatusValue::newGood($passwords); }
public function testForAuthentication(array $reqs) { if (!$this->passwordAttemptThrottle) { return \StatusValue::newGood(); } $ip = $this->manager->getRequest()->getIP(); try { $username = AuthenticationRequest::getUsernameFromRequests($reqs); } catch (\UnexpectedValueException $e) { $username = ''; } // Get everything this username could normalize to, and throttle each one individually. // If nothing uses usernames, just throttle by IP. $usernames = $this->manager->normalizeUsername($username); $result = false; foreach ($usernames as $name) { $r = $this->passwordAttemptThrottle->increase($name, $ip, __METHOD__); if ($r && (!$result || $result['wait'] < $r['wait'])) { $result = $r; } } if ($result) { $message = wfMessage('login-throttled')->durationParams($result['wait']); return \StatusValue::newFatal($message); } else { $this->manager->setAuthenticationSessionData('LoginThrottle', ['users' => $usernames, 'ip' => $ip]); return \StatusValue::newGood(); } }
public function testTryReset() { $user = \User::newFromName('UTSysop'); $provider = $this->getMockBuilder(ResetPasswordSecondaryAuthenticationProvider::class)->setMethods(['providerAllowsAuthenticationDataChange', 'providerChangeAuthenticationData'])->getMock(); $provider->expects($this->any())->method('providerAllowsAuthenticationDataChange')->will($this->returnCallback(function ($req) { $this->assertSame('UTSysop', $req->username); return $req->allow; })); $provider->expects($this->any())->method('providerChangeAuthenticationData')->will($this->returnCallback(function ($req) { $this->assertSame('UTSysop', $req->username); $req->done = true; })); $config = new \HashConfig(['AuthManagerConfig' => ['preauth' => [], 'primaryauth' => [], 'secondaryauth' => [['factory' => function () use($provider) { return $provider; }]]]]); $manager = new AuthManager(new \FauxRequest(), $config); $provider->setManager($manager); $provider = \TestingAccessWrapper::newFromObject($provider); $msg = wfMessage('foo'); $skipReq = new ButtonAuthenticationRequest('skipReset', wfMessage('authprovider-resetpass-skip-label'), wfMessage('authprovider-resetpass-skip-help')); $passReq = new PasswordAuthenticationRequest(); $passReq->action = AuthManager::ACTION_CHANGE; $passReq->password = '******'; $passReq->retype = 'Bar'; $passReq->allow = \StatusValue::newGood(); $passReq->done = false; $passReq2 = $this->getMockBuilder(PasswordAuthenticationRequest::class)->enableProxyingToOriginalMethods()->getMock(); $passReq2->action = AuthManager::ACTION_CHANGE; $passReq2->password = '******'; $passReq2->retype = 'Foo'; $passReq2->allow = \StatusValue::newGood(); $passReq2->done = false; $passReq3 = new PasswordAuthenticationRequest(); $passReq3->action = AuthManager::ACTION_LOGIN; $passReq3->password = '******'; $passReq3->retype = 'Foo'; $passReq3->allow = \StatusValue::newGood(); $passReq3->done = false; $this->assertEquals(AuthenticationResponse::newAbstain(), $provider->tryReset($user, [])); $manager->setAuthenticationSessionData('reset-pass', 'foo'); try { $provider->tryReset($user, []); $this->fail('Expected exception not thrown'); } catch (\UnexpectedValueException $ex) { $this->assertSame('reset-pass is not valid', $ex->getMessage()); } $manager->setAuthenticationSessionData('reset-pass', (object) []); try { $provider->tryReset($user, []); $this->fail('Expected exception not thrown'); } catch (\UnexpectedValueException $ex) { $this->assertSame('reset-pass msg is missing', $ex->getMessage()); } $manager->setAuthenticationSessionData('reset-pass', ['msg' => 'foo']); try { $provider->tryReset($user, []); $this->fail('Expected exception not thrown'); } catch (\UnexpectedValueException $ex) { $this->assertSame('reset-pass msg is not valid', $ex->getMessage()); } $manager->setAuthenticationSessionData('reset-pass', ['msg' => $msg]); try { $provider->tryReset($user, []); $this->fail('Expected exception not thrown'); } catch (\UnexpectedValueException $ex) { $this->assertSame('reset-pass hard is missing', $ex->getMessage()); } $manager->setAuthenticationSessionData('reset-pass', ['msg' => $msg, 'hard' => true, 'req' => 'foo']); try { $provider->tryReset($user, []); $this->fail('Expected exception not thrown'); } catch (\UnexpectedValueException $ex) { $this->assertSame('reset-pass req is not valid', $ex->getMessage()); } $manager->setAuthenticationSessionData('reset-pass', ['msg' => $msg, 'hard' => false, 'req' => $passReq3]); try { $provider->tryReset($user, [$passReq]); $this->fail('Expected exception not thrown'); } catch (\UnexpectedValueException $ex) { $this->assertSame('reset-pass req is not valid', $ex->getMessage()); } $manager->setAuthenticationSessionData('reset-pass', ['msg' => $msg, 'hard' => true]); $res = $provider->tryReset($user, []); $this->assertInstanceOf(AuthenticationResponse::class, $res); $this->assertSame(AuthenticationResponse::UI, $res->status); $this->assertEquals($msg, $res->message); $this->assertCount(1, $res->neededRequests); $this->assertInstanceOf(PasswordAuthenticationRequest::class, $res->neededRequests[0]); $this->assertNotNull($manager->getAuthenticationSessionData('reset-pass')); $this->assertFalse($passReq->done); $manager->setAuthenticationSessionData('reset-pass', ['msg' => $msg, 'hard' => false, 'req' => $passReq]); $res = $provider->tryReset($user, []); $this->assertInstanceOf(AuthenticationResponse::class, $res); $this->assertSame(AuthenticationResponse::UI, $res->status); $this->assertEquals($msg, $res->message); $this->assertCount(2, $res->neededRequests); $this->assertEquals($passReq, $res->neededRequests[0]); $this->assertEquals($skipReq, $res->neededRequests[1]); $this->assertNotNull($manager->getAuthenticationSessionData('reset-pass')); $this->assertFalse($passReq->done); $passReq->retype = 'Bad'; $manager->setAuthenticationSessionData('reset-pass', ['msg' => $msg, 'hard' => false, 'req' => $passReq]); $res = $provider->tryReset($user, [$skipReq, $passReq]); $this->assertEquals(AuthenticationResponse::newPass(), $res); $this->assertNull($manager->getAuthenticationSessionData('reset-pass')); $this->assertFalse($passReq->done); $passReq->retype = 'Bad'; $manager->setAuthenticationSessionData('reset-pass', ['msg' => $msg, 'hard' => true]); $res = $provider->tryReset($user, [$skipReq, $passReq]); $this->assertSame(AuthenticationResponse::UI, $res->status); $this->assertSame('badretype', $res->message->getKey()); $this->assertCount(1, $res->neededRequests); $this->assertInstanceOf(PasswordAuthenticationRequest::class, $res->neededRequests[0]); $this->assertNotNull($manager->getAuthenticationSessionData('reset-pass')); $this->assertFalse($passReq->done); $manager->setAuthenticationSessionData('reset-pass', ['msg' => $msg, 'hard' => true]); $res = $provider->tryReset($user, [$skipReq, $passReq3]); $this->assertSame(AuthenticationResponse::UI, $res->status); $this->assertEquals($msg, $res->message); $this->assertCount(1, $res->neededRequests); $this->assertInstanceOf(PasswordAuthenticationRequest::class, $res->neededRequests[0]); $this->assertNotNull($manager->getAuthenticationSessionData('reset-pass')); $this->assertFalse($passReq->done); $passReq->retype = $passReq->password; $passReq->allow = \StatusValue::newFatal('arbitrary-fail'); $res = $provider->tryReset($user, [$skipReq, $passReq]); $this->assertSame(AuthenticationResponse::UI, $res->status); $this->assertSame('arbitrary-fail', $res->message->getKey()); $this->assertCount(1, $res->neededRequests); $this->assertInstanceOf(PasswordAuthenticationRequest::class, $res->neededRequests[0]); $this->assertNotNull($manager->getAuthenticationSessionData('reset-pass')); $this->assertFalse($passReq->done); $passReq->allow = \StatusValue::newGood(); $res = $provider->tryReset($user, [$skipReq, $passReq]); $this->assertEquals(AuthenticationResponse::newPass(), $res); $this->assertNull($manager->getAuthenticationSessionData('reset-pass')); $this->assertTrue($passReq->done); $manager->setAuthenticationSessionData('reset-pass', ['msg' => $msg, 'hard' => false, 'req' => $passReq2]); $res = $provider->tryReset($user, [$passReq2]); $this->assertEquals(AuthenticationResponse::newPass(), $res); $this->assertNull($manager->getAuthenticationSessionData('reset-pass')); $this->assertTrue($passReq2->done); $passReq->done = false; $passReq2->done = false; $manager->setAuthenticationSessionData('reset-pass', ['msg' => $msg, 'hard' => false, 'req' => $passReq2]); $res = $provider->tryReset($user, [$passReq]); $this->assertInstanceOf(AuthenticationResponse::class, $res); $this->assertSame(AuthenticationResponse::UI, $res->status); $this->assertEquals($msg, $res->message); $this->assertCount(2, $res->neededRequests); $this->assertEquals($passReq2, $res->neededRequests[0]); $this->assertEquals($skipReq, $res->neededRequests[1]); $this->assertNotNull($manager->getAuthenticationSessionData('reset-pass')); $this->assertFalse($passReq->done); $this->assertFalse($passReq2->done); }
public function providerAllowsAuthenticationDataChange(AuthenticationRequest $req, $checkData = true) { if (get_class($req) !== $this->requestType) { return \StatusValue::newGood('ignored'); } // Hope it works, AuthPlugin gives us no way to do this. $curDomain = $this->auth->getDomain(); $this->setDomain($req); try { // If !$checkData the domain might be wrong. Nothing we can do about that. if (!$this->auth->allowPasswordChange()) { return \StatusValue::newFatal('authmanager-authplugin-setpass-denied'); } if (!$checkData) { return \StatusValue::newGood(); } if ($this->hasDomain) { if ($req->domain === null) { return \StatusValue::newGood('ignored'); } if (!$this->auth->validDomain($domain)) { return \StatusValue::newFatal('authmanager-authplugin-setpass-bad-domain'); } } $username = User::getCanonicalName($req->username, 'usable'); if ($username !== false) { $sv = \StatusValue::newGood(); if ($req->password !== null) { if ($req->password !== $req->retype) { $sv->fatal('badretype'); } else { $sv->merge($this->checkPasswordValidity($username, $req->password)); } } return $sv; } else { return \StatusValue::newGood('ignored'); } } finally { $this->auth->setDomain($curDomain); } }
public function testContinueAccountCreation() { $creator = \User::newFromName('UTSysop'); $username = self::usernameForCreation(); $this->logger = new \TestLogger(false, function ($message, $level) { return $level === LogLevel::DEBUG ? null : $message; }); $this->initializeManager(); $session = ['userid' => 0, 'username' => $username, 'creatorid' => 0, 'creatorname' => $username, 'reqs' => [], 'primary' => null, 'primaryResponse' => null, 'secondary' => [], 'ranPreTests' => true]; $this->hook('LocalUserCreated', $this->never()); try { $this->manager->continueAccountCreation([]); $this->fail('Expected exception not thrown'); } catch (\LogicException $ex) { $this->assertEquals('Account creation is not possible', $ex->getMessage()); } $this->unhook('LocalUserCreated'); $mock = $this->getMockForAbstractClass(PrimaryAuthenticationProvider::class); $mock->expects($this->any())->method('getUniqueId')->will($this->returnValue('X')); $mock->expects($this->any())->method('accountCreationType')->will($this->returnValue(PrimaryAuthenticationProvider::TYPE_CREATE)); $mock->expects($this->any())->method('testUserExists')->will($this->returnValue(false)); $mock->expects($this->any())->method('beginPrimaryAccountCreation')->will($this->returnValue(AuthenticationResponse::newFail($this->message('fail')))); $this->primaryauthMocks = [$mock]; $this->initializeManager(true); $this->request->getSession()->setSecret('AuthManager::accountCreationState', null); $this->hook('LocalUserCreated', $this->never()); $ret = $this->manager->continueAccountCreation([]); $this->unhook('LocalUserCreated'); $this->assertSame(AuthenticationResponse::FAIL, $ret->status); $this->assertSame('authmanager-create-not-in-progress', $ret->message->getKey()); $this->request->getSession()->setSecret('AuthManager::accountCreationState', ['username' => "{$username}<>"] + $session); $this->hook('LocalUserCreated', $this->never()); $ret = $this->manager->continueAccountCreation([]); $this->unhook('LocalUserCreated'); $this->assertSame(AuthenticationResponse::FAIL, $ret->status); $this->assertSame('noname', $ret->message->getKey()); $this->assertNull($this->request->getSession()->getSecret('AuthManager::accountCreationState')); $this->request->getSession()->setSecret('AuthManager::accountCreationState', $session); $this->hook('LocalUserCreated', $this->never()); $cache = \ObjectCache::getLocalClusterInstance(); $lock = $cache->getScopedLock($cache->makeGlobalKey('account', md5($username))); $ret = $this->manager->continueAccountCreation([]); unset($lock); $this->unhook('LocalUserCreated'); $this->assertSame(AuthenticationResponse::FAIL, $ret->status); $this->assertSame('usernameinprogress', $ret->message->getKey()); // This error shouldn't remove the existing session, because the // raced-with process "owns" it. $this->assertSame($session, $this->request->getSession()->getSecret('AuthManager::accountCreationState')); $this->request->getSession()->setSecret('AuthManager::accountCreationState', ['username' => $creator->getName()] + $session); $this->setMwGlobals(['wgReadOnly' => 'Because']); $this->hook('LocalUserCreated', $this->never()); $ret = $this->manager->continueAccountCreation([]); $this->unhook('LocalUserCreated'); $this->assertSame(AuthenticationResponse::FAIL, $ret->status); $this->assertSame('readonlytext', $ret->message->getKey()); $this->assertSame(['Because'], $ret->message->getParams()); $this->setMwGlobals(['wgReadOnly' => false]); $this->request->getSession()->setSecret('AuthManager::accountCreationState', ['username' => $creator->getName()] + $session); $this->hook('LocalUserCreated', $this->never()); $ret = $this->manager->continueAccountCreation([]); $this->unhook('LocalUserCreated'); $this->assertSame(AuthenticationResponse::FAIL, $ret->status); $this->assertSame('userexists', $ret->message->getKey()); $this->assertNull($this->request->getSession()->getSecret('AuthManager::accountCreationState')); $this->request->getSession()->setSecret('AuthManager::accountCreationState', ['userid' => $creator->getId()] + $session); $this->hook('LocalUserCreated', $this->never()); try { $ret = $this->manager->continueAccountCreation([]); $this->fail('Expected exception not thrown'); } catch (\UnexpectedValueException $ex) { $this->assertEquals("User \"{$username}\" should exist now, but doesn't!", $ex->getMessage()); } $this->unhook('LocalUserCreated'); $this->assertNull($this->request->getSession()->getSecret('AuthManager::accountCreationState')); $id = $creator->getId(); $name = $creator->getName(); $this->request->getSession()->setSecret('AuthManager::accountCreationState', ['username' => $name, 'userid' => $id + 1] + $session); $this->hook('LocalUserCreated', $this->never()); try { $ret = $this->manager->continueAccountCreation([]); $this->fail('Expected exception not thrown'); } catch (\UnexpectedValueException $ex) { $this->assertEquals("User \"{$name}\" exists, but ID {$id} != " . ($id + 1) . '!', $ex->getMessage()); } $this->unhook('LocalUserCreated'); $this->assertNull($this->request->getSession()->getSecret('AuthManager::accountCreationState')); $req = $this->getMockBuilder(UserDataAuthenticationRequest::class)->setMethods(['populateUser'])->getMock(); $req->expects($this->any())->method('populateUser')->willReturn(\StatusValue::newFatal('populatefail')); $this->request->getSession()->setSecret('AuthManager::accountCreationState', ['reqs' => [$req]] + $session); $ret = $this->manager->continueAccountCreation([]); $this->assertSame(AuthenticationResponse::FAIL, $ret->status); $this->assertSame('populatefail', $ret->message->getKey()); $this->assertNull($this->request->getSession()->getSecret('AuthManager::accountCreationState')); }
public function testAccountCreationEmail() { $creator = \User::newFromName('Foo'); $user = \User::newFromName('UTSysop'); $reset = new \ScopedCallback(function ($email) use($user) { $user->setEmail($email); $user->saveSettings(); }, [$user->getEmail()]); $user->setEmail(null); $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); $req->hasBackchannel = true; $status = $provider->testForAccountCreation($user, $creator, [$req]); $this->assertFalse($status->hasMessage('emaildisabled')); $req->hasBackchannel = false; $provider = $this->getProvider(['emailEnabled' => true]); $status = $provider->testForAccountCreation($user, $creator, [$req]); $this->assertEquals(\StatusValue::newFatal('noemailcreate'), $status); $req->hasBackchannel = true; $status = $provider->testForAccountCreation($user, $creator, [$req]); $this->assertFalse($status->hasMessage('noemailcreate')); $req->hasBackchannel = false; $user->setEmail('*****@*****.**'); $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('*****@*****.**', $to[0]->address); $this->assertContains($req->password, $body); return false; }); $expect = AuthenticationResponse::newPass('UTSysop'); $expect->createRequest = clone $req; $expect->createRequest->username = '******'; $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); }
/** * Attempt the operation * * @return StatusValue */ public final function attempt() { if ($this->state !== self::STATE_CHECKED) { return StatusValue::newFatal('fileop-fail-state', self::STATE_CHECKED, $this->state); } elseif ($this->failed) { // failed precheck return StatusValue::newFatal('fileop-fail-attempt-precheck'); } $this->state = self::STATE_ATTEMPTED; if ($this->doOperation) { $status = $this->doAttempt(); if (!$status->isOK()) { $this->failed = true; $this->logFailure('attempt'); } } else { // no-op $status = StatusValue::newGood(); } return $status; }
public static function providePopulateUser() { $good = \StatusValue::newGood(); return [['*****@*****.**', 'Real Name', $good], ['*****@*****.**', '', $good], ['', 'Real Name', $good], ['', '', $good], ['invalid-email', 'Real Name', \StatusValue::newFatal('invalidemailaddress')]]; }