X-Git-Url: https://git.heureux-cyclage.org/?p=lhc%2Fweb%2Fwiklou.git;a=blobdiff_plain;f=includes%2Fuser%2FPasswordReset.php;h=be7ea915e47ab2de1a2c47c2a20204ab2906652b;hp=8ef1d0d500347f9454540116cab1c023cdd92124;hb=29bee071b23832173a55364453d3132a2fe26101;hpb=f6633d9f46951e57e5c7060953194aeeb7522024 diff --git a/includes/user/PasswordReset.php b/includes/user/PasswordReset.php index 8ef1d0d500..be7ea915e4 100644 --- a/includes/user/PasswordReset.php +++ b/includes/user/PasswordReset.php @@ -61,6 +61,7 @@ class PasswordReset implements LoggerAwareInterface { private $permissionCache; public static $constructorOptions = [ + 'AllowRequiringEmailForResets', 'EnableEmail', 'PasswordResetRoutes', ]; @@ -166,12 +167,14 @@ class PasswordReset implements LoggerAwareInterface { . ' is not allowed to reset passwords' ); } + $username = $username ?? ''; + $email = $email ?? ''; + $resetRoutes = $this->config->get( 'PasswordResetRoutes' ) + [ 'username' => false, 'email' => false ]; if ( $resetRoutes['username'] && $username ) { $method = 'username'; - $users = [ User::newFromName( $username ) ]; - $email = null; + $users = [ $this->lookupUser( $username ) ]; } elseif ( $resetRoutes['email'] && $email ) { if ( !Sanitizer::validateEmail( $email ) ) { return StatusValue::newFatal( 'passwordreset-invalidemail' ); @@ -188,12 +191,33 @@ class PasswordReset implements LoggerAwareInterface { $error = []; $data = [ 'Username' => $username, - 'Email' => $email, + // Email gets set to null for backward compatibility + 'Email' => $method === 'email' ? $email : null, ]; if ( !Hooks::run( 'SpecialPasswordResetOnSubmit', [ &$users, $data, &$error ] ) ) { return StatusValue::newFatal( Message::newFromSpecifier( $error ) ); } + $firstUser = $users[0] ?? null; + $requireEmail = $this->config->get( 'AllowRequiringEmailForResets' ) + && $method === 'username' + && $firstUser + && $firstUser->getBoolOption( 'requireemail' ); + if ( $requireEmail ) { + if ( $email === '' ) { + return StatusValue::newFatal( 'passwordreset-username-email-required' ); + } + + if ( !Sanitizer::validateEmail( $email ) ) { + return StatusValue::newFatal( 'passwordreset-invalidemail' ); + } + } + + // Check against the rate limiter + if ( $performingUser->pingLimiter( 'mailpassword' ) ) { + return StatusValue::newFatal( 'actionthrottledtext' ); + } + if ( !$users ) { if ( $method === 'email' ) { // Don't reveal whether or not an email address is in use @@ -203,18 +227,11 @@ class PasswordReset implements LoggerAwareInterface { } } - $firstUser = $users[0]; - if ( !$firstUser instanceof User || !$firstUser->getId() ) { // Don't parse username as wikitext (T67501) 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 @@ -222,6 +239,11 @@ class PasswordReset implements LoggerAwareInterface { wfEscapeWikiText( $firstUser->getName() ) ) ); } + if ( $requireEmail && $firstUser->getEmail() !== $email ) { + // Pretend everything's fine to avoid disclosure + return StatusValue::newGood(); + } + // We need to have a valid IP address for the hook, but per T20347, we should // send the user's name if they're logged in. $ip = $performingUser->getRequest()->getIP(); @@ -324,4 +346,15 @@ class PasswordReset implements LoggerAwareInterface { } return $users; } + + /** + * User object creation helper for testability + * @codeCoverageIgnore + * + * @param string $username + * @return User|false + */ + protected function lookupUser( $username ) { + return User::newFromName( $username ); + } }