예제 #1
0
 /**
  * @dataProvider provideAllowsAuthenticationDataChange
  * @param StatusValue $primaryReturn
  * @param StatusValue $secondaryReturn
  * @param Status $expect
  */
 public function testAllowsAuthenticationDataChange($primaryReturn, $secondaryReturn, $expect)
 {
     $req = $this->getMockForAbstractClass(AuthenticationRequest::class);
     $mock1 = $this->getMockForAbstractClass(PrimaryAuthenticationProvider::class);
     $mock1->expects($this->any())->method('getUniqueId')->will($this->returnValue('1'));
     $mock1->expects($this->any())->method('providerAllowsAuthenticationDataChange')->with($this->equalTo($req))->will($this->returnValue($primaryReturn));
     $mock2 = $this->getMockForAbstractClass(SecondaryAuthenticationProvider::class);
     $mock2->expects($this->any())->method('getUniqueId')->will($this->returnValue('2'));
     $mock2->expects($this->any())->method('providerAllowsAuthenticationDataChange')->with($this->equalTo($req))->will($this->returnValue($secondaryReturn));
     $this->primaryauthMocks = [$mock1];
     $this->secondaryauthMocks = [$mock2];
     $this->initializeManager(true);
     $this->assertEquals($expect, $this->manager->allowsAuthenticationDataChange($req));
 }
예제 #2
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);
 }