39use Psr\Log\LoggerAwareInterface;
40use Psr\Log\LoggerAwareTrait;
41use Psr\Log\LoggerInterface;
90 LoggerInterface $logger,
100 $this->config = $config;
101 $this->logger = $logger;
103 $this->authManager = $authManager;
104 $this->hookRunner =
new HookRunner( $hookContainer );
105 $this->userIdentityLookup = $userIdentityLookup;
106 $this->userFactory = $userFactory;
107 $this->userNameUtils = $userNameUtils;
108 $this->userOptionsLookup = $userOptionsLookup;
120 return $this->permissionCache->getWithSetCallback(
122 function () use ( $user ) {
123 return $this->computeIsAllowed( $user );
133 $resetRoutes = $this->config->get(
MainConfigNames::PasswordResetRoutes );
134 if ( !is_array( $resetRoutes ) || !in_array(
true, $resetRoutes,
true ) ) {
136 return StatusValue::newFatal(
'passwordreset-disabled' );
139 $providerStatus = $this->authManager->allowsAuthenticationDataChange(
141 if ( !$providerStatus->isGood() ) {
143 return StatusValue::newFatal(
'resetpass_forbidden-reason',
144 $providerStatus->getMessage() );
148 return StatusValue::newFatal(
'passwordreset-emaildisabled' );
150 return StatusValue::newGood();
157 private function computeIsAllowed( User $user ):
StatusValue {
158 $enabledStatus = $this->isEnabled();
159 if ( !$enabledStatus->isGood() ) {
160 return $enabledStatus;
162 if ( !$user->isAllowed(
'editmyprivateinfo' ) ) {
166 if ( $this->isBlocked( $user ) ) {
191 User $performingUser,
195 if ( !$this->isAllowed( $performingUser )->isGood() ) {
196 throw new LogicException(
197 'User ' . $performingUser->
getName() .
' is not allowed to reset passwords'
203 if ( $performingUser->
pingLimiter(
'mailpassword' ) ) {
204 return StatusValue::newGood();
211 return StatusValue::newFatal(
'badipaddress' );
214 $resetRoutes = $this->config->get( MainConfigNames::PasswordResetRoutes )
215 + [
'username' =>
false,
'email' => false ];
216 if ( !$resetRoutes[
'username'] || $username ===
'' ) {
219 if ( !$resetRoutes[
'email'] || $email ===
'' ) {
223 if ( $username !==
null && !$this->userNameUtils->getCanonical( $username ) ) {
224 return StatusValue::newFatal(
'noname' );
226 if ( $email !==
null && !Sanitizer::validateEmail( $email ) ) {
227 return StatusValue::newFatal(
'passwordreset-invalidemail' );
234 if ( $username !==
null ) {
235 $user = $this->userFactory->newFromName( $username );
237 if ( $user && $user->isRegistered() && $user->getEmail() && (
238 !$this->userOptionsLookup->getBoolOption( $user,
'requireemail' ) ||
239 $user->getEmail() === $email
246 } elseif ( $email !==
null ) {
247 foreach ( $this->getUsersByEmail( $email ) as $userIdent ) {
249 if ( $this->userOptionsLookup->getBoolOption( $userIdent,
'requireemail' ) ) {
252 $users[] = $this->userFactory->newFromUserIdentity( $userIdent );
257 return StatusValue::newFatal(
'passwordreset-nodata' );
262 'Username' => $username,
267 if ( !$this->hookRunner->onSpecialPasswordResetOnSubmit( $users, $data, $error ) ) {
268 return StatusValue::newFatal( Message::newFromSpecifier( $error ) );
273 return StatusValue::newGood();
278 $firstUser = reset( $users );
280 $this->hookRunner->onUser__mailPasswordInternal( $performingUser, $ip, $firstUser );
282 $result = StatusValue::newGood();
284 foreach ( $users as $user ) {
285 $req = TemporaryPasswordAuthenticationRequest::newRandom();
286 $req->username = $user->getName();
287 $req->mailpassword =
true;
288 $req->caller = $performingUser->
getName();
290 $status = $this->authManager->allowsAuthenticationDataChange( $req,
true );
294 if ( $status->isGood() && $status->getValue() ===
'throttled-mailpassword' ) {
295 return StatusValue::newGood();
298 if ( $status->isGood() && $status->getValue() !==
'ignored' ) {
300 } elseif ( $result->isGood() ) {
303 if ( $status->getValue() ===
'ignored' ) {
304 $status = StatusValue::newFatal(
'passwordreset-ignored' );
306 $result->merge( $status );
311 'requestingIp' => $ip,
312 'requestingUser' => $performingUser->
getName(),
313 'targetUsername' => $username,
314 'targetEmail' => $email,
317 if ( !$result->isGood() ) {
319 "{requestingUser} attempted password reset of {targetUsername} but failed",
320 $logContext + [
'errors' => $result->getErrors() ]
325 DeferredUpdates::addUpdate(
327 DeferredUpdates::POSTSEND
330 return StatusValue::newGood();
340 private function isBlocked(
User $user ) {
342 return $block && $block->appliesToPasswordReset();
353 return $this->userIdentityLookup->newSelectQueryBuilder()
354 ->join(
'user',
null, [
"actor_user=user_id" ] )
355 ->where( [
'user_email' => $email ] )
356 ->caller( __METHOD__ )
357 ->fetchUserIdentities();
363class_alias( PasswordReset::class,
'PasswordReset' );
Store key-value entries in a size-limited in-memory LRU cache.
A class containing constants representing the names of configuration variables.
const EnableEmail
Name constant for the EnableEmail setting, for use with Config::get()
const PasswordResetRoutes
Name constant for the PasswordResetRoutes setting, for use with Config::get()
Generic operation result class Has warning/error list, boolean status and arbitrary value.
static newFatal( $message,... $parameters)
Factory function for fatal errors.
static newGood( $value=null)
Factory function for good results.