MediaWiki  1.23.14
SpecialPasswordReset.php
Go to the documentation of this file.
1 <?php
33  private $email;
34 
38  private $firstUser;
39 
43  private $result;
44 
45  public function __construct() {
46  parent::__construct( 'PasswordReset', 'editmyprivateinfo' );
47  }
48 
49  public function userCanExecute( User $user ) {
50  return $this->canChangePassword( $user ) === true && parent::userCanExecute( $user );
51  }
52 
53  public function checkExecutePermissions( User $user ) {
54  $error = $this->canChangePassword( $user );
55  if ( is_string( $error ) ) {
56  throw new ErrorPageError( 'internalerror', $error );
57  } elseif ( !$error ) {
58  throw new ErrorPageError( 'internalerror', 'resetpass_forbidden' );
59  }
60 
61  return parent::checkExecutePermissions( $user );
62  }
63 
64  protected function getFormFields() {
65  global $wgPasswordResetRoutes, $wgAuth;
66  $a = array();
67  if ( isset( $wgPasswordResetRoutes['username'] ) && $wgPasswordResetRoutes['username'] ) {
68  $a['Username'] = array(
69  'type' => 'text',
70  'label-message' => 'passwordreset-username',
71  );
72 
73  if ( $this->getUser()->isLoggedIn() ) {
74  $a['Username']['default'] = $this->getUser()->getName();
75  }
76  }
77 
78  if ( isset( $wgPasswordResetRoutes['email'] ) && $wgPasswordResetRoutes['email'] ) {
79  $a['Email'] = array(
80  'type' => 'email',
81  'label-message' => 'passwordreset-email',
82  );
83  }
84 
85  if ( isset( $wgPasswordResetRoutes['domain'] ) && $wgPasswordResetRoutes['domain'] ) {
86  $domains = $wgAuth->domainList();
87  $a['Domain'] = array(
88  'type' => 'select',
89  'options' => $domains,
90  'label-message' => 'passwordreset-domain',
91  );
92  }
93 
94  if ( $this->getUser()->isAllowed( 'passwordreset' ) ) {
95  $a['Capture'] = array(
96  'type' => 'check',
97  'label-message' => 'passwordreset-capture',
98  'help-message' => 'passwordreset-capture-help',
99  );
100  }
101 
102  return $a;
103  }
104 
105  public function alterForm( HTMLForm $form ) {
106  global $wgPasswordResetRoutes;
107 
108  $form->setDisplayFormat( 'vform' );
109  // Turn the old-school line around the form off.
110  // XXX This wouldn't be necessary here if we could set the format of
111  // the HTMLForm to 'vform' at its creation, but there's no way to do so
112  // from a FormSpecialPage class.
113  $form->setWrapperLegend( false );
114 
115  $form->addHiddenFields( $this->getRequest()->getValues( 'returnto', 'returntoquery' ) );
116 
117  $i = 0;
118  if ( isset( $wgPasswordResetRoutes['username'] ) && $wgPasswordResetRoutes['username'] ) {
119  $i++;
120  }
121  if ( isset( $wgPasswordResetRoutes['email'] ) && $wgPasswordResetRoutes['email'] ) {
122  $i++;
123  }
124  if ( isset( $wgPasswordResetRoutes['domain'] ) && $wgPasswordResetRoutes['domain'] ) {
125  $i++;
126  }
127 
128  $message = ( $i > 1 ) ? 'passwordreset-text-many' : 'passwordreset-text-one';
129 
130  $form->setHeaderText( $this->msg( $message, $i )->parseAsBlock() );
131  $form->setSubmitTextMsg( 'mailmypassword' );
132  }
133 
143  public function onSubmit( array $data ) {
144  global $wgAuth;
145 
146  if ( isset( $data['Domain'] ) ) {
147  if ( $wgAuth->validDomain( $data['Domain'] ) ) {
148  $wgAuth->setDomain( $data['Domain'] );
149  } else {
150  $wgAuth->setDomain( 'invaliddomain' );
151  }
152  }
153 
154  if ( isset( $data['Capture'] ) && !$this->getUser()->isAllowed( 'passwordreset' ) ) {
155  // The user knows they don't have the passwordreset permission,
156  // but they tried to spoof the form. That's naughty
157  throw new PermissionsError( 'passwordreset' );
158  }
159 
165  if ( isset( $data['Username'] ) && $data['Username'] !== '' ) {
166  $method = 'username';
167  $users = array( User::newFromName( $data['Username'] ) );
168  } elseif ( isset( $data['Email'] )
169  && $data['Email'] !== ''
170  && Sanitizer::validateEmail( $data['Email'] )
171  ) {
172  $method = 'email';
173  $res = wfGetDB( DB_SLAVE )->select(
174  'user',
176  array( 'user_email' => $data['Email'] ),
177  __METHOD__
178  );
179 
180  if ( $res ) {
181  $users = array();
182 
183  foreach ( $res as $row ) {
184  $users[] = User::newFromRow( $row );
185  }
186  } else {
187  // Some sort of database error, probably unreachable
188  throw new MWException( 'Unknown database error in ' . __METHOD__ );
189  }
190  } else {
191  // The user didn't supply any data
192  return false;
193  }
194 
195  // Check for hooks (captcha etc), and allow them to modify the users list
196  $error = array();
197  if ( !wfRunHooks( 'SpecialPasswordResetOnSubmit', array( &$users, $data, &$error ) ) ) {
198  return array( $error );
199  }
200 
201  if ( count( $users ) == 0 ) {
202  if ( $method == 'email' ) {
203  // Don't reveal whether or not an email address is in use
204  return true;
205  } else {
206  return array( 'noname' );
207  }
208  }
209 
210  $firstUser = $users[0];
211 
212  if ( !$firstUser instanceof User || !$firstUser->getID() ) {
213  // Don't parse username as wikitext (bug 65501)
214  return array( array( 'nosuchuser', wfEscapeWikiText( $data['Username'] ) ) );
215  }
216 
217  // Check against the rate limiter
218  if ( $this->getUser()->pingLimiter( 'mailpassword' ) ) {
219  throw new ThrottledError;
220  }
221 
222  // Check against password throttle
223  foreach ( $users as $user ) {
224  if ( $user->isPasswordReminderThrottled() ) {
225  global $wgPasswordReminderResendTime;
226 
227  # Round the time in hours to 3 d.p., in case someone is specifying
228  # minutes or seconds.
229  return array( array(
230  'throttled-mailpassword',
231  round( $wgPasswordReminderResendTime, 3 )
232  ) );
233  }
234  }
235 
236  global $wgNewPasswordExpiry;
237 
238  // All the users will have the same email address
239  if ( $firstUser->getEmail() == '' ) {
240  // This won't be reachable from the email route, so safe to expose the username
241  return array( array( 'noemail', wfEscapeWikiText( $firstUser->getName() ) ) );
242  }
243 
244  // We need to have a valid IP address for the hook, but per bug 18347, we should
245  // send the user's name if they're logged in.
246  $ip = $this->getRequest()->getIP();
247  if ( !$ip ) {
248  return array( 'badipaddress' );
249  }
250  $caller = $this->getUser();
251  wfRunHooks( 'User::mailPasswordInternal', array( &$caller, &$ip, &$firstUser ) );
252  $username = $caller->getName();
253  $msg = IP::isValid( $username )
254  ? 'passwordreset-emailtext-ip'
255  : 'passwordreset-emailtext-user';
256 
257  // Send in the user's language; which should hopefully be the same
258  $userLanguage = $firstUser->getOption( 'language' );
259 
260  $passwords = array();
261  foreach ( $users as $user ) {
262  $password = $user->randomPassword();
263  $user->setNewpassword( $password );
264  $user->saveSettings();
265  $passwords[] = $this->msg( 'passwordreset-emailelement', $user->getName(), $password )
266  ->inLanguage( $userLanguage )->text(); // We'll escape the whole thing later
267  }
268  $passwordBlock = implode( "\n\n", $passwords );
269 
270  $this->email = $this->msg( $msg )->inLanguage( $userLanguage );
271  $this->email->params(
272  $username,
273  $passwordBlock,
274  count( $passwords ),
275  '<' . Title::newMainPage()->getCanonicalURL() . '>',
276  round( $wgNewPasswordExpiry / 86400 )
277  );
278 
279  $title = $this->msg( 'passwordreset-emailtitle' );
280 
281  $this->result = $firstUser->sendMail( $title->text(), $this->email->text() );
282 
283  if ( isset( $data['Capture'] ) && $data['Capture'] ) {
284  // Save the user, will be used if an error occurs when sending the email
285  $this->firstUser = $firstUser;
286  } else {
287  // Blank the email if the user is not supposed to see it
288  $this->email = null;
289  }
290 
291  if ( $this->result->isGood() ) {
292  return true;
293  } elseif ( isset( $data['Capture'] ) && $data['Capture'] ) {
294  // The email didn't send, but maybe they knew that and that's why they captured it
295  return true;
296  } else {
297  // @todo FIXME: The email wasn't sent, but we have already set
298  // the password throttle timestamp, so they won't be able to try
299  // again until it expires... :(
300  return array( array( 'mailerror', $this->result->getMessage() ) );
301  }
302  }
303 
304  public function onSuccess() {
305  if ( $this->getUser()->isAllowed( 'passwordreset' ) && $this->email != null ) {
306  // @todo Logging
307 
308  if ( $this->result->isGood() ) {
309  $this->getOutput()->addWikiMsg( 'passwordreset-emailsent-capture' );
310  } else {
311  $this->getOutput()->addWikiMsg( 'passwordreset-emailerror-capture',
312  $this->result->getMessage(), $this->firstUser->getName() );
313  }
314 
315  $this->getOutput()->addHTML( Html::rawElement( 'pre', array(), $this->email->escaped() ) );
316  }
317 
318  $this->getOutput()->addWikiMsg( 'passwordreset-emailsent' );
319  $this->getOutput()->returnToMain();
320  }
321 
322  protected function canChangePassword( User $user ) {
323  global $wgPasswordResetRoutes, $wgEnableEmail, $wgAuth;
324 
325  // Maybe password resets are disabled, or there are no allowable routes
326  if ( !is_array( $wgPasswordResetRoutes ) ||
327  !in_array( true, array_values( $wgPasswordResetRoutes ) )
328  ) {
329  return 'passwordreset-disabled';
330  }
331 
332  // Maybe the external auth plugin won't allow local password changes
333  if ( !$wgAuth->allowPasswordChange() ) {
334  return 'resetpass_forbidden';
335  }
336 
337  // Maybe email features have been disabled
338  if ( !$wgEnableEmail ) {
339  return 'passwordreset-emaildisabled';
340  }
341 
342  // Maybe the user is blocked (check this here rather than relying on the parent
343  // method as we have a more specific error message to use here
344  if ( $user->isBlocked() ) {
345  return 'blocked-mailpassword';
346  }
347 
348  return true;
349  }
350 
355  function isListed() {
356  if ( $this->canChangePassword( $this->getUser() ) === true ) {
357  return parent::isListed();
358  }
359 
360  return false;
361  }
362 
363  protected function getGroupName() {
364  return 'users';
365  }
366 }
SpecialPasswordReset
Special page for requesting a password reset email.
Definition: SpecialPasswordReset.php:29
SpecialPasswordReset\getGroupName
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
Definition: SpecialPasswordReset.php:360
php
skin txt MediaWiki includes four core it has been set as the default in MediaWiki since the replacing Monobook it had been been the default skin since before being replaced by Vector largely rewritten in while keeping its appearance Several legacy skins were removed in the as the burden of supporting them became too heavy to bear Those in etc for skin dependent CSS etc for skin dependent JavaScript These can also be customised on a per user by etc This feature has led to a wide variety of user styles becoming that gallery is a good place to ending in php
Definition: skin.txt:62
SpecialPage\getOutput
getOutput()
Get the OutputPage being used for this instance.
Definition: SpecialPage.php:535
SpecialPasswordReset\isListed
isListed()
Hide the password reset page if resets are disabled.
Definition: SpecialPasswordReset.php:352
SpecialPasswordReset\$result
Status $result
Definition: SpecialPasswordReset.php:40
SpecialPasswordReset\getFormFields
getFormFields()
Get an HTMLForm descriptor array.
Definition: SpecialPasswordReset.php:61
wfGetDB
& wfGetDB( $db, $groups=array(), $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:3714
Title\newMainPage
static newMainPage()
Create a new Title for the Main Page.
Definition: Title.php:441
$form
usually copyright or history_copyright This message must be in HTML not wikitext $subpages will be ignored and the rest of subPageSubtitle() will run. 'SkinTemplateBuildNavUrlsNav_urlsAfterPermalink' whether MediaWiki currently thinks this is a CSS JS page Hooks may change this value to override the return value of Title::isCssOrJsPage(). 'TitleIsAlwaysKnown' whether MediaWiki currently thinks this page is known isMovable() always returns false. $title whether MediaWiki currently thinks this page is movable Hooks may change this value to override the return value of Title::isMovable(). 'TitleIsWikitextPage' whether MediaWiki currently thinks this is a wikitext page Hooks may change this value to override the return value of Title::isWikitextPage() 'TitleMove' use UploadVerification and UploadVerifyFile instead $form
Definition: hooks.txt:2584
Sanitizer\validateEmail
static validateEmail( $addr)
Does a string look like an e-mail address?
Definition: Sanitizer.php:1847
FormSpecialPage
Special page which uses an HTMLForm to handle processing.
Definition: FormSpecialPage.php:31
User\newFromName
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:389
PermissionsError
Show an error when a user tries to do something they do not have the necessary permissions for.
Definition: PermissionsError.php:28
User\newFromRow
static newFromRow( $row, $data=null)
Create a new user object from a user row.
Definition: User.php:471
Status
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: Status.php:40
User\getEmail
getEmail()
Get the user's e-mail address.
Definition: User.php:2328
SpecialPasswordReset\__construct
__construct()
Definition: SpecialPasswordReset.php:42
MWException
MediaWiki exception.
Definition: MWException.php:26
SpecialPasswordReset\onSubmit
onSubmit(array $data)
Process the form.
Definition: SpecialPasswordReset.php:140
wfRunHooks
wfRunHooks( $event, array $args=array(), $deprecatedVersion=null)
Call hook functions defined in $wgHooks.
Definition: GlobalFunctions.php:4066
ThrottledError
Show an error when the user hits a rate limit.
Definition: ThrottledError.php:27
SpecialPasswordReset\checkExecutePermissions
checkExecutePermissions(User $user)
Called from execute() to check if the given user can perform this action.
Definition: SpecialPasswordReset.php:50
array
the array() calling protocol came about after MediaWiki 1.4rc1.
List of Api Query prop modules.
SpecialPage\getUser
getUser()
Shortcut to get the User executing this instance.
Definition: SpecialPage.php:545
global
when a variable name is used in a it is silently declared as a new masking the global
Definition: design.txt:93
SpecialPasswordReset\onSuccess
onSuccess()
Do something exciting on successful processing of the form, most likely to show a confirmation messag...
Definition: SpecialPasswordReset.php:301
$title
presenting them properly to the user as errors is done by the caller $title
Definition: hooks.txt:1324
SpecialPage\msg
msg()
Wrapper around wfMessage that sets the current context.
Definition: SpecialPage.php:609
User\getOption
getOption( $oname, $defaultOverride=null, $ignoreHidden=false)
Get the user's current setting for a given option.
Definition: User.php:2425
SpecialPage\getRequest
getRequest()
Get the WebRequest being used for this instance.
Definition: SpecialPage.php:525
wfEscapeWikiText
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
Definition: GlobalFunctions.php:2124
IP\isValid
static isValid( $ip)
Validate an IP address.
Definition: IP.php:108
$user
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a account $user
Definition: hooks.txt:237
$password
return false to override stock group addition can be modified try getUserPermissionsErrors userCan checks are continued by internal code can override on output return false to not delete it return false to override the default password checks this Boolean value will be checked to determine if the password was valid return false to implement your own hashing method & $password
Definition: hooks.txt:2708
DB_SLAVE
const DB_SLAVE
Definition: Defines.php:55
SpecialPasswordReset\userCanExecute
userCanExecute(User $user)
Checks if the given user (identified by an object) can execute this special page (as defined by $mRes...
Definition: SpecialPasswordReset.php:46
as
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
SpecialPasswordReset\alterForm
alterForm(HTMLForm $form)
Play with the HTMLForm if you need to more substantially.
Definition: SpecialPasswordReset.php:102
User\sendMail
sendMail( $subject, $body, $from=null, $replyto=null)
Send an e-mail to this user's account.
Definition: User.php:3874
User\selectFields
static selectFields()
Return the list of user fields that should be selected to create a new user object.
Definition: User.php:4831
SpecialPasswordReset\$email
Message $email
Definition: SpecialPasswordReset.php:32
$error
usually copyright or history_copyright This message must be in HTML not wikitext $subpages will be ignored and the rest of subPageSubtitle() will run. 'SkinTemplateBuildNavUrlsNav_urlsAfterPermalink' whether MediaWiki currently thinks this is a CSS JS page Hooks may change this value to override the return value of Title::isCssOrJsPage(). 'TitleIsAlwaysKnown' whether MediaWiki currently thinks this page is known isMovable() always returns false. $title whether MediaWiki currently thinks this page is movable Hooks may change this value to override the return value of Title::isMovable(). 'TitleIsWikitextPage' whether MediaWiki currently thinks this is a wikitext page Hooks may change this value to override the return value of Title::isWikitextPage() 'TitleMove' use UploadVerification and UploadVerifyFile instead where the first element is the message key and the remaining elements are used as parameters to the message based on mime etc Preferred in most cases over UploadVerification object with all info about the upload string as detected by MediaWiki Handlers will typically only apply for specific mime types object & $error
Definition: hooks.txt:2584
Html\rawElement
static rawElement( $element, $attribs=array(), $contents='')
Returns an HTML element in a string.
Definition: Html.php:124
ErrorPageError
An error page which can definitely be safely rendered using the OutputPage.
Definition: ErrorPageError.php:27
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:59
$res
$res
Definition: database.txt:21
SpecialPasswordReset\canChangePassword
canChangePassword(User $user)
Definition: SpecialPasswordReset.php:319
User\getName
getName()
Get the user name, or the IP of an anonymous user.
Definition: User.php:1876
SpecialPasswordReset\$firstUser
User $firstUser
Definition: SpecialPasswordReset.php:36
HTMLForm
Object handling generic submission, CSRF protection, layout and other logic for UI forms.
Definition: HTMLForm.php:100