public function testAbstractSecondaryAuthenticationProvider() { $user = \User::newFromName('UTSysop'); $provider = $this->getMockForAbstractClass(AbstractSecondaryAuthenticationProvider::class); try { $provider->continueSecondaryAuthentication($user, []); $this->fail('Expected exception not thrown'); } catch (\BadMethodCallException $ex) { } try { $provider->continueSecondaryAccountCreation($user, $user, []); $this->fail('Expected exception not thrown'); } catch (\BadMethodCallException $ex) { } $req = $this->getMockForAbstractClass(AuthenticationRequest::class); $this->assertTrue($provider->providerAllowsPropertyChange('foo')); $this->assertEquals(\StatusValue::newGood('ignored'), $provider->providerAllowsAuthenticationDataChange($req)); $this->assertEquals(\StatusValue::newGood(), $provider->testForAccountCreation($user, $user, [])); $this->assertEquals(\StatusValue::newGood(), $provider->testUserForCreation($user, AuthManager::AUTOCREATE_SOURCE_SESSION)); $this->assertEquals(\StatusValue::newGood(), $provider->testUserForCreation($user, false)); $provider->providerChangeAuthenticationData($req); $provider->autoCreatedAccount($user, AuthManager::AUTOCREATE_SOURCE_SESSION); $res = AuthenticationResponse::newPass(); $provider->postAuthentication($user, $res); $provider->postAccountCreation($user, $user, $res); }
public function testAbstractPrimaryAuthenticationProvider() { $user = \User::newFromName('UTSysop'); $provider = $this->getMockForAbstractClass(AbstractPrimaryAuthenticationProvider::class); try { $provider->continuePrimaryAuthentication([]); $this->fail('Expected exception not thrown'); } catch (\BadMethodCallException $ex) { } try { $provider->continuePrimaryAccountCreation($user, $user, []); $this->fail('Expected exception not thrown'); } catch (\BadMethodCallException $ex) { } $req = $this->getMockForAbstractClass(AuthenticationRequest::class); $this->assertTrue($provider->providerAllowsPropertyChange('foo')); $this->assertEquals(\StatusValue::newGood(), $provider->testForAccountCreation($user, $user, [])); $this->assertEquals(\StatusValue::newGood(), $provider->testUserForCreation($user, AuthManager::AUTOCREATE_SOURCE_SESSION)); $this->assertEquals(\StatusValue::newGood(), $provider->testUserForCreation($user, false)); $this->assertNull($provider->finishAccountCreation($user, $user, AuthenticationResponse::newPass())); $provider->autoCreatedAccount($user, AuthManager::AUTOCREATE_SOURCE_SESSION); $res = AuthenticationResponse::newPass(); $provider->postAuthentication($user, $res); $provider->postAccountCreation($user, $user, $res); $provider->postAccountLink($user, $res); $provider->expects($this->once())->method('testUserExists')->with($this->equalTo('foo'))->will($this->returnValue(true)); $this->assertTrue($provider->testUserCanAuthenticate('foo')); }
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); }
protected function doPrecheck(array &$predicates) { $status = StatusValue::newGood(); // Check if the source file exists if (!$this->fileExists($this->params['src'], $predicates)) { if ($this->getParam('ignoreMissingSource')) { $this->doOperation = false; // no-op // Update file existence predicates (cache 404s) $predicates['exists'][$this->params['src']] = false; $predicates['sha1'][$this->params['src']] = false; return $status; // nothing to do } else { $status->fatal('backend-fail-notexists', $this->params['src']); return $status; } // Check if a file can be placed/changed at the source } elseif (!$this->backend->isPathUsableInternal($this->params['src'])) { $status->fatal('backend-fail-usable', $this->params['src']); $status->fatal('backend-fail-delete', $this->params['src']); return $status; } // Update file existence predicates $predicates['exists'][$this->params['src']] = false; $predicates['sha1'][$this->params['src']] = false; return $status; // safe to call attempt() }
protected function doAttempt() { if (!$this->overwriteSameCase) { // Create the file at the destination return $this->backend->createInternal($this->setFlags($this->params)); } return StatusValue::newGood(); }
public function testDisabled() { $provider = new ThrottlePreAuthenticationProvider(['accountCreationThrottle' => [], 'passwordAttemptThrottle' => [], 'cache' => new \HashBagOStuff()]); $provider->setLogger(new \Psr\Log\NullLogger()); $provider->setConfig(new \HashConfig(['AccountCreationThrottle' => null, 'PasswordAttemptThrottle' => null])); $provider->setManager(AuthManager::singleton()); $this->assertEquals(\StatusValue::newGood(), $provider->testForAccountCreation(\User::newFromName('Created'), \User::newFromName('Creator'), [])); $this->assertEquals(\StatusValue::newGood(), $provider->testForAuthentication([])); }
/** * @see QuorumLockManager::releaseAllLocks() * @return StatusValue */ protected function releaseAllLocks() { $status = StatusValue::newGood(); foreach ($this->conns as $lockDb => $db) { try { $db->query("SELECT pg_advisory_unlock_all()", __METHOD__); } catch (DBError $e) { $status->fatal('lockmanager-fail-db-release', $lockDb); } } return $status; }
protected function doAttempt() { if ($this->overwriteSameCase) { $status = StatusValue::newGood(); // nothing to do } elseif ($this->params['src'] === $this->params['dst']) { // Just update the destination file headers $headers = $this->getParam('headers') ?: []; $status = $this->backend->describeInternal($this->setFlags(['src' => $this->params['dst'], 'headers' => $headers])); } else { // Copy the file to the destination $status = $this->backend->copyInternal($this->setFlags($this->params)); } return $status; }
public function testAbstractPreAuthenticationProvider() { $user = \User::newFromName('UTSysop'); $provider = $this->getMockForAbstractClass(AbstractPreAuthenticationProvider::class); $this->assertEquals([], $provider->getAuthenticationRequests(AuthManager::ACTION_LOGIN, [])); $this->assertEquals(\StatusValue::newGood(), $provider->testForAuthentication([])); $this->assertEquals(\StatusValue::newGood(), $provider->testForAccountCreation($user, $user, [])); $this->assertEquals(\StatusValue::newGood(), $provider->testUserForCreation($user, AuthManager::AUTOCREATE_SOURCE_SESSION)); $this->assertEquals(\StatusValue::newGood(), $provider->testUserForCreation($user, false)); $this->assertEquals(\StatusValue::newGood(), $provider->testForAccountLink($user)); $res = AuthenticationResponse::newPass(); $provider->postAuthentication($user, $res); $provider->postAccountCreation($user, $user, $res); $provider->postAccountLink($user, $res); }
/** * @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'); }
protected function doPrecheck(array &$predicates) { $status = StatusValue::newGood(); // Check if the source file exists if (!$this->fileExists($this->params['src'], $predicates)) { $status->fatal('backend-fail-notexists', $this->params['src']); return $status; // Check if a file can be placed/changed at the source } elseif (!$this->backend->isPathUsableInternal($this->params['src'])) { $status->fatal('backend-fail-usable', $this->params['src']); $status->fatal('backend-fail-describe', $this->params['src']); return $status; } // Update file existence predicates $predicates['exists'][$this->params['src']] = $this->fileExists($this->params['src'], $predicates); $predicates['sha1'][$this->params['src']] = $this->fileSha1($this->params['src'], $predicates); return $status; // safe to call attempt() }
public function testTestUserForCreation() { $provider = new CheckBlocksSecondaryAuthenticationProvider(['blockDisablesLogin' => false]); $provider->setLogger(new \Psr\Log\NullLogger()); $provider->setConfig(new \HashConfig()); $provider->setManager(AuthManager::singleton()); $unblockedUser = \User::newFromName('UTSysop'); $blockedUser = $this->getBlockedUser(); $user = \User::newFromName('RandomUser'); $this->assertEquals(\StatusValue::newGood(), $provider->testUserForCreation($unblockedUser, AuthManager::AUTOCREATE_SOURCE_SESSION)); $this->assertEquals(\StatusValue::newGood(), $provider->testUserForCreation($unblockedUser, false)); $status = $provider->testUserForCreation($blockedUser, AuthManager::AUTOCREATE_SOURCE_SESSION); $this->assertInstanceOf('StatusValue', $status); $this->assertFalse($status->isOK()); $this->assertTrue($status->hasMessage('cantcreateaccount-text')); $status = $provider->testUserForCreation($blockedUser, false); $this->assertInstanceOf('StatusValue', $status); $this->assertFalse($status->isOK()); $this->assertTrue($status->hasMessage('cantcreateaccount-text')); }
protected function doAttempt() { if ($this->overwriteSameCase) { if ($this->params['src'] === $this->params['dst']) { // Do nothing to the destination (which is also the source) $status = StatusValue::newGood(); } else { // Just delete the source as the destination file needs no changes $status = $this->backend->deleteInternal($this->setFlags(['src' => $this->params['src']])); } } elseif ($this->params['src'] === $this->params['dst']) { // Just update the destination file headers $headers = $this->getParam('headers') ?: []; $status = $this->backend->describeInternal($this->setFlags(['src' => $this->params['dst'], 'headers' => $headers])); } else { // Move the file to the destination $status = $this->backend->moveInternal($this->setFlags($this->params)); } return $status; }
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; }
/** * @see FileJournal::doPurgeOldLogs() * @return StatusValue */ protected function doPurgeOldLogs() { return StatusValue::newGood(); }
public function testTestForAccountCreation() { $user = \User::newFromName('foo'); $plugin = $this->getMock('AuthPlugin'); $plugin->expects($this->any())->method('domainList')->willReturn([]); $provider = new AuthPluginPrimaryAuthenticationProvider($plugin); $this->assertEquals(\StatusValue::newGood(), $provider->testForAccountCreation($user, $user, [])); }
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 testForAccountCreation($user, $creator, array $reqs) { $req = AuthenticationRequest::getRequestByClass($reqs, PasswordAuthenticationRequest::class); $ret = \StatusValue::newGood(); if (!$this->loginOnly && $req && $req->username !== null && $req->password !== null) { if ($req->password !== $req->retype) { $ret->fatal('badretype'); } else { $ret->merge($this->checkPasswordValidity($user->getName(), $req->password)); } } return $ret; }
/** * Attempt to perform a series of file operations. * Callers are responsible for handling file locking. * * $opts is an array of options, including: * - force : Errors that would normally cause a rollback do not. * The remaining operations are still attempted if any fail. * - nonJournaled : Don't log this operation batch in the file journal. * - concurrency : Try to do this many operations in parallel when possible. * * The resulting StatusValue will be "OK" unless: * - a) unexpected operation errors occurred (network partitions, disk full...) * - b) significant operation errors occurred and 'force' was not set * * @param FileOp[] $performOps List of FileOp operations * @param array $opts Batch operation options * @param FileJournal $journal Journal to log operations to * @return StatusValue */ public static function attempt(array $performOps, array $opts, FileJournal $journal) { $status = StatusValue::newGood(); $n = count($performOps); if ($n > self::MAX_BATCH_SIZE) { $status->fatal('backend-fail-batchsize', $n, self::MAX_BATCH_SIZE); return $status; } $batchId = $journal->getTimestampedUUID(); $ignoreErrors = !empty($opts['force']); $journaled = empty($opts['nonJournaled']); $maxConcurrency = isset($opts['concurrency']) ? $opts['concurrency'] : 1; $entries = []; // file journal entry list $predicates = FileOp::newPredicates(); // account for previous ops in prechecks $curBatch = []; // concurrent FileOp sub-batch accumulation $curBatchDeps = FileOp::newDependencies(); // paths used in FileOp sub-batch $pPerformOps = []; // ordered list of concurrent FileOp sub-batches $lastBackend = null; // last op backend name // Do pre-checks for each operation; abort on failure... foreach ($performOps as $index => $fileOp) { $backendName = $fileOp->getBackend()->getName(); $fileOp->setBatchId($batchId); // transaction ID // Decide if this op can be done concurrently within this sub-batch // or if a new concurrent sub-batch must be started after this one... if ($fileOp->dependsOn($curBatchDeps) || count($curBatch) >= $maxConcurrency || $backendName !== $lastBackend && count($curBatch)) { $pPerformOps[] = $curBatch; // push this batch $curBatch = []; // start a new sub-batch $curBatchDeps = FileOp::newDependencies(); } $lastBackend = $backendName; $curBatch[$index] = $fileOp; // keep index // Update list of affected paths in this batch $curBatchDeps = $fileOp->applyDependencies($curBatchDeps); // Simulate performing the operation... $oldPredicates = $predicates; $subStatus = $fileOp->precheck($predicates); // updates $predicates $status->merge($subStatus); if ($subStatus->isOK()) { if ($journaled) { // journal log entries $entries = array_merge($entries, $fileOp->getJournalEntries($oldPredicates, $predicates)); } } else { // operation failed? $status->success[$index] = false; ++$status->failCount; if (!$ignoreErrors) { return $status; // abort } } } // Push the last sub-batch if (count($curBatch)) { $pPerformOps[] = $curBatch; } // Log the operations in the file journal... if (count($entries)) { $subStatus = $journal->logChangeBatch($entries, $batchId); if (!$subStatus->isOK()) { $status->merge($subStatus); return $status; // abort } } if ($ignoreErrors) { // treat precheck() fatals as mere warnings $status->setResult(true, $status->value); } // Attempt each operation (in parallel if allowed and possible)... self::runParallelBatches($pPerformOps, $status); return $status; }
/** * @dataProvider provideTestUserForCreation * @param string|null $error * @param string|null $failMsg */ public function testTestUserForCreation($error, $failMsg) { $this->hook('AbortNewAccount', $this->never()); $this->hook('AbortAutoAccount', $this->once())->will($this->returnCallback(function ($user, &$abortError) use($error) { $this->assertInstanceOf('User', $user); $this->assertSame('UTSysop', $user->getName()); $abortError = $error; return $error === null; })); $status = $this->getProvider()->testUserForCreation(\User::newFromName('UTSysop'), AuthManager::AUTOCREATE_SOURCE_SESSION); $this->unhook('AbortNewAccount'); $this->unhook('AbortAutoAccount'); if ($failMsg === null) { $this->assertEquals(\StatusValue::newGood(), $status, 'should succeed'); } else { $this->assertInstanceOf('StatusValue', $status, 'should fail (type)'); $this->assertFalse($status->isOk(), 'should fail (ok)'); $errors = $status->getErrors(); $this->assertEquals($failMsg, $errors[0]['message'], 'should fail (message)'); } $this->hook('AbortAutoAccount', $this->never()); $this->hook('AbortNewAccount', $this->once())->will($this->returnCallback(function ($user, &$abortError, &$abortStatus) use($error) { $this->assertInstanceOf('User', $user); $this->assertSame('UTSysop', $user->getName()); $abortError = $error; return $error === null; })); $status = $this->getProvider()->testUserForCreation(\User::newFromName('UTSysop'), false); $this->unhook('AbortNewAccount'); $this->unhook('AbortAutoAccount'); if ($failMsg === null) { $this->assertEquals(\StatusValue::newGood(), $status, 'should succeed'); } else { $this->assertInstanceOf('StatusValue', $status, 'should fail (type)'); $this->assertFalse($status->isOk(), 'should fail (ok)'); $errors = $status->getErrors(); $msg = $errors[0]['message']; $this->assertInstanceOf(\Message::class, $msg); $this->assertEquals('createaccount-hook-aborted', $msg->getKey(), 'should fail (message)'); } if ($error !== false) { $this->hook('AbortAutoAccount', $this->never()); $this->hook('AbortNewAccount', $this->once())->will($this->returnCallback(function ($user, &$abortError, &$abortStatus) use($error) { $this->assertInstanceOf('User', $user); $this->assertSame('UTSysop', $user->getName()); $abortStatus = $error ? \Status::newFatal($error) : \Status::newGood(); return $error === null; })); $status = $this->getProvider()->testUserForCreation(\User::newFromName('UTSysop'), false); $this->unhook('AbortNewAccount'); $this->unhook('AbortAutoAccount'); if ($failMsg === null) { $this->assertEquals(\StatusValue::newGood(), $status, 'should succeed'); } else { $this->assertInstanceOf('StatusValue', $status, 'should fail (type)'); $this->assertFalse($status->isOk(), 'should fail (ok)'); $errors = $status->getErrors(); $this->assertEquals($failMsg, $errors[0]['message'], 'should fail (message)'); } } }
/** * 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); }
/** * @see FileJournal::purgeOldLogs() * @return StatusValue * @throws DBError */ protected function doPurgeOldLogs() { $status = StatusValue::newGood(); if ($this->ttlDays <= 0) { return $status; // nothing to do } $dbw = $this->getMasterDB(); $dbCutoff = $dbw->timestamp(time() - 86400 * $this->ttlDays); $dbw->delete('filejournal', ['fj_timestamp < ' . $dbw->addQuotes($dbCutoff)], __METHOD__); return $status; }
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); }
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); }
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'); }
public function testUserForCreation($user, $autocreate, array $options = []) { return \StatusValue::newGood(); }
/** * Check for errors with regards to the destination file already existing. * Also set the destExists, overwriteSameCase and sourceSha1 member variables. * A bad StatusValue will be returned if there is no chance it can be overwritten. * * @param array $predicates * @return StatusValue */ protected function precheckDestExistence(array $predicates) { $status = StatusValue::newGood(); // Get hash of source file/string and the destination file $this->sourceSha1 = $this->getSourceSha1Base36(); // FS file or data string if ($this->sourceSha1 === null) { // file in storage? $this->sourceSha1 = $this->fileSha1($this->params['src'], $predicates); } $this->overwriteSameCase = false; $this->destExists = $this->fileExists($this->params['dst'], $predicates); if ($this->destExists) { if ($this->getParam('overwrite')) { return $status; // OK } elseif ($this->getParam('overwriteSame')) { $dhash = $this->fileSha1($this->params['dst'], $predicates); // Check if hashes are valid and match each other... if (!strlen($this->sourceSha1) || !strlen($dhash)) { $status->fatal('backend-fail-hashes'); } elseif ($this->sourceSha1 !== $dhash) { // Give an error if the files are not identical $status->fatal('backend-fail-notsame', $this->params['dst']); } else { $this->overwriteSameCase = true; // OK } return $status; // do nothing; either OK or bad status } else { $status->fatal('backend-fail-alreadyexists', $this->params['dst']); return $status; } } return $status; }
/** * Attempt to release locks with the peers for a bucket * * @param int $bucket * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths * @return StatusValue */ protected final function doUnlockingRequestBucket($bucket, array $pathsByType) { $status = StatusValue::newGood(); $yesVotes = 0; // locks freed on trustable servers $votesLeft = count($this->srvsByBucket[$bucket]); // remaining peers $quorum = floor($votesLeft / 2 + 1); // simple majority $isDegraded = isset($this->degradedBuckets[$bucket]); // not the normal quorum? foreach ($this->srvsByBucket[$bucket] as $lockSrv) { if (!$this->isServerUp($lockSrv)) { $status->warning('lockmanager-fail-svr-release', $lockSrv); } else { // Attempt to release the lock on this peer $status->merge($this->freeLocksOnServer($lockSrv, $pathsByType)); ++$yesVotes; // success for this peer // Normally the first peers form the quorum, and the others are ignored. // Ignore them in this case, but not when an alternative quorum was used. if ($yesVotes >= $quorum && !$isDegraded) { break; // lock released } } } // Set a bad StatusValue if the quorum was not met. // Assumes the same "up" servers as during the acquire step. $status->setResult($yesVotes >= $quorum); return $status; }
/** * Log changes made by a batch file operation. * * @param array $entries List of file operations (each an array of parameters) which contain: * op : Basic operation name (create, update, delete) * path : The storage path of the file * newSha1 : The final base 36 SHA-1 of the file * Note that 'false' should be used as the SHA-1 for non-existing files. * @param string $batchId UUID string that identifies the operation batch * @return StatusValue */ public final function logChangeBatch(array $entries, $batchId) { if (!count($entries)) { return StatusValue::newGood(); } return $this->doLogChangeBatch($entries, $batchId); }
protected function freeLocksOnServer($lockSrv, array $pathsByType) { return StatusValue::newGood(); }