MediaWiki
REL1_24
|
00001 <?php 00029 class SpecialPasswordReset extends FormSpecialPage { 00033 private $email; 00034 00038 private $firstUser; 00039 00043 private $result; 00044 00045 public function __construct() { 00046 parent::__construct( 'PasswordReset', 'editmyprivateinfo' ); 00047 } 00048 00049 public function userCanExecute( User $user ) { 00050 return $this->canChangePassword( $user ) === true && parent::userCanExecute( $user ); 00051 } 00052 00053 public function checkExecutePermissions( User $user ) { 00054 $error = $this->canChangePassword( $user ); 00055 if ( is_string( $error ) ) { 00056 throw new ErrorPageError( 'internalerror', $error ); 00057 } elseif ( !$error ) { 00058 throw new ErrorPageError( 'internalerror', 'resetpass_forbidden' ); 00059 } 00060 00061 return parent::checkExecutePermissions( $user ); 00062 } 00063 00064 protected function getFormFields() { 00065 global $wgAuth; 00066 $resetRoutes = $this->getConfig()->get( 'PasswordResetRoutes' ); 00067 $a = array(); 00068 if ( isset( $resetRoutes['username'] ) && $resetRoutes['username'] ) { 00069 $a['Username'] = array( 00070 'type' => 'text', 00071 'label-message' => 'passwordreset-username', 00072 ); 00073 00074 if ( $this->getUser()->isLoggedIn() ) { 00075 $a['Username']['default'] = $this->getUser()->getName(); 00076 } 00077 } 00078 00079 if ( isset( $resetRoutes['email'] ) && $resetRoutes['email'] ) { 00080 $a['Email'] = array( 00081 'type' => 'email', 00082 'label-message' => 'passwordreset-email', 00083 ); 00084 } 00085 00086 if ( isset( $resetRoutes['domain'] ) && $resetRoutes['domain'] ) { 00087 $domains = $wgAuth->domainList(); 00088 $a['Domain'] = array( 00089 'type' => 'select', 00090 'options' => $domains, 00091 'label-message' => 'passwordreset-domain', 00092 ); 00093 } 00094 00095 if ( $this->getUser()->isAllowed( 'passwordreset' ) ) { 00096 $a['Capture'] = array( 00097 'type' => 'check', 00098 'label-message' => 'passwordreset-capture', 00099 'help-message' => 'passwordreset-capture-help', 00100 ); 00101 } 00102 00103 return $a; 00104 } 00105 00106 public function alterForm( HTMLForm $form ) { 00107 $resetRoutes = $this->getConfig()->get( 'PasswordResetRoutes' ); 00108 00109 $form->setDisplayFormat( 'vform' ); 00110 // Turn the old-school line around the form off. 00111 // XXX This wouldn't be necessary here if we could set the format of 00112 // the HTMLForm to 'vform' at its creation, but there's no way to do so 00113 // from a FormSpecialPage class. 00114 $form->setWrapperLegend( false ); 00115 00116 $form->addHiddenFields( $this->getRequest()->getValues( 'returnto', 'returntoquery' ) ); 00117 00118 $i = 0; 00119 if ( isset( $resetRoutes['username'] ) && $resetRoutes['username'] ) { 00120 $i++; 00121 } 00122 if ( isset( $resetRoutes['email'] ) && $resetRoutes['email'] ) { 00123 $i++; 00124 } 00125 if ( isset( $resetRoutes['domain'] ) && $resetRoutes['domain'] ) { 00126 $i++; 00127 } 00128 00129 $message = ( $i > 1 ) ? 'passwordreset-text-many' : 'passwordreset-text-one'; 00130 00131 $form->setHeaderText( $this->msg( $message, $i )->parseAsBlock() ); 00132 $form->setSubmitTextMsg( 'mailmypassword' ); 00133 } 00134 00144 public function onSubmit( array $data ) { 00145 global $wgAuth; 00146 00147 if ( isset( $data['Domain'] ) ) { 00148 if ( $wgAuth->validDomain( $data['Domain'] ) ) { 00149 $wgAuth->setDomain( $data['Domain'] ); 00150 } else { 00151 $wgAuth->setDomain( 'invaliddomain' ); 00152 } 00153 } 00154 00155 if ( isset( $data['Capture'] ) && !$this->getUser()->isAllowed( 'passwordreset' ) ) { 00156 // The user knows they don't have the passwordreset permission, 00157 // but they tried to spoof the form. That's naughty 00158 throw new PermissionsError( 'passwordreset' ); 00159 } 00160 00166 if ( isset( $data['Username'] ) && $data['Username'] !== '' ) { 00167 $method = 'username'; 00168 $users = array( User::newFromName( $data['Username'] ) ); 00169 } elseif ( isset( $data['Email'] ) 00170 && $data['Email'] !== '' 00171 && Sanitizer::validateEmail( $data['Email'] ) 00172 ) { 00173 $method = 'email'; 00174 $res = wfGetDB( DB_SLAVE )->select( 00175 'user', 00176 User::selectFields(), 00177 array( 'user_email' => $data['Email'] ), 00178 __METHOD__ 00179 ); 00180 00181 if ( $res ) { 00182 $users = array(); 00183 00184 foreach ( $res as $row ) { 00185 $users[] = User::newFromRow( $row ); 00186 } 00187 } else { 00188 // Some sort of database error, probably unreachable 00189 throw new MWException( 'Unknown database error in ' . __METHOD__ ); 00190 } 00191 } else { 00192 // The user didn't supply any data 00193 return false; 00194 } 00195 00196 // Check for hooks (captcha etc), and allow them to modify the users list 00197 $error = array(); 00198 if ( !wfRunHooks( 'SpecialPasswordResetOnSubmit', array( &$users, $data, &$error ) ) ) { 00199 return array( $error ); 00200 } 00201 00202 if ( count( $users ) == 0 ) { 00203 if ( $method == 'email' ) { 00204 // Don't reveal whether or not an email address is in use 00205 return true; 00206 } else { 00207 return array( 'noname' ); 00208 } 00209 } 00210 00211 $firstUser = $users[0]; 00212 00213 if ( !$firstUser instanceof User || !$firstUser->getID() ) { 00214 // Don't parse username as wikitext (bug 65501) 00215 return array( array( 'nosuchuser', wfEscapeWikiText( $data['Username'] ) ) ); 00216 } 00217 00218 // Check against the rate limiter 00219 if ( $this->getUser()->pingLimiter( 'mailpassword' ) ) { 00220 throw new ThrottledError; 00221 } 00222 00223 // Check against password throttle 00224 foreach ( $users as $user ) { 00225 if ( $user->isPasswordReminderThrottled() ) { 00226 00227 # Round the time in hours to 3 d.p., in case someone is specifying 00228 # minutes or seconds. 00229 return array( array( 00230 'throttled-mailpassword', 00231 round( $this->getConfig()->get( 'PasswordReminderResendTime' ), 3 ) 00232 ) ); 00233 } 00234 } 00235 00236 // All the users will have the same email address 00237 if ( $firstUser->getEmail() == '' ) { 00238 // This won't be reachable from the email route, so safe to expose the username 00239 return array( array( 'noemail', wfEscapeWikiText( $firstUser->getName() ) ) ); 00240 } 00241 00242 // We need to have a valid IP address for the hook, but per bug 18347, we should 00243 // send the user's name if they're logged in. 00244 $ip = $this->getRequest()->getIP(); 00245 if ( !$ip ) { 00246 return array( 'badipaddress' ); 00247 } 00248 $caller = $this->getUser(); 00249 wfRunHooks( 'User::mailPasswordInternal', array( &$caller, &$ip, &$firstUser ) ); 00250 $username = $caller->getName(); 00251 $msg = IP::isValid( $username ) 00252 ? 'passwordreset-emailtext-ip' 00253 : 'passwordreset-emailtext-user'; 00254 00255 // Send in the user's language; which should hopefully be the same 00256 $userLanguage = $firstUser->getOption( 'language' ); 00257 00258 $passwords = array(); 00259 foreach ( $users as $user ) { 00260 $password = $user->randomPassword(); 00261 $user->setNewpassword( $password ); 00262 $user->saveSettings(); 00263 $passwords[] = $this->msg( 'passwordreset-emailelement', $user->getName(), $password ) 00264 ->inLanguage( $userLanguage )->text(); // We'll escape the whole thing later 00265 } 00266 $passwordBlock = implode( "\n\n", $passwords ); 00267 00268 $this->email = $this->msg( $msg )->inLanguage( $userLanguage ); 00269 $this->email->params( 00270 $username, 00271 $passwordBlock, 00272 count( $passwords ), 00273 '<' . Title::newMainPage()->getCanonicalURL() . '>', 00274 round( $this->getConfig()->get( 'NewPasswordExpiry' ) / 86400 ) 00275 ); 00276 00277 $title = $this->msg( 'passwordreset-emailtitle' ); 00278 00279 $this->result = $firstUser->sendMail( $title->text(), $this->email->text() ); 00280 00281 if ( isset( $data['Capture'] ) && $data['Capture'] ) { 00282 // Save the user, will be used if an error occurs when sending the email 00283 $this->firstUser = $firstUser; 00284 } else { 00285 // Blank the email if the user is not supposed to see it 00286 $this->email = null; 00287 } 00288 00289 if ( $this->result->isGood() ) { 00290 return true; 00291 } elseif ( isset( $data['Capture'] ) && $data['Capture'] ) { 00292 // The email didn't send, but maybe they knew that and that's why they captured it 00293 return true; 00294 } else { 00295 // @todo FIXME: The email wasn't sent, but we have already set 00296 // the password throttle timestamp, so they won't be able to try 00297 // again until it expires... :( 00298 return array( array( 'mailerror', $this->result->getMessage() ) ); 00299 } 00300 } 00301 00302 public function onSuccess() { 00303 if ( $this->getUser()->isAllowed( 'passwordreset' ) && $this->email != null ) { 00304 // @todo Logging 00305 00306 if ( $this->result->isGood() ) { 00307 $this->getOutput()->addWikiMsg( 'passwordreset-emailsent-capture' ); 00308 } else { 00309 $this->getOutput()->addWikiMsg( 'passwordreset-emailerror-capture', 00310 $this->result->getMessage(), $this->firstUser->getName() ); 00311 } 00312 00313 $this->getOutput()->addHTML( Html::rawElement( 'pre', array(), $this->email->escaped() ) ); 00314 } 00315 00316 $this->getOutput()->addWikiMsg( 'passwordreset-emailsent' ); 00317 $this->getOutput()->returnToMain(); 00318 } 00319 00320 protected function canChangePassword( User $user ) { 00321 global $wgAuth; 00322 $resetRoutes = $this->getConfig()->get( 'PasswordResetRoutes' ); 00323 00324 // Maybe password resets are disabled, or there are no allowable routes 00325 if ( !is_array( $resetRoutes ) || 00326 !in_array( true, array_values( $resetRoutes ) ) 00327 ) { 00328 return 'passwordreset-disabled'; 00329 } 00330 00331 // Maybe the external auth plugin won't allow local password changes 00332 if ( !$wgAuth->allowPasswordChange() ) { 00333 return 'resetpass_forbidden'; 00334 } 00335 00336 // Maybe email features have been disabled 00337 if ( !$this->getConfig()->get( 'EnableEmail' ) ) { 00338 return 'passwordreset-emaildisabled'; 00339 } 00340 00341 // Maybe the user is blocked (check this here rather than relying on the parent 00342 // method as we have a more specific error message to use here 00343 if ( $user->isBlocked() ) { 00344 return 'blocked-mailpassword'; 00345 } 00346 00347 return true; 00348 } 00349 00354 function isListed() { 00355 if ( $this->canChangePassword( $this->getUser() ) === true ) { 00356 return parent::isListed(); 00357 } 00358 00359 return false; 00360 } 00361 00362 protected function getGroupName() { 00363 return 'users'; 00364 } 00365 }