X-Git-Url: https://git.heureux-cyclage.org/?p=lhc%2Fweb%2Fwiklou.git;a=blobdiff_plain;f=includes%2Fuser%2FPasswordReset.php;h=be7ea915e47ab2de1a2c47c2a20204ab2906652b;hp=38707dec5b2c588f58d72008fa3ddf8d71c629d4;hb=29bee071b23832173a55364453d3132a2fe26101;hpb=e80ff96c373454b1cf6fd65a6af8a9ae282f0f55 diff --git a/includes/user/PasswordReset.php b/includes/user/PasswordReset.php index 38707dec5b..be7ea915e4 100644 --- a/includes/user/PasswordReset.php +++ b/includes/user/PasswordReset.php @@ -22,10 +22,14 @@ use MediaWiki\Auth\AuthManager; use MediaWiki\Auth\TemporaryPasswordAuthenticationRequest; +use MediaWiki\Config\ServiceOptions; +use MediaWiki\Logger\LoggerFactory; +use MediaWiki\MediaWikiServices; use MediaWiki\Permissions\PermissionManager; use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerAwareTrait; use Psr\Log\LoggerInterface; -use MediaWiki\Logger\LoggerFactory; +use Wikimedia\Rdbms\ILoadBalancer; /** * Helper class for the password reset functionality shared by the web UI and the API. @@ -35,17 +39,19 @@ use MediaWiki\Logger\LoggerFactory; * functionality) to be enabled. */ class PasswordReset implements LoggerAwareInterface { - /** @var Config */ + use LoggerAwareTrait; + + /** @var ServiceOptions|Config */ protected $config; /** @var AuthManager */ protected $authManager; /** @var PermissionManager */ - private $permissionManager; + protected $permissionManager; - /** @var LoggerInterface */ - protected $logger; + /** @var ILoadBalancer */ + protected $loadBalancer; /** * In-process cache for isAllowed lookups, by username. @@ -54,26 +60,45 @@ class PasswordReset implements LoggerAwareInterface { */ private $permissionCache; + public static $constructorOptions = [ + 'AllowRequiringEmailForResets', + 'EnableEmail', + 'PasswordResetRoutes', + ]; + + /** + * This class is managed by MediaWikiServices, don't instantiate directly. + * + * @param ServiceOptions|Config $config + * @param AuthManager $authManager + * @param PermissionManager $permissionManager + * @param ILoadBalancer|null $loadBalancer + * @param LoggerInterface|null $logger + */ public function __construct( - Config $config, + $config, AuthManager $authManager, - PermissionManager $permissionManager + PermissionManager $permissionManager, + ILoadBalancer $loadBalancer = null, + LoggerInterface $logger = null ) { $this->config = $config; $this->authManager = $authManager; $this->permissionManager = $permissionManager; - $this->permissionCache = new MapCacheLRU( 1 ); - $this->logger = LoggerFactory::getInstance( 'authentication' ); - } - /** - * Set the logger instance to use. - * - * @param LoggerInterface $logger - * @since 1.29 - */ - public function setLogger( LoggerInterface $logger ) { + if ( !$loadBalancer ) { + wfDeprecated( 'Not passing LoadBalancer to ' . __METHOD__, '1.34' ); + $loadBalancer = MediaWikiServices::getInstance()->getDBLoadBalancer(); + } + $this->loadBalancer = $loadBalancer; + + if ( !$logger ) { + wfDeprecated( 'Not passing LoggerInterface to ' . __METHOD__, '1.34' ); + $logger = LoggerFactory::getInstance( 'authentication' ); + } $this->logger = $logger; + + $this->permissionCache = new MapCacheLRU( 1 ); } /** @@ -142,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' ); @@ -164,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 @@ -179,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 @@ -198,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(); @@ -280,7 +326,7 @@ class PasswordReset implements LoggerAwareInterface { */ protected function getUsersByEmail( $email ) { $userQuery = User::getQueryInfo(); - $res = wfGetDB( DB_REPLICA )->select( + $res = $this->loadBalancer->getConnectionRef( DB_REPLICA )->select( $userQuery['tables'], $userQuery['fields'], [ 'user_email' => $email ], @@ -300,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 ); + } }