MediaWiki
REL1_22
|
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 $i = 0; 00116 if ( isset( $wgPasswordResetRoutes['username'] ) && $wgPasswordResetRoutes['username'] ) { 00117 $i++; 00118 } 00119 if ( isset( $wgPasswordResetRoutes['email'] ) && $wgPasswordResetRoutes['email'] ) { 00120 $i++; 00121 } 00122 if ( isset( $wgPasswordResetRoutes['domain'] ) && $wgPasswordResetRoutes['domain'] ) { 00123 $i++; 00124 } 00125 00126 $message = ( $i > 1 ) ? 'passwordreset-text-many' : 'passwordreset-text-one'; 00127 00128 $form->setHeaderText( $this->msg( $message, $i )->parseAsBlock() ); 00129 $form->setSubmitTextMsg( 'mailmypassword' ); 00130 } 00131 00141 public function onSubmit( array $data ) { 00142 global $wgAuth; 00143 00144 if ( isset( $data['Domain'] ) ) { 00145 if ( $wgAuth->validDomain( $data['Domain'] ) ) { 00146 $wgAuth->setDomain( $data['Domain'] ); 00147 } else { 00148 $wgAuth->setDomain( 'invaliddomain' ); 00149 } 00150 } 00151 00152 if ( isset( $data['Capture'] ) && !$this->getUser()->isAllowed( 'passwordreset' ) ) { 00153 // The user knows they don't have the passwordreset permission, 00154 // but they tried to spoof the form. That's naughty 00155 throw new PermissionsError( 'passwordreset' ); 00156 } 00157 00163 if ( isset( $data['Username'] ) && $data['Username'] !== '' ) { 00164 $method = 'username'; 00165 $users = array( User::newFromName( $data['Username'] ) ); 00166 } elseif ( isset( $data['Email'] ) 00167 && $data['Email'] !== '' 00168 && Sanitizer::validateEmail( $data['Email'] ) 00169 ) { 00170 $method = 'email'; 00171 $res = wfGetDB( DB_SLAVE )->select( 00172 'user', 00173 User::selectFields(), 00174 array( 'user_email' => $data['Email'] ), 00175 __METHOD__ 00176 ); 00177 00178 if ( $res ) { 00179 $users = array(); 00180 00181 foreach ( $res as $row ) { 00182 $users[] = User::newFromRow( $row ); 00183 } 00184 } else { 00185 // Some sort of database error, probably unreachable 00186 throw new MWException( 'Unknown database error in ' . __METHOD__ ); 00187 } 00188 } else { 00189 // The user didn't supply any data 00190 return false; 00191 } 00192 00193 // Check for hooks (captcha etc), and allow them to modify the users list 00194 $error = array(); 00195 if ( !wfRunHooks( 'SpecialPasswordResetOnSubmit', array( &$users, $data, &$error ) ) ) { 00196 return array( $error ); 00197 } 00198 00199 if ( count( $users ) == 0 ) { 00200 if ( $method == 'email' ) { 00201 // Don't reveal whether or not an email address is in use 00202 return true; 00203 } else { 00204 return array( 'noname' ); 00205 } 00206 } 00207 00208 $firstUser = $users[0]; 00209 00210 if ( !$firstUser instanceof User || !$firstUser->getID() ) { 00211 // Don't parse username as wikitext (bug 65501) 00212 return array( array( 'nosuchuser', wfEscapeWikiText( $data['Username'] ) ) ); 00213 } 00214 00215 // Check against the rate limiter 00216 if ( $this->getUser()->pingLimiter( 'mailpassword' ) ) { 00217 throw new ThrottledError; 00218 } 00219 00220 // Check against password throttle 00221 foreach ( $users as $user ) { 00222 if ( $user->isPasswordReminderThrottled() ) { 00223 global $wgPasswordReminderResendTime; 00224 00225 # Round the time in hours to 3 d.p., in case someone is specifying 00226 # minutes or seconds. 00227 return array( array( 00228 'throttled-mailpassword', 00229 round( $wgPasswordReminderResendTime, 3 ) 00230 ) ); 00231 } 00232 } 00233 00234 global $wgNewPasswordExpiry; 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( $wgNewPasswordExpiry / 86400 ) 00275 ); 00276 00277 $title = $this->msg( 'passwordreset-emailtitle' ); 00278 00279 $this->result = $firstUser->sendMail( $title->escaped(), $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 $wgPasswordResetRoutes, $wgEnableEmail, $wgAuth; 00322 00323 // Maybe password resets are disabled, or there are no allowable routes 00324 if ( !is_array( $wgPasswordResetRoutes ) || 00325 !in_array( true, array_values( $wgPasswordResetRoutes ) ) 00326 ) { 00327 return 'passwordreset-disabled'; 00328 } 00329 00330 // Maybe the external auth plugin won't allow local password changes 00331 if ( !$wgAuth->allowPasswordChange() ) { 00332 return 'resetpass_forbidden'; 00333 } 00334 00335 // Maybe email features have been disabled 00336 if ( !$wgEnableEmail ) { 00337 return 'passwordreset-emaildisabled'; 00338 } 00339 00340 // Maybe the user is blocked (check this here rather than relying on the parent 00341 // method as we have a more specific error message to use here 00342 if ( $user->isBlocked() ) { 00343 return 'blocked-mailpassword'; 00344 } 00345 00346 return true; 00347 } 00348 00353 function isListed() { 00354 if ( $this->canChangePassword( $this->getUser() ) === true ) { 00355 return parent::isListed(); 00356 } 00357 00358 return false; 00359 } 00360 00361 protected function getGroupName() { 00362 return 'users'; 00363 } 00364 }