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