37use Psr\Log\LoggerAwareInterface;
38use Psr\Log\LoggerAwareTrait;
39use Psr\Log\LoggerInterface;
88 LoggerInterface $logger,
98 $this->config = $config;
99 $this->logger = $logger;
101 $this->authManager = $authManager;
102 $this->hookRunner =
new HookRunner( $hookContainer );
103 $this->userIdentityLookup = $userIdentityLookup;
104 $this->userFactory = $userFactory;
105 $this->userNameUtils = $userNameUtils;
106 $this->userOptionsLookup = $userOptionsLookup;
118 return $this->permissionCache->getWithSetCallback(
120 function () use ( $user ) {
121 return $this->computeIsAllowed( $user );
131 $resetRoutes = $this->config->get(
MainConfigNames::PasswordResetRoutes );
132 if ( !is_array( $resetRoutes ) || !in_array(
true, $resetRoutes,
true ) ) {
134 return StatusValue::newFatal(
'passwordreset-disabled' );
137 $providerStatus = $this->authManager->allowsAuthenticationDataChange(
139 if ( !$providerStatus->isGood() ) {
141 return StatusValue::newFatal(
'resetpass_forbidden-reason',
142 $providerStatus->getMessage() );
146 return StatusValue::newFatal(
'passwordreset-emaildisabled' );
148 return StatusValue::newGood();
151 private function computeIsAllowed( User $user ):
StatusValue {
152 $enabledStatus = $this->isEnabled();
153 if ( !$enabledStatus->isGood() ) {
154 return $enabledStatus;
156 if ( !$user->isAllowed(
'editmyprivateinfo' ) ) {
160 if ( $this->isBlocked( $user ) ) {
185 User $performingUser,
189 if ( !$this->isAllowed( $performingUser )->isGood() ) {
190 throw new LogicException(
191 'User ' . $performingUser->
getName() .
' is not allowed to reset passwords'
197 if ( $performingUser->
pingLimiter(
'mailpassword' ) ) {
198 return StatusValue::newGood();
205 return StatusValue::newFatal(
'badipaddress' );
208 $resetRoutes = $this->config->get( MainConfigNames::PasswordResetRoutes )
209 + [
'username' =>
false,
'email' => false ];
210 if ( !$resetRoutes[
'username'] || $username ===
'' ) {
213 if ( !$resetRoutes[
'email'] || $email ===
'' ) {
217 if ( $username !==
null && !$this->userNameUtils->getCanonical( $username ) ) {
218 return StatusValue::newFatal(
'noname' );
220 if ( $email !==
null && !Sanitizer::validateEmail( $email ) ) {
221 return StatusValue::newFatal(
'passwordreset-invalidemail' );
228 if ( $username !==
null ) {
229 $user = $this->userFactory->newFromName( $username );
231 if ( $user && $user->isRegistered() && $user->getEmail() && (
232 !$this->userOptionsLookup->getBoolOption( $user,
'requireemail' ) ||
233 $user->getEmail() === $email
240 } elseif ( $email !==
null ) {
241 foreach ( $this->getUsersByEmail( $email ) as $userIdent ) {
243 if ( $this->userOptionsLookup->getBoolOption( $userIdent,
'requireemail' ) ) {
246 $users[] = $this->userFactory->newFromUserIdentity( $userIdent );
251 return StatusValue::newFatal(
'passwordreset-nodata' );
256 'Username' => $username,
261 if ( !$this->hookRunner->onSpecialPasswordResetOnSubmit( $users, $data, $error ) ) {
262 return StatusValue::newFatal( Message::newFromSpecifier( $error ) );
267 return StatusValue::newGood();
272 $firstUser = reset( $users );
274 $this->hookRunner->onUser__mailPasswordInternal( $performingUser, $ip, $firstUser );
276 $result = StatusValue::newGood();
278 foreach ( $users as $user ) {
279 $req = TemporaryPasswordAuthenticationRequest::newRandom();
280 $req->username = $user->getName();
281 $req->mailpassword =
true;
282 $req->caller = $performingUser->
getName();
284 $status = $this->authManager->allowsAuthenticationDataChange( $req,
true );
288 if ( $status->isGood() && $status->getValue() ===
'throttled-mailpassword' ) {
289 return StatusValue::newGood();
292 if ( $status->isGood() && $status->getValue() !==
'ignored' ) {
294 } elseif ( $result->isGood() ) {
297 if ( $status->getValue() ===
'ignored' ) {
298 $status = StatusValue::newFatal(
'passwordreset-ignored' );
300 $result->merge( $status );
305 'requestingIp' => $ip,
306 'requestingUser' => $performingUser->
getName(),
307 'targetUsername' => $username,
308 'targetEmail' => $email,
311 if ( !$result->isGood() ) {
313 "{requestingUser} attempted password reset of {targetUsername} but failed",
314 $logContext + [
'errors' => $result->getErrors() ]
319 DeferredUpdates::addUpdate(
321 DeferredUpdates::POSTSEND
324 return StatusValue::newGood();
334 private function isBlocked(
User $user ) {
336 return $block && $block->appliesToPasswordReset();
347 return $this->userIdentityLookup->newSelectQueryBuilder()
348 ->join(
'user',
null, [
"actor_user=user_id" ] )
349 ->where( [
'user_email' => $email ] )
350 ->caller( __METHOD__ )
351 ->fetchUserIdentities();
357class_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.