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