31use Psr\Log\LoggerAwareInterface;
32use Psr\Log\LoggerAwareTrait;
33use Psr\Log\LoggerInterface;
72 'AllowRequiringEmailForResets',
74 'PasswordResetRoutes',
92 LoggerInterface $logger =
null,
96 $this->authManager = $authManager;
97 $this->permissionManager = $permissionManager;
101 ' was deprecated in MediaWiki 1.34',
'1.34' );
102 $loadBalancer = MediaWikiServices::getInstance()->getDBLoadBalancer();
108 ' was deprecated in MediaWiki 1.34',
'1.34' );
109 $logger = LoggerFactory::getInstance(
'authentication' );
111 $this->logger = $logger;
114 $hookContainer = MediaWikiServices::getInstance()->getHookContainer();
129 $status = $this->permissionCache->get( $user->
getName() );
131 $resetRoutes = $this->config->get(
'PasswordResetRoutes' );
132 $status = StatusValue::newGood();
134 if ( !is_array( $resetRoutes ) || !in_array(
true, $resetRoutes,
true ) ) {
136 $status = StatusValue::newFatal(
'passwordreset-disabled' );
138 ( $providerStatus = $this->authManager->allowsAuthenticationDataChange(
140 && !$providerStatus->isGood()
143 $status = StatusValue::newFatal(
'resetpass_forbidden-reason',
144 $providerStatus->getMessage() );
145 } elseif ( !$this->config->get(
'EnableEmail' ) ) {
147 $status = StatusValue::newFatal(
'passwordreset-emaildisabled' );
148 } elseif ( !$this->permissionManager->userHasRight( $user,
'editmyprivateinfo' ) ) {
150 $status = StatusValue::newFatal(
'badaccess' );
155 $status = StatusValue::newFatal(
'blocked-mailpassword' );
158 $this->permissionCache->set( $user->
getName(), $status );
180 User $performingUser, $username =
null, $email =
null
182 if ( !$this->
isAllowed( $performingUser )->isGood() ) {
183 throw new LogicException(
'User ' . $performingUser->
getName()
184 .
' is not allowed to reset passwords' );
189 if ( $performingUser->
pingLimiter(
'mailpassword' ) ) {
190 return StatusValue::newGood();
197 return StatusValue::newFatal(
'badipaddress' );
200 $username = $username ??
'';
201 $email = $email ??
'';
203 $resetRoutes = $this->config->get(
'PasswordResetRoutes' )
204 + [
'username' =>
false,
'email' => false ];
205 if ( $resetRoutes[
'username'] && $username ) {
206 $method =
'username';
208 } elseif ( $resetRoutes[
'email'] && $email ) {
209 if ( !Sanitizer::validateEmail( $email ) ) {
211 return StatusValue::newGood();
218 if ( $this->config->get(
'AllowRequiringEmailForResets' ) ) {
219 foreach ( $users as $index => $user ) {
220 if ( $user->getBoolOption(
'requireemail' ) ) {
221 unset( $users[$index] );
227 return StatusValue::newFatal(
'passwordreset-nodata' );
232 return StatusValue::newFatal(
'noname' );
238 'Username' => $username,
240 'Email' => $method ===
'email' ? $email :
null,
246 $users = array_values( $users );
248 if ( !$this->hookRunner->onSpecialPasswordResetOnSubmit( $users, $data, $error ) ) {
254 $firstUser = reset( $users );
256 $requireEmail = $this->config->get(
'AllowRequiringEmailForResets' )
257 && $method ===
'username'
259 && $firstUser->getBoolOption(
'requireemail' );
260 if ( $requireEmail && ( $email ===
'' || !Sanitizer::validateEmail( $email ) ) ) {
262 return StatusValue::newGood();
266 if ( $method ===
'email' ) {
268 return StatusValue::newGood();
270 return StatusValue::newFatal(
'noname' );
278 if ( !$firstUser instanceof
User || !$firstUser->
getId() || !$firstUser->getEmail() ) {
279 return StatusValue::newGood();
283 if ( $requireEmail && $firstUser->getEmail() !== $email ) {
284 return StatusValue::newGood();
287 $this->hookRunner->onUser__mailPasswordInternal( $performingUser, $ip, $firstUser );
289 $result = StatusValue::newGood();
291 foreach ( $users as $user ) {
292 $req = TemporaryPasswordAuthenticationRequest::newRandom();
293 $req->username = $user->getName();
294 $req->mailpassword =
true;
295 $req->caller = $performingUser->
getName();
297 $status = $this->authManager->allowsAuthenticationDataChange( $req,
true );
301 if ( $status->isGood() && $status->getValue() ===
'throttled-mailpassword' ) {
302 return StatusValue::newGood();
305 if ( $status->isGood() && $status->getValue() !==
'ignored' ) {
307 } elseif ( $result->isGood() ) {
310 if ( $status->getValue() ===
'ignored' ) {
311 $status = StatusValue::newFatal(
'passwordreset-ignored' );
313 $result->merge( $status );
318 'requestingIp' => $ip,
319 'requestingUser' => $performingUser->
getName(),
320 'targetUsername' => $username,
321 'targetEmail' => $email,
324 if ( !$result->isGood() ) {
326 "{requestingUser} attempted password reset of {actualUser} but failed",
327 $logContext + [
'errors' => $result->getErrors() ]
332 DeferredUpdates::addUpdate(
334 DeferredUpdates::POSTSEND
337 return StatusValue::newGood();
352 return $block->appliesToPasswordReset();
362 $res = $this->loadBalancer->getConnectionRef(
DB_REPLICA )->select(
363 $userQuery[
'tables'],
364 $userQuery[
'fields'],
365 [
'user_email' => $email ],
373 throw new MWException(
'Unknown database error in ' . __METHOD__ );
377 foreach (
$res as $row ) {
wfDeprecatedMsg( $msg, $version=false, $component=false, $callerOffset=2)
Log a deprecation warning with arbitrary message text.
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( $config, AuthManager $authManager, PermissionManager $permissionManager, ILoadBalancer $loadBalancer=null, LoggerInterface $logger=null, HookContainer $hookContainer=null)
This class is managed by MediaWikiServices, don't instantiate directly.
ServiceOptions Config $config
const CONSTRUCTOR_OPTIONS
PermissionManager $permissionManager
HookContainer $hookContainer
MapCacheLRU $permissionCache
In-process cache for isAllowed lookups, by username.
lookupUser( $username)
User object creation helper for testability.
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,...
getRequest()
Get the WebRequest object to use with this object.
getName()
Get the user name, or the IP of an anonymous user.
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new user object.
getId()
Get the user's ID.
getGlobalBlock( $ip='')
Check if user is blocked on all wikis.
static getCanonicalName( $name, $validate='valid')
Given unvalidated user input, return a canonical username, or false if the username is invalid.
static newFromRow( $row, $data=null)
Create a new user object from a user row.
pingLimiter( $action='edit', $incrBy=1)
Primitive rate limits: enforce maximum actions per time period to put a brake on flooding.
getBlock( $fromReplica=true, $disableIpBlockExemptChecking=false)
Get the block affecting the user, or null if the user is not blocked.
Interface for configuration instances.