22use Psr\Log\LoggerAwareInterface;
23use Psr\Log\LoggerAwareTrait;
24use Psr\Log\LoggerInterface;
59 LoggerInterface $logger,
67 $config->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
69 $this->logger = $logger;
70 $this->hookRunner =
new HookRunner( $hookContainer );
82 return $this->permissionCache->getWithSetCallback(
84 function () use ( $user ) {
85 return $this->computeIsAllowed( $user );
95 $resetRoutes = $this->config->get(
MainConfigNames::PasswordResetRoutes );
96 if ( !is_array( $resetRoutes ) || !in_array(
true, $resetRoutes,
true ) ) {
98 return StatusValue::newFatal(
'passwordreset-disabled' );
101 $providerStatus = $this->authManager->allowsAuthenticationDataChange(
103 if ( !$providerStatus->isGood() ) {
105 return StatusValue::newFatal(
'resetpass_forbidden-reason',
106 $providerStatus->getMessage() );
110 return StatusValue::newFatal(
'passwordreset-emaildisabled' );
112 return StatusValue::newGood();
115 private function computeIsAllowed( User $user ):
StatusValue {
116 $enabledStatus = $this->isEnabled();
117 if ( !$enabledStatus->isGood() ) {
118 return $enabledStatus;
120 if ( !$user->isAllowed(
'editmyprivateinfo' ) ) {
124 if ( $this->isBlocked( $user ) ) {
149 User $performingUser,
153 if ( !$this->isAllowed( $performingUser )->isGood() ) {
154 throw new LogicException(
155 'User ' . $performingUser->
getName() .
' is not allowed to reset passwords'
161 if ( $performingUser->
pingLimiter(
'mailpassword' ) ) {
162 return StatusValue::newGood();
169 return StatusValue::newFatal(
'badipaddress' );
172 $resetRoutes = $this->config->get( MainConfigNames::PasswordResetRoutes )
173 + [
'username' =>
false,
'email' => false ];
174 if ( !$resetRoutes[
'username'] || $username ===
'' ) {
177 if ( !$resetRoutes[
'email'] || $email ===
'' ) {
181 if ( $username !==
null && !$this->userNameUtils->getCanonical( $username ) ) {
182 return StatusValue::newFatal(
'noname' );
184 if ( $email !==
null && !Sanitizer::validateEmail( $email ) ) {
185 return StatusValue::newFatal(
'passwordreset-invalidemail' );
192 if ( $username !==
null ) {
193 $user = $this->userFactory->newFromName( $username );
195 if ( $user && $user->isRegistered() && $user->getEmail() && (
196 !$this->userOptionsLookup->getBoolOption( $user,
'requireemail' ) ||
197 $user->getEmail() === $email
204 } elseif ( $email !==
null ) {
205 foreach ( $this->getUsersByEmail( $email ) as $userIdent ) {
207 if ( $this->userOptionsLookup->getBoolOption( $userIdent,
'requireemail' ) ) {
210 $users[] = $this->userFactory->newFromUserIdentity( $userIdent );
215 return StatusValue::newFatal(
'passwordreset-nodata' );
220 'Username' => $username,
225 if ( !$this->hookRunner->onSpecialPasswordResetOnSubmit( $users, $data, $error ) ) {
226 return StatusValue::newFatal( Message::newFromSpecifier( $error ) );
231 return StatusValue::newGood();
236 $firstUser = reset( $users );
238 $this->hookRunner->onUser__mailPasswordInternal( $performingUser, $ip, $firstUser );
240 $result = StatusValue::newGood();
242 foreach ( $users as $user ) {
243 $req = TemporaryPasswordAuthenticationRequest::newRandom();
244 $req->username = $user->getName();
245 $req->mailpassword =
true;
246 $req->caller = $performingUser->
getName();
248 $status = $this->authManager->allowsAuthenticationDataChange( $req,
true );
252 if ( $status->isGood() && $status->getValue() ===
'throttled-mailpassword' ) {
253 return StatusValue::newGood();
256 if ( $status->isGood() && $status->getValue() !==
'ignored' ) {
258 } elseif ( $result->isGood() ) {
261 if ( $status->getValue() ===
'ignored' ) {
262 $status = StatusValue::newFatal(
'passwordreset-ignored' );
264 $result->merge( $status );
269 'requestingIp' => $ip,
270 'requestingUser' => $performingUser->
getName(),
271 'targetUsername' => $username,
272 'targetEmail' => $email,
273 ] + $performingUser->
getRequest()->getSecurityLogContext();
275 if ( !$result->isGood() ) {
277 "{requestingUser} attempted password reset of {targetUsername} but failed",
278 $logContext + [
'errors' => $result->getErrors() ]
283 DeferredUpdates::addUpdate(
285 DeferredUpdates::POSTSEND
288 return StatusValue::newGood();
298 private function isBlocked(
User $user ) {
300 return $block && $block->appliesToPasswordReset();
311 return $this->userIdentityLookup->newSelectQueryBuilder()
312 ->join(
'user',
null, [
"actor_user=user_id" ] )
313 ->where( [
'user_email' => $email ] )
314 ->caller( __METHOD__ )
315 ->fetchUserIdentities();
321class_alias( PasswordReset::class,
'PasswordReset' );
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.