MediaWiki
REL1_20
|
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 00126 public function onSubmit( array $data ) { 00127 global $wgAuth; 00128 00129 if ( isset( $data['Domain'] ) ) { 00130 if ( $wgAuth->validDomain( $data['Domain'] ) ) { 00131 $wgAuth->setDomain( $data['Domain'] ); 00132 } else { 00133 $wgAuth->setDomain( 'invaliddomain' ); 00134 } 00135 } 00136 00137 if( isset( $data['Capture'] ) && !$this->getUser()->isAllowed( 'passwordreset' ) ){ 00138 // The user knows they don't have the passwordreset permission, but they tried to spoof the form. That's naughty 00139 throw new PermissionsError( 'passwordreset' ); 00140 } 00141 00147 if ( isset( $data['Username'] ) && $data['Username'] !== '' ) { 00148 $method = 'username'; 00149 $users = array( User::newFromName( $data['Username'] ) ); 00150 } elseif ( isset( $data['Email'] ) 00151 && $data['Email'] !== '' 00152 && Sanitizer::validateEmail( $data['Email'] ) ) 00153 { 00154 $method = 'email'; 00155 $res = wfGetDB( DB_SLAVE )->select( 00156 'user', 00157 User::selectFields(), 00158 array( 'user_email' => $data['Email'] ), 00159 __METHOD__ 00160 ); 00161 if ( $res ) { 00162 $users = array(); 00163 foreach( $res as $row ){ 00164 $users[] = User::newFromRow( $row ); 00165 } 00166 } else { 00167 // Some sort of database error, probably unreachable 00168 throw new MWException( 'Unknown database error in ' . __METHOD__ ); 00169 } 00170 } else { 00171 // The user didn't supply any data 00172 return false; 00173 } 00174 00175 // Check for hooks (captcha etc), and allow them to modify the users list 00176 $error = array(); 00177 if ( !wfRunHooks( 'SpecialPasswordResetOnSubmit', array( &$users, $data, &$error ) ) ) { 00178 return array( $error ); 00179 } 00180 00181 if( count( $users ) == 0 ){ 00182 if( $method == 'email' ){ 00183 // Don't reveal whether or not an email address is in use 00184 return true; 00185 } else { 00186 return array( 'noname' ); 00187 } 00188 } 00189 00190 $firstUser = $users[0]; 00191 00192 if ( !$firstUser instanceof User || !$firstUser->getID() ) { 00193 return array( array( 'nosuchuser', $data['Username'] ) ); 00194 } 00195 00196 // Check against the rate limiter 00197 if ( $this->getUser()->pingLimiter( 'mailpassword' ) ) { 00198 throw new ThrottledError; 00199 } 00200 00201 // Check against password throttle 00202 foreach ( $users as $user ) { 00203 if ( $user->isPasswordReminderThrottled() ) { 00204 global $wgPasswordReminderResendTime; 00205 # Round the time in hours to 3 d.p., in case someone is specifying 00206 # minutes or seconds. 00207 return array( array( 'throttled-mailpassword', round( $wgPasswordReminderResendTime, 3 ) ) ); 00208 } 00209 } 00210 00211 global $wgNewPasswordExpiry; 00212 00213 // All the users will have the same email address 00214 if ( $firstUser->getEmail() == '' ) { 00215 // This won't be reachable from the email route, so safe to expose the username 00216 return array( array( 'noemail', $firstUser->getName() ) ); 00217 } 00218 00219 // We need to have a valid IP address for the hook, but per bug 18347, we should 00220 // send the user's name if they're logged in. 00221 $ip = $this->getRequest()->getIP(); 00222 if ( !$ip ) { 00223 return array( 'badipaddress' ); 00224 } 00225 $caller = $this->getUser(); 00226 wfRunHooks( 'User::mailPasswordInternal', array( &$caller, &$ip, &$firstUser ) ); 00227 $username = $caller->getName(); 00228 $msg = IP::isValid( $username ) 00229 ? 'passwordreset-emailtext-ip' 00230 : 'passwordreset-emailtext-user'; 00231 00232 // Send in the user's language; which should hopefully be the same 00233 $userLanguage = $firstUser->getOption( 'language' ); 00234 00235 $passwords = array(); 00236 foreach ( $users as $user ) { 00237 $password = $user->randomPassword(); 00238 $user->setNewpassword( $password ); 00239 $user->saveSettings(); 00240 $passwords[] = $this->msg( 'passwordreset-emailelement', $user->getName(), $password 00241 )->inLanguage( $userLanguage )->text(); // We'll escape the whole thing later 00242 } 00243 $passwordBlock = implode( "\n\n", $passwords ); 00244 00245 $this->email = $this->msg( $msg )->inLanguage( $userLanguage ); 00246 $this->email->params( 00247 $username, 00248 $passwordBlock, 00249 count( $passwords ), 00250 Title::newMainPage()->getCanonicalUrl(), 00251 round( $wgNewPasswordExpiry / 86400 ) 00252 ); 00253 00254 $title = $this->msg( 'passwordreset-emailtitle' ); 00255 00256 $this->result = $firstUser->sendMail( $title->escaped(), $this->email->escaped() ); 00257 00258 // Blank the email if the user is not supposed to see it 00259 if( !isset( $data['Capture'] ) || !$data['Capture'] ) { 00260 $this->email = null; 00261 } 00262 00263 if ( $this->result->isGood() ) { 00264 return true; 00265 } elseif( isset( $data['Capture'] ) && $data['Capture'] ){ 00266 // The email didn't send, but maybe they knew that and that's why they captured it 00267 return true; 00268 } else { 00269 // @todo FIXME: The email didn't send, but we have already set the password throttle 00270 // timestamp, so they won't be able to try again until it expires... :( 00271 return array( array( 'mailerror', $this->result->getMessage() ) ); 00272 } 00273 } 00274 00275 public function onSuccess() { 00276 if( $this->getUser()->isAllowed( 'passwordreset' ) && $this->email != null ){ 00277 // @todo: Logging 00278 00279 if( $this->result->isGood() ){ 00280 $this->getOutput()->addWikiMsg( 'passwordreset-emailsent-capture' ); 00281 } else { 00282 $this->getOutput()->addWikiMsg( 'passwordreset-emailerror-capture', $this->result->getMessage() ); 00283 } 00284 00285 $this->getOutput()->addHTML( Html::rawElement( 'pre', array(), $this->email->escaped() ) ); 00286 } 00287 00288 $this->getOutput()->addWikiMsg( 'passwordreset-emailsent' ); 00289 $this->getOutput()->returnToMain(); 00290 } 00291 00292 protected function canChangePassword( User $user ) { 00293 global $wgPasswordResetRoutes, $wgAuth; 00294 00295 // Maybe password resets are disabled, or there are no allowable routes 00296 if ( !is_array( $wgPasswordResetRoutes ) || 00297 !in_array( true, array_values( $wgPasswordResetRoutes ) ) ) 00298 { 00299 return 'passwordreset-disabled'; 00300 } 00301 00302 // Maybe the external auth plugin won't allow local password changes 00303 if ( !$wgAuth->allowPasswordChange() ) { 00304 return 'resetpass_forbidden'; 00305 } 00306 00307 // Maybe the user is blocked (check this here rather than relying on the parent 00308 // method as we have a more specific error message to use here 00309 if ( $user->isBlocked() ) { 00310 return 'blocked-mailpassword'; 00311 } 00312 00313 return true; 00314 } 00315 00320 function isListed() { 00321 if ( $this->canChangePassword( $this->getUser() ) === true ) { 00322 return parent::isListed(); 00323 } 00324 00325 return false; 00326 } 00327 }