X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2Fspecials%2FSpecialPasswordReset.php;h=9746ef6983126055da72a9ce045ba083b981c095;hb=4e618302d9e7b96c008d3dfa3c0554cff52d5222;hp=44f31a53e6cd51e9c788209f06929eb89992db8d;hpb=fb7b3eebeb8de47eb42e8d6ccf204106a2d6d9e4;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/specials/SpecialPasswordReset.php b/includes/specials/SpecialPasswordReset.php index 44f31a53e6..9746ef6983 100644 --- a/includes/specials/SpecialPasswordReset.php +++ b/includes/specials/SpecialPasswordReset.php @@ -21,21 +21,25 @@ * @ingroup SpecialPage */ +use MediaWiki\Auth\AuthManager; + /** - * Special page for requesting a password reset email + * Special page for requesting a password reset email. + * + * Requires the TemporaryPasswordPrimaryAuthenticationProvider and the + * EmailNotificationSecondaryAuthenticationProvider (or something providing equivalent + * functionality) to be enabled. * * @ingroup SpecialPage */ class SpecialPasswordReset extends FormSpecialPage { - /** - * @var Message - */ - private $email; + /** @var PasswordReset */ + private $passwordReset; /** - * @var User + * @var string[] Temporary storage for the passwords which have been sent out, keyed by username. */ - private $firstUser; + private $passwords = []; /** * @var Status @@ -49,6 +53,7 @@ class SpecialPasswordReset extends FormSpecialPage { public function __construct() { parent::__construct( 'PasswordReset', 'editmyprivateinfo' ); + $this->passwordReset = new PasswordReset( $this->getConfig(), AuthManager::singleton() ); } public function doesWrites() { @@ -56,22 +61,19 @@ class SpecialPasswordReset extends FormSpecialPage { } public function userCanExecute( User $user ) { - return $this->canChangePassword( $user ) === true && parent::userCanExecute( $user ); + return $this->passwordReset->isAllowed( $user )->isGood(); } public function checkExecutePermissions( User $user ) { - $error = $this->canChangePassword( $user ); - if ( is_string( $error ) ) { - throw new ErrorPageError( 'internalerror', $error ); - } elseif ( !$error ) { - throw new ErrorPageError( 'internalerror', 'resetpass_forbidden' ); + $status = Status::wrap( $this->passwordReset->isAllowed( $user ) ); + if ( !$status->isGood() ) { + throw new ErrorPageError( 'internalerror', $status->getMessage() ); } parent::checkExecutePermissions( $user ); } protected function getFormFields() { - global $wgAuth; $resetRoutes = $this->getConfig()->get( 'PasswordResetRoutes' ); $a = []; if ( isset( $resetRoutes['username'] ) && $resetRoutes['username'] ) { @@ -92,15 +94,6 @@ class SpecialPasswordReset extends FormSpecialPage { ]; } - if ( isset( $resetRoutes['domain'] ) && $resetRoutes['domain'] ) { - $domains = $wgAuth->domainList(); - $a['Domain'] = [ - 'type' => 'select', - 'options' => $domains, - 'label-message' => 'passwordreset-domain', - ]; - } - if ( $this->getUser()->isAllowed( 'passwordreset' ) ) { $a['Capture'] = [ 'type' => 'check', @@ -128,9 +121,6 @@ class SpecialPasswordReset extends FormSpecialPage { if ( isset( $resetRoutes['email'] ) && $resetRoutes['email'] ) { $i++; } - if ( isset( $resetRoutes['domain'] ) && $resetRoutes['domain'] ) { - $i++; - } $message = ( $i > 1 ) ? 'passwordreset-text-many' : 'passwordreset-text-one'; @@ -145,180 +135,54 @@ class SpecialPasswordReset extends FormSpecialPage { * @param array $data * @throws MWException * @throws ThrottledError|PermissionsError - * @return bool|array + * @return Status */ public function onSubmit( array $data ) { - global $wgAuth, $wgMinimalPasswordLength; - - if ( isset( $data['Domain'] ) ) { - if ( $wgAuth->validDomain( $data['Domain'] ) ) { - $wgAuth->setDomain( $data['Domain'] ); - } else { - $wgAuth->setDomain( 'invaliddomain' ); - } - } - if ( isset( $data['Capture'] ) && !$this->getUser()->isAllowed( 'passwordreset' ) ) { // The user knows they don't have the passwordreset permission, // but they tried to spoof the form. That's naughty throw new PermissionsError( 'passwordreset' ); } - /** - * @var $firstUser User - * @var $users User[] - */ - - if ( isset( $data['Username'] ) && $data['Username'] !== '' ) { - $method = 'username'; - $users = [ User::newFromName( $data['Username'] ) ]; - } elseif ( isset( $data['Email'] ) - && $data['Email'] !== '' - && Sanitizer::validateEmail( $data['Email'] ) - ) { - $method = 'email'; - $res = wfGetDB( DB_SLAVE )->select( - 'user', - User::selectFields(), - [ 'user_email' => $data['Email'] ], - __METHOD__ - ); - - if ( $res ) { - $users = []; - - foreach ( $res as $row ) { - $users[] = User::newFromRow( $row ); - } - } else { - // Some sort of database error, probably unreachable - throw new MWException( 'Unknown database error in ' . __METHOD__ ); - } - } else { - // The user didn't supply any data - return false; - } - - // Check for hooks (captcha etc), and allow them to modify the users list - $error = []; - if ( !Hooks::run( 'SpecialPasswordResetOnSubmit', [ &$users, $data, &$error ] ) ) { - return [ $error ]; - } - - $this->method = $method; - - if ( count( $users ) == 0 ) { - if ( $method == 'email' ) { - // Don't reveal whether or not an email address is in use - return true; - } else { - return [ 'noname' ]; - } - } - - $firstUser = $users[0]; + $username = isset( $data['Username'] ) ? $data['Username'] : null; + $email = isset( $data['Email'] ) ? $data['Email'] : null; + $capture = !empty( $data['Capture'] ); - if ( !$firstUser instanceof User || !$firstUser->getID() ) { - // Don't parse username as wikitext (bug 65501) - return [ [ 'nosuchuser', wfEscapeWikiText( $data['Username'] ) ] ]; + $this->method = $username ? 'username' : 'email'; + $this->result = Status::wrap( + $this->passwordReset->execute( $this->getUser(), $username, $email, $capture ) ); + if ( $capture && $this->result->isOK() ) { + $this->passwords = $this->result->getValue(); } - // Check against the rate limiter - if ( $this->getUser()->pingLimiter( 'mailpassword' ) ) { + if ( $this->result->hasMessage( 'actionthrottledtext' ) ) { throw new ThrottledError; } - // Check against password throttle - foreach ( $users as $user ) { - if ( $user->isPasswordReminderThrottled() ) { - - # Round the time in hours to 3 d.p., in case someone is specifying - # minutes or seconds. - return [ [ - 'throttled-mailpassword', - round( $this->getConfig()->get( 'PasswordReminderResendTime' ), 3 ) - ] ]; - } - } - - // All the users will have the same email address - if ( $firstUser->getEmail() == '' ) { - // This won't be reachable from the email route, so safe to expose the username - return [ [ 'noemail', wfEscapeWikiText( $firstUser->getName() ) ] ]; - } - - // We need to have a valid IP address for the hook, but per bug 18347, we should - // send the user's name if they're logged in. - $ip = $this->getRequest()->getIP(); - if ( !$ip ) { - return [ 'badipaddress' ]; - } - $caller = $this->getUser(); - Hooks::run( 'User::mailPasswordInternal', [ &$caller, &$ip, &$firstUser ] ); - $username = $caller->getName(); - $msg = IP::isValid( $username ) - ? 'passwordreset-emailtext-ip' - : 'passwordreset-emailtext-user'; - - // Send in the user's language; which should hopefully be the same - $userLanguage = $firstUser->getOption( 'language' ); - - $passwords = []; - foreach ( $users as $user ) { - $password = PasswordFactory::generateRandomPasswordString( $wgMinimalPasswordLength ); - $user->setNewpassword( $password ); - $user->saveSettings(); - $passwords[] = $this->msg( 'passwordreset-emailelement', $user->getName(), $password ) - ->inLanguage( $userLanguage )->text(); // We'll escape the whole thing later - } - $passwordBlock = implode( "\n\n", $passwords ); - - $this->email = $this->msg( $msg )->inLanguage( $userLanguage ); - $this->email->params( - $username, - $passwordBlock, - count( $passwords ), - '<' . Title::newMainPage()->getCanonicalURL() . '>', - round( $this->getConfig()->get( 'NewPasswordExpiry' ) / 86400 ) - ); - - $title = $this->msg( 'passwordreset-emailtitle' )->inLanguage( $userLanguage ); - - $this->result = $firstUser->sendMail( $title->text(), $this->email->text() ); - - if ( isset( $data['Capture'] ) && $data['Capture'] ) { - // Save the user, will be used if an error occurs when sending the email - $this->firstUser = $firstUser; - } else { - // Blank the email if the user is not supposed to see it - $this->email = null; - } - - if ( $this->result->isGood() ) { - return true; - } elseif ( isset( $data['Capture'] ) && $data['Capture'] ) { - // The email didn't send, but maybe they knew that and that's why they captured it - return true; - } else { - // @todo FIXME: The email wasn't sent, but we have already set - // the password throttle timestamp, so they won't be able to try - // again until it expires... :( - return [ [ 'mailerror', $this->result->getMessage() ] ]; - } + return $this->result; } public function onSuccess() { - if ( $this->getUser()->isAllowed( 'passwordreset' ) && $this->email != null ) { + if ( $this->getUser()->isAllowed( 'passwordreset' ) && $this->passwords ) { // @todo Logging if ( $this->result->isGood() ) { - $this->getOutput()->addWikiMsg( 'passwordreset-emailsent-capture' ); + $this->getOutput()->addWikiMsg( 'passwordreset-emailsent-capture2', + count( $this->passwords ) ); } else { - $this->getOutput()->addWikiMsg( 'passwordreset-emailerror-capture', - $this->result->getMessage(), $this->firstUser->getName() ); + $this->getOutput()->addWikiMsg( 'passwordreset-emailerror-capture2', + $this->result->getMessage(), key( $this->passwords ), count( $this->passwords ) ); } - $this->getOutput()->addHTML( Html::rawElement( 'pre', [], $this->email->escaped() ) ); + $this->getOutput()->addHTML( Html::openElement( 'ul' ) ); + foreach ( $this->passwords as $username => $pwd ) { + $this->getOutput()->addHTML( Html::rawElement( 'li', [], + htmlspecialchars( $username, ENT_QUOTES ) + . $this->msg( 'colon-separator' )->text() + . htmlspecialchars( $pwd, ENT_QUOTES ) + ) ); + } + $this->getOutput()->addHTML( Html::closeElement( 'ul' ) ); } if ( $this->method === 'email' ) { @@ -330,42 +194,12 @@ class SpecialPasswordReset extends FormSpecialPage { $this->getOutput()->returnToMain(); } - protected function canChangePassword( User $user ) { - global $wgAuth; - $resetRoutes = $this->getConfig()->get( 'PasswordResetRoutes' ); - - // Maybe password resets are disabled, or there are no allowable routes - if ( !is_array( $resetRoutes ) || - !in_array( true, array_values( $resetRoutes ) ) - ) { - return 'passwordreset-disabled'; - } - - // Maybe the external auth plugin won't allow local password changes - if ( !$wgAuth->allowPasswordChange() ) { - return 'resetpass_forbidden'; - } - - // Maybe email features have been disabled - if ( !$this->getConfig()->get( 'EnableEmail' ) ) { - return 'passwordreset-emaildisabled'; - } - - // Maybe the user is blocked (check this here rather than relying on the parent - // method as we have a more specific error message to use here - if ( $user->isBlocked() ) { - return 'blocked-mailpassword'; - } - - return true; - } - /** * Hide the password reset page if resets are disabled. * @return bool */ - function isListed() { - if ( $this->canChangePassword( $this->getUser() ) === true ) { + public function isListed() { + if ( $this->passwordReset->isAllowed( $this->getUser() )->isGood() ) { return parent::isListed(); }