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);
 }
Esempio n. 4
0
 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()
 }
Esempio n. 5
0
 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;
 }
Esempio n. 8
0
 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');
 }
Esempio n. 11
0
 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'));
 }
Esempio n. 13
0
 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;
 }
Esempio n. 15
0
 /**
  * @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;
 }
Esempio n. 19
0
 /**
  * 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)');
         }
     }
 }
Esempio n. 21
0
 /**
  * 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);
 }
Esempio n. 22
0
 /**
  * @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();
 }
Esempio n. 27
0
 /**
  * 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;
 }
Esempio n. 28
0
 /**
  * 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;
 }
Esempio n. 29
0
 /**
  * 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);
 }
Esempio n. 30
0
 protected function freeLocksOnServer($lockSrv, array $pathsByType)
 {
     return StatusValue::newGood();
 }