22use Psr\Log\LoggerAwareInterface;
23use Psr\Log\LoggerAwareTrait;
24use Psr\Log\LoggerInterface;
74 LoggerInterface $logger,
84 $this->config = $config;
85 $this->logger = $logger;
87 $this->authManager = $authManager;
88 $this->hookRunner =
new HookRunner( $hookContainer );
89 $this->userIdentityLookup = $userIdentityLookup;
90 $this->userFactory = $userFactory;
91 $this->userNameUtils = $userNameUtils;
92 $this->userOptionsLookup = $userOptionsLookup;
104 return $this->permissionCache->getWithSetCallback(
106 function () use ( $user ) {
107 return $this->computeIsAllowed( $user );
117 $resetRoutes = $this->config->get(
MainConfigNames::PasswordResetRoutes );
118 if ( !is_array( $resetRoutes ) || !in_array(
true, $resetRoutes,
true ) ) {
120 return StatusValue::newFatal(
'passwordreset-disabled' );
123 $providerStatus = $this->authManager->allowsAuthenticationDataChange(
125 if ( !$providerStatus->isGood() ) {
127 return StatusValue::newFatal(
'resetpass_forbidden-reason',
128 $providerStatus->getMessage() );
132 return StatusValue::newFatal(
'passwordreset-emaildisabled' );
134 return StatusValue::newGood();
137 private function computeIsAllowed( User $user ):
StatusValue {
138 $enabledStatus = $this->isEnabled();
139 if ( !$enabledStatus->isGood() ) {
140 return $enabledStatus;
142 if ( !$user->isAllowed(
'editmyprivateinfo' ) ) {
146 if ( $this->isBlocked( $user ) ) {
171 User $performingUser,
175 if ( !$this->isAllowed( $performingUser )->isGood() ) {
176 throw new LogicException(
177 'User ' . $performingUser->
getName() .
' is not allowed to reset passwords'
183 if ( $performingUser->
pingLimiter(
'mailpassword' ) ) {
184 return StatusValue::newGood();
191 return StatusValue::newFatal(
'badipaddress' );
194 $resetRoutes = $this->config->get( MainConfigNames::PasswordResetRoutes )
195 + [
'username' =>
false,
'email' => false ];
196 if ( !$resetRoutes[
'username'] || $username ===
'' ) {
199 if ( !$resetRoutes[
'email'] || $email ===
'' ) {
203 if ( $username !==
null && !$this->userNameUtils->getCanonical( $username ) ) {
204 return StatusValue::newFatal(
'noname' );
206 if ( $email !==
null && !Sanitizer::validateEmail( $email ) ) {
207 return StatusValue::newFatal(
'passwordreset-invalidemail' );
214 if ( $username !==
null ) {
215 $user = $this->userFactory->newFromName( $username );
217 if ( $user && $user->isRegistered() && $user->getEmail() && (
218 !$this->userOptionsLookup->getBoolOption( $user,
'requireemail' ) ||
219 $user->getEmail() === $email
226 } elseif ( $email !==
null ) {
227 foreach ( $this->getUsersByEmail( $email ) as $userIdent ) {
229 if ( $this->userOptionsLookup->getBoolOption( $userIdent,
'requireemail' ) ) {
232 $users[] = $this->userFactory->newFromUserIdentity( $userIdent );
237 return StatusValue::newFatal(
'passwordreset-nodata' );
242 'Username' => $username,
247 if ( !$this->hookRunner->onSpecialPasswordResetOnSubmit( $users, $data, $error ) ) {
248 return StatusValue::newFatal( Message::newFromSpecifier( $error ) );
253 return StatusValue::newGood();
258 $firstUser = reset( $users );
260 $this->hookRunner->onUser__mailPasswordInternal( $performingUser, $ip, $firstUser );
262 $result = StatusValue::newGood();
264 foreach ( $users as $user ) {
265 $req = TemporaryPasswordAuthenticationRequest::newRandom();
266 $req->username = $user->getName();
267 $req->mailpassword =
true;
268 $req->caller = $performingUser->
getName();
270 $status = $this->authManager->allowsAuthenticationDataChange( $req,
true );
274 if ( $status->isGood() && $status->getValue() ===
'throttled-mailpassword' ) {
275 return StatusValue::newGood();
278 if ( $status->isGood() && $status->getValue() !==
'ignored' ) {
280 } elseif ( $result->isGood() ) {
283 if ( $status->getValue() ===
'ignored' ) {
284 $status = StatusValue::newFatal(
'passwordreset-ignored' );
286 $result->merge( $status );
291 'requestingIp' => $ip,
292 'requestingUser' => $performingUser->
getName(),
293 'targetUsername' => $username,
294 'targetEmail' => $email,
297 if ( !$result->isGood() ) {
299 "{requestingUser} attempted password reset of {targetUsername} but failed",
300 $logContext + [
'errors' => $result->getErrors() ]
305 DeferredUpdates::addUpdate(
307 DeferredUpdates::POSTSEND
310 return StatusValue::newGood();
320 private function isBlocked(
User $user ) {
322 return $block && $block->appliesToPasswordReset();
333 return $this->userIdentityLookup->newSelectQueryBuilder()
334 ->join(
'user',
null, [
"actor_user=user_id" ] )
335 ->where( [
'user_email' => $email ] )
336 ->caller( __METHOD__ )
337 ->fetchUserIdentities();
343class_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.