31use Psr\Log\LoggerAwareInterface;
32use Psr\Log\LoggerAwareTrait;
33use Psr\Log\LoggerInterface;
78 'AllowRequiringEmailForResets',
80 'PasswordResetRoutes',
97 LoggerInterface $logger,
107 $this->config = $config;
108 $this->logger = $logger;
110 $this->authManager = $authManager;
111 $this->hookRunner =
new HookRunner( $hookContainer );
112 $this->loadBalancer = $loadBalancer;
113 $this->userFactory = $userFactory;
114 $this->userNameUtils = $userNameUtils;
127 $status = $this->permissionCache->get( $user->
getName() );
129 $resetRoutes = $this->config->get(
'PasswordResetRoutes' );
130 $status = StatusValue::newGood();
132 if ( !is_array( $resetRoutes ) || !in_array(
true, $resetRoutes,
true ) ) {
134 $status = StatusValue::newFatal(
'passwordreset-disabled' );
136 ( $providerStatus = $this->authManager->allowsAuthenticationDataChange(
138 && !$providerStatus->isGood()
141 $status = StatusValue::newFatal(
'resetpass_forbidden-reason',
142 $providerStatus->getMessage() );
143 } elseif ( !$this->config->get(
'EnableEmail' ) ) {
145 $status = StatusValue::newFatal(
'passwordreset-emaildisabled' );
146 } elseif ( !$user->
isAllowed(
'editmyprivateinfo' ) ) {
148 $status = StatusValue::newFatal(
'badaccess' );
153 $status = StatusValue::newFatal(
'blocked-mailpassword' );
156 $this->permissionCache->set( $user->
getName(), $status );
178 User $performingUser,
182 if ( !$this->
isAllowed( $performingUser )->isGood() ) {
183 throw new LogicException(
184 'User ' . $performingUser->
getName() .
' is not allowed to reset passwords'
190 if ( $performingUser->
pingLimiter(
'mailpassword' ) ) {
191 return StatusValue::newGood();
198 return StatusValue::newFatal(
'badipaddress' );
201 $username = $username ??
'';
202 $email = $email ??
'';
204 $resetRoutes = $this->config->get(
'PasswordResetRoutes' )
205 + [
'username' =>
false,
'email' => false ];
206 if ( $resetRoutes[
'username'] && $username ) {
207 $method =
'username';
208 $users = [ $this->userFactory->newFromName( $username ) ];
209 } elseif ( $resetRoutes[
'email'] && $email ) {
210 if ( !Sanitizer::validateEmail( $email ) ) {
212 return StatusValue::newGood();
219 if ( $this->config->get(
'AllowRequiringEmailForResets' ) ) {
221 foreach ( $users as $index => $user ) {
222 if ( $optionsLookup->getBoolOption( $user,
'requireemail' ) ) {
223 unset( $users[$index] );
229 return StatusValue::newFatal(
'passwordreset-nodata' );
233 if ( $username && !$this->userNameUtils->getCanonical( $username ) ) {
234 return StatusValue::newFatal(
'noname' );
240 'Username' => $username,
242 'Email' => $method ===
'email' ? $email :
null,
248 $users = array_values( $users );
250 if ( !$this->hookRunner->onSpecialPasswordResetOnSubmit( $users, $data, $error ) ) {
256 $firstUser = reset( $users );
258 $requireEmail = $this->config->get(
'AllowRequiringEmailForResets' )
259 && $method ===
'username'
261 && $this->userOptionsLookup->getBoolOption( $firstUser,
'requireemail' );
262 if ( $requireEmail && ( $email ===
'' || !Sanitizer::validateEmail( $email ) ) ) {
264 return StatusValue::newGood();
268 if ( $method ===
'email' ) {
270 return StatusValue::newGood();
272 return StatusValue::newFatal(
'noname' );
280 if ( !$firstUser instanceof
User || !$firstUser->
getId() || !$firstUser->getEmail() ) {
281 return StatusValue::newGood();
285 if ( $requireEmail && $firstUser->getEmail() !== $email ) {
286 return StatusValue::newGood();
289 $this->hookRunner->onUser__mailPasswordInternal( $performingUser, $ip, $firstUser );
291 $result = StatusValue::newGood();
293 foreach ( $users as $user ) {
294 $req = TemporaryPasswordAuthenticationRequest::newRandom();
295 $req->username = $user->getName();
296 $req->mailpassword =
true;
297 $req->caller = $performingUser->
getName();
299 $status = $this->authManager->allowsAuthenticationDataChange( $req,
true );
303 if ( $status->isGood() && $status->getValue() ===
'throttled-mailpassword' ) {
304 return StatusValue::newGood();
307 if ( $status->isGood() && $status->getValue() !==
'ignored' ) {
309 } elseif ( $result->isGood() ) {
312 if ( $status->getValue() ===
'ignored' ) {
313 $status = StatusValue::newFatal(
'passwordreset-ignored' );
315 $result->merge( $status );
320 'requestingIp' => $ip,
321 'requestingUser' => $performingUser->
getName(),
322 'targetUsername' => $username,
323 'targetEmail' => $email,
326 if ( !$result->isGood() ) {
328 "{requestingUser} attempted password reset of {actualUser} but failed",
329 $logContext + [
'errors' => $result->getErrors() ]
334 DeferredUpdates::addUpdate(
336 DeferredUpdates::POSTSEND
339 return StatusValue::newGood();
354 return $block->appliesToPasswordReset();
366 $res = $this->loadBalancer->getConnectionRef(
DB_REPLICA )->select(
367 $userQuery[
'tables'],
368 $userQuery[
'fields'],
369 [
'user_email' => $email ],
377 throw new MWException(
'Unknown database error in ' . __METHOD__ );
381 foreach (
$res as $row ) {
382 $users[] = $this->userFactory->newFromRow( $row );
UserOptionsLookup $userOptionsLookup
Handles a simple LRU key/value map with a maximum number of entries.
static newFromSpecifier( $value)
Transform a MessageSpecifier or a primitive value used interchangeably with specifiers (a message key...
Helper class for the password reset functionality shared by the web UI and the API.
isBlocked(User $user)
Check whether the user is blocked.
isAllowed(User $user)
Check if a given user has permission to use this functionality.
execute(User $performingUser, $username=null, $email=null)
Do a password reset.
ILoadBalancer $loadBalancer
__construct(ServiceOptions $config, LoggerInterface $logger, AuthManager $authManager, HookContainer $hookContainer, ILoadBalancer $loadBalancer, UserFactory $userFactory, UserNameUtils $userNameUtils, UserOptionsLookup $userOptionsLookup)
This class is managed by MediaWikiServices, don't instantiate directly.
UserOptionsLookup $userOptionsLookup
const CONSTRUCTOR_OPTIONS
UserNameUtils $userNameUtils
MapCacheLRU $permissionCache
In-process cache for isAllowed lookups, by username.
Sends emails to all accounts associated with that email to reset the password.
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
getBlock( $freshness=self::READ_NORMAL, $disableIpBlockExemptChecking=false)
Get the block affecting the user, or null if the user is not blocked.
getRequest()
Get the WebRequest object to use with this object.
getName()
Get the user name, or the IP of an anonymous user.
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new user object.
getGlobalBlock( $ip='')
Check if user is blocked on all wikis.
getId( $wikiId=self::LOCAL)
Get the user's ID.
pingLimiter( $action='edit', $incrBy=1)
Primitive rate limits: enforce maximum actions per time period to put a brake on flooding.
isAllowed(string $permission)
Checks whether this authority has the given permission in general.