[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/specials/ -> SpecialPasswordReset.php (source)

   1  <?php
   2  /**
   3   * Implements Special:PasswordReset
   4   *
   5   * This program is free software; you can redistribute it and/or modify
   6   * it under the terms of the GNU General Public License as published by
   7   * the Free Software Foundation; either version 2 of the License, or
   8   * (at your option) any later version.
   9   *
  10   * This program is distributed in the hope that it will be useful,
  11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13   * GNU General Public License for more details.
  14   *
  15   * You should have received a copy of the GNU General Public License along
  16   * with this program; if not, write to the Free Software Foundation, Inc.,
  17   * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18   * http://www.gnu.org/copyleft/gpl.html
  19   *
  20   * @file
  21   * @ingroup SpecialPage
  22   */
  23  
  24  /**
  25   * Special page for requesting a password reset email
  26   *
  27   * @ingroup SpecialPage
  28   */
  29  class SpecialPasswordReset extends FormSpecialPage {
  30      /**
  31       * @var Message
  32       */
  33      private $email;
  34  
  35      /**
  36       * @var User
  37       */
  38      private $firstUser;
  39  
  40      /**
  41       * @var Status
  42       */
  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 $wgAuth;
  66          $resetRoutes = $this->getConfig()->get( 'PasswordResetRoutes' );
  67          $a = array();
  68          if ( isset( $resetRoutes['username'] ) && $resetRoutes['username'] ) {
  69              $a['Username'] = array(
  70                  'type' => 'text',
  71                  'label-message' => 'passwordreset-username',
  72              );
  73  
  74              if ( $this->getUser()->isLoggedIn() ) {
  75                  $a['Username']['default'] = $this->getUser()->getName();
  76              }
  77          }
  78  
  79          if ( isset( $resetRoutes['email'] ) && $resetRoutes['email'] ) {
  80              $a['Email'] = array(
  81                  'type' => 'email',
  82                  'label-message' => 'passwordreset-email',
  83              );
  84          }
  85  
  86          if ( isset( $resetRoutes['domain'] ) && $resetRoutes['domain'] ) {
  87              $domains = $wgAuth->domainList();
  88              $a['Domain'] = array(
  89                  'type' => 'select',
  90                  'options' => $domains,
  91                  'label-message' => 'passwordreset-domain',
  92              );
  93          }
  94  
  95          if ( $this->getUser()->isAllowed( 'passwordreset' ) ) {
  96              $a['Capture'] = array(
  97                  'type' => 'check',
  98                  'label-message' => 'passwordreset-capture',
  99                  'help-message' => 'passwordreset-capture-help',
 100              );
 101          }
 102  
 103          return $a;
 104      }
 105  
 106  	public function alterForm( HTMLForm $form ) {
 107          $resetRoutes = $this->getConfig()->get( 'PasswordResetRoutes' );
 108  
 109          $form->setDisplayFormat( 'vform' );
 110          // Turn the old-school line around the form off.
 111          // XXX This wouldn't be necessary here if we could set the format of
 112          // the HTMLForm to 'vform' at its creation, but there's no way to do so
 113          // from a FormSpecialPage class.
 114          $form->setWrapperLegend( false );
 115  
 116          $form->addHiddenFields( $this->getRequest()->getValues( 'returnto', 'returntoquery' ) );
 117  
 118          $i = 0;
 119          if ( isset( $resetRoutes['username'] ) && $resetRoutes['username'] ) {
 120              $i++;
 121          }
 122          if ( isset( $resetRoutes['email'] ) && $resetRoutes['email'] ) {
 123              $i++;
 124          }
 125          if ( isset( $resetRoutes['domain'] ) && $resetRoutes['domain'] ) {
 126              $i++;
 127          }
 128  
 129          $message = ( $i > 1 ) ? 'passwordreset-text-many' : 'passwordreset-text-one';
 130  
 131          $form->setHeaderText( $this->msg( $message, $i )->parseAsBlock() );
 132          $form->setSubmitTextMsg( 'mailmypassword' );
 133      }
 134  
 135      /**
 136       * Process the form.  At this point we know that the user passes all the criteria in
 137       * userCanExecute(), and if the data array contains 'Username', etc, then Username
 138       * resets are allowed.
 139       * @param array $data
 140       * @throws MWException
 141       * @throws ThrottledError|PermissionsError
 142       * @return bool|array
 143       */
 144  	public function onSubmit( array $data ) {
 145          global $wgAuth;
 146  
 147          if ( isset( $data['Domain'] ) ) {
 148              if ( $wgAuth->validDomain( $data['Domain'] ) ) {
 149                  $wgAuth->setDomain( $data['Domain'] );
 150              } else {
 151                  $wgAuth->setDomain( 'invaliddomain' );
 152              }
 153          }
 154  
 155          if ( isset( $data['Capture'] ) && !$this->getUser()->isAllowed( 'passwordreset' ) ) {
 156              // The user knows they don't have the passwordreset permission,
 157              // but they tried to spoof the form. That's naughty
 158              throw new PermissionsError( 'passwordreset' );
 159          }
 160  
 161          /**
 162           * @var $firstUser User
 163           * @var $users User[]
 164           */
 165  
 166          if ( isset( $data['Username'] ) && $data['Username'] !== '' ) {
 167              $method = 'username';
 168              $users = array( User::newFromName( $data['Username'] ) );
 169          } elseif ( isset( $data['Email'] )
 170              && $data['Email'] !== ''
 171              && Sanitizer::validateEmail( $data['Email'] )
 172          ) {
 173              $method = 'email';
 174              $res = wfGetDB( DB_SLAVE )->select(
 175                  'user',
 176                  User::selectFields(),
 177                  array( 'user_email' => $data['Email'] ),
 178                  __METHOD__
 179              );
 180  
 181              if ( $res ) {
 182                  $users = array();
 183  
 184                  foreach ( $res as $row ) {
 185                      $users[] = User::newFromRow( $row );
 186                  }
 187              } else {
 188                  // Some sort of database error, probably unreachable
 189                  throw new MWException( 'Unknown database error in ' . __METHOD__ );
 190              }
 191          } else {
 192              // The user didn't supply any data
 193              return false;
 194          }
 195  
 196          // Check for hooks (captcha etc), and allow them to modify the users list
 197          $error = array();
 198          if ( !wfRunHooks( 'SpecialPasswordResetOnSubmit', array( &$users, $data, &$error ) ) ) {
 199              return array( $error );
 200          }
 201  
 202          if ( count( $users ) == 0 ) {
 203              if ( $method == 'email' ) {
 204                  // Don't reveal whether or not an email address is in use
 205                  return true;
 206              } else {
 207                  return array( 'noname' );
 208              }
 209          }
 210  
 211          $firstUser = $users[0];
 212  
 213          if ( !$firstUser instanceof User || !$firstUser->getID() ) {
 214              // Don't parse username as wikitext (bug 65501)
 215              return array( array( 'nosuchuser', wfEscapeWikiText( $data['Username'] ) ) );
 216          }
 217  
 218          // Check against the rate limiter
 219          if ( $this->getUser()->pingLimiter( 'mailpassword' ) ) {
 220              throw new ThrottledError;
 221          }
 222  
 223          // Check against password throttle
 224          foreach ( $users as $user ) {
 225              if ( $user->isPasswordReminderThrottled() ) {
 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( $this->getConfig()->get( 'PasswordReminderResendTime' ), 3 )
 232                  ) );
 233              }
 234          }
 235  
 236          // All the users will have the same email address
 237          if ( $firstUser->getEmail() == '' ) {
 238              // This won't be reachable from the email route, so safe to expose the username
 239              return array( array( 'noemail', wfEscapeWikiText( $firstUser->getName() ) ) );
 240          }
 241  
 242          // We need to have a valid IP address for the hook, but per bug 18347, we should
 243          // send the user's name if they're logged in.
 244          $ip = $this->getRequest()->getIP();
 245          if ( !$ip ) {
 246              return array( 'badipaddress' );
 247          }
 248          $caller = $this->getUser();
 249          wfRunHooks( 'User::mailPasswordInternal', array( &$caller, &$ip, &$firstUser ) );
 250          $username = $caller->getName();
 251          $msg = IP::isValid( $username )
 252              ? 'passwordreset-emailtext-ip'
 253              : 'passwordreset-emailtext-user';
 254  
 255          // Send in the user's language; which should hopefully be the same
 256          $userLanguage = $firstUser->getOption( 'language' );
 257  
 258          $passwords = array();
 259          foreach ( $users as $user ) {
 260              $password = $user->randomPassword();
 261              $user->setNewpassword( $password );
 262              $user->saveSettings();
 263              $passwords[] = $this->msg( 'passwordreset-emailelement', $user->getName(), $password )
 264                  ->inLanguage( $userLanguage )->text(); // We'll escape the whole thing later
 265          }
 266          $passwordBlock = implode( "\n\n", $passwords );
 267  
 268          $this->email = $this->msg( $msg )->inLanguage( $userLanguage );
 269          $this->email->params(
 270              $username,
 271              $passwordBlock,
 272              count( $passwords ),
 273              '<' . Title::newMainPage()->getCanonicalURL() . '>',
 274              round( $this->getConfig()->get( 'NewPasswordExpiry' ) / 86400 )
 275          );
 276  
 277          $title = $this->msg( 'passwordreset-emailtitle' );
 278  
 279          $this->result = $firstUser->sendMail( $title->text(), $this->email->text() );
 280  
 281          if ( isset( $data['Capture'] ) && $data['Capture'] ) {
 282              // Save the user, will be used if an error occurs when sending the email
 283              $this->firstUser = $firstUser;
 284          } else {
 285              // Blank the email if the user is not supposed to see it
 286              $this->email = null;
 287          }
 288  
 289          if ( $this->result->isGood() ) {
 290              return true;
 291          } elseif ( isset( $data['Capture'] ) && $data['Capture'] ) {
 292              // The email didn't send, but maybe they knew that and that's why they captured it
 293              return true;
 294          } else {
 295              // @todo FIXME: The email wasn't sent, but we have already set
 296              // the password throttle timestamp, so they won't be able to try
 297              // again until it expires...  :(
 298              return array( array( 'mailerror', $this->result->getMessage() ) );
 299          }
 300      }
 301  
 302  	public function onSuccess() {
 303          if ( $this->getUser()->isAllowed( 'passwordreset' ) && $this->email != null ) {
 304              // @todo Logging
 305  
 306              if ( $this->result->isGood() ) {
 307                  $this->getOutput()->addWikiMsg( 'passwordreset-emailsent-capture' );
 308              } else {
 309                  $this->getOutput()->addWikiMsg( 'passwordreset-emailerror-capture',
 310                      $this->result->getMessage(), $this->firstUser->getName() );
 311              }
 312  
 313              $this->getOutput()->addHTML( Html::rawElement( 'pre', array(), $this->email->escaped() ) );
 314          }
 315  
 316          $this->getOutput()->addWikiMsg( 'passwordreset-emailsent' );
 317          $this->getOutput()->returnToMain();
 318      }
 319  
 320  	protected function canChangePassword( User $user ) {
 321          global $wgAuth;
 322          $resetRoutes = $this->getConfig()->get( 'PasswordResetRoutes' );
 323  
 324          // Maybe password resets are disabled, or there are no allowable routes
 325          if ( !is_array( $resetRoutes ) ||
 326              !in_array( true, array_values( $resetRoutes ) )
 327          ) {
 328              return 'passwordreset-disabled';
 329          }
 330  
 331          // Maybe the external auth plugin won't allow local password changes
 332          if ( !$wgAuth->allowPasswordChange() ) {
 333              return 'resetpass_forbidden';
 334          }
 335  
 336          // Maybe email features have been disabled
 337          if ( !$this->getConfig()->get( 'EnableEmail' ) ) {
 338              return 'passwordreset-emaildisabled';
 339          }
 340  
 341          // Maybe the user is blocked (check this here rather than relying on the parent
 342          // method as we have a more specific error message to use here
 343          if ( $user->isBlocked() ) {
 344              return 'blocked-mailpassword';
 345          }
 346  
 347          return true;
 348      }
 349  
 350      /**
 351       * Hide the password reset page if resets are disabled.
 352       * @return bool
 353       */
 354  	function isListed() {
 355          if ( $this->canChangePassword( $this->getUser() ) === true ) {
 356              return parent::isListed();
 357          }
 358  
 359          return false;
 360      }
 361  
 362  	protected function getGroupName() {
 363          return 'users';
 364      }
 365  }


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1