Textpattern PHP Cross Reference Content Management Systems

Source: /textpattern/include/txp_auth.php - 365 lines - 12574 bytes - Summary - Text - Print

Description: Login panel.

   1  <?php
   2  
   3  /*
   4   * Textpattern Content Management System
   5   * http://textpattern.com
   6   *
   7   * Copyright (C) 2005 Dean Allen
   8   * Copyright (C) 2016 The Textpattern Development Team
   9   *
  10   * This file is part of Textpattern.
  11   *
  12   * Textpattern is free software; you can redistribute it and/or
  13   * modify it under the terms of the GNU General Public License
  14   * as published by the Free Software Foundation, version 2.
  15   *
  16   * Textpattern is distributed in the hope that it will be useful,
  17   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  18   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19   * GNU General Public License for more details.
  20   *
  21   * You should have received a copy of the GNU General Public License
  22   * along with Textpattern. If not, see <http://www.gnu.org/licenses/>.
  23   */
  24  
  25  /**
  26   * Login panel.
  27   *
  28   * @package Admin\Auth
  29   */
  30  
  31  if (!defined('txpinterface')) {
  32      die('txpinterface is undefined.');
  33  }
  34  
  35  /**
  36   * Renders a login panel if necessary.
  37   *
  38   * If the current visitor isn't authenticated,
  39   * terminates the script and instead renders
  40   * a login page.
  41   *
  42   * @access private
  43   */
  44  
  45  function doAuth()
  46  {
  47      global $txp_user;
  48  
  49      $txp_user = null;
  50  
  51      $message = doTxpValidate();
  52  
  53      if (!$txp_user) {
  54          if (trim(ps('app_mode')) == 'async') {
  55              echo script_js(
  56                  'alert("'.escape_js(gTxt('login_to_textpattern')).'"); 
  57                  window.location.assign("index.php")'
  58              );
  59              exit();
  60          } else {
  61              doLoginForm($message);
  62          }
  63      }
  64  
  65      ob_start();
  66  }
  67  
  68  /**
  69   * Renders and outputs a login form.
  70   *
  71   * This function outputs a full HTML document,
  72   * including &lt;head&gt; and footer.
  73   *
  74   * @param string|array $message The activity message
  75   */
  76  
  77  function doLoginForm($message)
  78  {
  79      global $textarray_script, $event, $step;
  80  
  81      include txpath.'/lib/txplib_head.php';
  82  
  83      $event = 'login';
  84  
  85      $stay = (cs('txp_login') && !gps('logout') ? 1 : 0);
  86      $reset = gps('reset');
  87      $confirm = gps('confirm');
  88      $activate = gps('activate');
  89  
  90      if (gps('logout')) {
  91          $step = 'logout';
  92      } elseif ($reset) {
  93          $step = 'reset';
  94      } elseif ($activate) {
  95          $step = 'activate';
  96      } elseif ($confirm) {
  97          $step = 'confirm';
  98      }
  99  
 100      $name = join(',', array_slice(explode(',', cs('txp_login')), 0, -1));
 101      $out = array();
 102  
 103      if ($reset) {
 104          $pageTitle = gTxt('password_reset');
 105          $out[] = hed(gTxt('password_reset'), 1, array('id' => 'txp-login-heading')).
 106              inputLabel(
 107                  'login_name',
 108                  fInput('text', 'p_userid', $name, '', '', '', INPUT_REGULAR, '', 'login_name'),
 109                  'name', '', array('class' => 'txp-form-field login-name')
 110              ).
 111              graf(
 112                  fInput('submit', '', gTxt('password_reset_button'), 'publish')
 113              ).
 114              graf(
 115                  href(gTxt('back_to_login'), 'index.php'), array('class' => 'login-return')
 116              ).
 117              hInput('p_reset', 1);
 118      } elseif ($confirm || $activate) {
 119          $pageTitle = ($confirm) ? gTxt('change_password') : gTxt('set_password');
 120          $label = ($confirm) ? 'change_password' : 'set_password';
 121          $class = ($confirm) ? 'change-password' : 'set-password';
 122          $out[] = hed($pageTitle, 1, array('id' => 'txp-'.$class.'-heading')).
 123              inputLabel(
 124                  $label,
 125                  fInput('password', 'p_password', '', 'txp-maskable txp-strength-hint', '', '', INPUT_REGULAR, '', $label, false, true).
 126                  n.tag(null, 'div', array('class' => 'strength-meter')).
 127                  n.tag(
 128                      checkbox('unmask', 1, false, 0, 'show_password').
 129                      n.tag(gTxt('show_password'), 'label', array('for' => 'show_password')),
 130                      'div', array('class' => 'show-password')),
 131                  'new_password', '', array('class' => 'txp-form-field '.$class)
 132              ).
 133              graf(
 134                  fInput('submit', '', gTxt('password_confirm_button'), 'publish')
 135              ).
 136              graf(
 137                  href(gTxt('back_to_login'), 'index.php'), array('class' => 'login-return')
 138              ).
 139              hInput('hash', gps('confirm').gps('activate')).
 140              hInput(($confirm ? 'p_alter' : 'p_set'), 1);
 141      } else {
 142          $pageTitle = gTxt('login');
 143          $out[] = hed(gTxt('login_to_textpattern'), 1, array('id' => 'txp-login-heading')).
 144              inputLabel(
 145                  'login_name',
 146                  fInput('text', 'p_userid', $name, '', '', '', INPUT_REGULAR, '', 'login_name'),
 147                  'name', '', array('class' => 'txp-form-field login-name')
 148              ).
 149              inputLabel(
 150                  'login_password',
 151                  fInput('password', 'p_password', '', '', '', '', INPUT_REGULAR, '', 'login_password'),
 152                  'password', '', array('class' => 'txp-form-field login-password')
 153              ).
 154              graf(
 155                  checkbox('stay', 1, $stay, '', 'login_stay').n.
 156                  tag(gTxt('stay_logged_in'), 'label', array('for' => 'login_stay')).
 157                  popHelp('remember_login'), array('class' => 'login-stay')
 158              ).
 159              graf(
 160                  fInput('submit', '', gTxt('log_in_button'), 'publish')
 161              ).
 162              graf(
 163                  href(gTxt('password_forgotten'), '?reset=1'), array('class' => 'login-forgot')
 164              );
 165  
 166          if (gps('event')) {
 167              $out[] = eInput(gps('event'));
 168          }
 169      }
 170  
 171      pagetop($pageTitle, $message);
 172  
 173      gTxtScript(array(
 174          'password_strength_0',
 175          'password_strength_1',
 176          'password_strength_2',
 177          'password_strength_3',
 178          'password_strength_4',
 179          )
 180      );
 181  
 182      echo form(
 183          join('', $out), '', '', 'post', 'txp-login', '', 'login_form').
 184  
 185      script_js('vendors/dropbox/zxcvbn/zxcvbn.js', TEXTPATTERN_SCRIPT_URL).
 186      script_js('textpattern.textarray = '.json_encode($textarray_script)).
 187      n.'</main><!-- /txp-body -->'.n.'</body>'.n.'</html>';
 188  
 189      exit(0);
 190  }
 191  
 192  /**
 193   * Validates the sent login form and creates a session.
 194   *
 195   * During the reset request procedure, it is conceivable to verify the
 196   * token as soon as it's presented in the URL, but that would:
 197   *  a) require refactoring code similarities in both p_confirm and p_alter branches
 198   *  b) require some way (e.g. an Exception) to signal back to doLoginForm() that
 199   *     the token is bogus so the 'change your password' form is not displayed.
 200   *  c) leak information about the validity of a token, thus allowing rapid brute-force
 201   *     attempts.
 202   *
 203   * The inconvenience of a real user following an expired token and being told so
 204   * after they've set a password is a small price to pay for the improved security
 205   * and reduction of attack surface that validating after submission affords.
 206   *
 207   * @todo  Could the checks be done via a (reusable) Validator()?
 208   *
 209   * @return string A localised feedback message
 210   * @see    doLoginForm()
 211   */
 212  
 213  function doTxpValidate()
 214  {
 215      global $logout, $txp_user;
 216  
 217      $p_userid   = ps('p_userid');
 218      $p_password = ps('p_password');
 219      $p_reset    = ps('p_reset');
 220      $p_alter    = ps('p_alter');
 221      $p_set      = ps('p_set');
 222      $stay       = ps('stay');
 223      $p_confirm  = gps('confirm');
 224      $logout     = gps('logout');
 225      $message    = '';
 226      $pub_path   = preg_replace('|//$|', '/', rhu.'/');
 227  
 228      if (cs('txp_login') && strpos(cs('txp_login'), ',')) {
 229          $txp_login = explode(',', cs('txp_login'));
 230          $c_hash = end($txp_login);
 231          $c_userid = join(',', array_slice($txp_login, 0, -1));
 232      } else {
 233          $c_hash   = '';
 234          $c_userid = '';
 235      }
 236  
 237      if ($logout) {
 238          setcookie('txp_login', '', time() - 3600);
 239          setcookie('txp_login_public', '', time() - 3600, $pub_path);
 240      }
 241  
 242      if ($c_userid && strlen($c_hash) === 32) {
 243          // Cookie exists.
 244          // @todo Improve security by using a better nonce/salt mechanism. md5 and uniqid are bad.
 245          $r = safe_row(
 246              "name, nonce",
 247              'txp_users',
 248              "name = '".doSlash($c_userid)."' AND last_access > DATE_SUB(NOW(), INTERVAL 30 DAY)"
 249          );
 250  
 251          if ($r && $r['nonce'] && $r['nonce'] === md5($c_userid.pack('H*', $c_hash))) {
 252              // Cookie is good.
 253              if ($logout) {
 254                  // Destroy nonce.
 255                  safe_update(
 256                      'txp_users',
 257                      "nonce = '".doSlash(md5(uniqid(mt_rand(), true)))."'",
 258                      "name = '".doSlash($c_userid)."'"
 259                  );
 260              } else {
 261                  // Create $txp_user.
 262                  $txp_user = $r['name'];
 263              }
 264  
 265              return $message;
 266          } else {
 267              txp_status_header('401 Your session has expired');
 268              setcookie('txp_login', $c_userid, time() + 3600 * 24 * 365);
 269              setcookie('txp_login_public', '', time() - 3600, $pub_path);
 270              $message = array(gTxt('bad_cookie'), E_ERROR);
 271          }
 272      } elseif ($p_userid && $p_password) {
 273          // Incoming login vars.
 274          $name = txp_validate($p_userid, $p_password);
 275  
 276          if ($name !== false) {
 277              $c_hash = md5(uniqid(mt_rand(), true));
 278              $nonce  = md5($name.pack('H*', $c_hash));
 279  
 280              safe_update(
 281                  'txp_users',
 282                  "nonce = '".doSlash($nonce)."'",
 283                  "name = '".doSlash($name)."'"
 284              );
 285  
 286              setcookie(
 287                  'txp_login',
 288                  $name.','.$c_hash,
 289                  ($stay ? time() + 3600 * 24 * 365 : 0),
 290                  null,
 291                  null,
 292                  null,
 293                  LOGIN_COOKIE_HTTP_ONLY
 294              );
 295  
 296              setcookie(
 297                  'txp_login_public',
 298                  substr(md5($nonce), -10).$name,
 299                  ($stay ? time() + 3600 * 24 * 30 : 0),
 300                  $pub_path
 301              );
 302  
 303              // Login is good, create $txp_user.
 304              $txp_user = $name;
 305  
 306              return '';
 307          } else {
 308              sleep(3);
 309              txp_status_header('401 Could not log in with that username/password');
 310              $message = array(gTxt('could_not_log_in'), E_ERROR);
 311          }
 312      } elseif ($p_reset) {
 313          // Reset request.
 314          sleep(3);
 315  
 316          include_once txpath.'/lib/txplib_admin.php';
 317  
 318          $message = ($p_userid) ? send_reset_confirmation_request($p_userid) : '';
 319      } elseif ($p_alter || $p_set) {
 320          // Password change/set confirmation.
 321          sleep(3);
 322          global $sitename;
 323  
 324          $pass = ps('p_password');
 325          $type = ($p_alter) ? 'password_reset' : 'account_activation';
 326  
 327          if (trim($pass) === '') {
 328              $message = array(gTxt('password_required'), E_ERROR);
 329          } else {
 330              $hash = gps('hash');
 331              $selector = substr($hash, SALT_LENGTH);
 332  
 333              $tokenInfo = safe_row("reference_id, token, expires", 'txp_token', "selector = '".doSlash($selector)."' AND type='$type'");
 334  
 335              if ($tokenInfo) {
 336                  if (strtotime($tokenInfo['expires']) <= time()) {
 337                      $message = array(gTxt('token_expired'), E_ERROR);
 338                  } else {
 339                      $uid = assert_int($tokenInfo['reference_id']);
 340                      $row = safe_row("name, email, nonce, pass AS old_pass", 'txp_users', "user_id = $uid");
 341  
 342                      if ($row && $row['nonce'] && ($hash === bin2hex(pack('H*', substr(hash(HASHING_ALGORITHM, $row['nonce'].$selector.$row['old_pass']), 0, SALT_LENGTH))).$selector)) {
 343                          if (change_user_password($row['name'], $pass)) {
 344                              $body = gTxt('salutation', array('{name}' => $row['name'])).
 345                                  n.n.($p_alter ? gTxt('password_change_confirmation') : gTxt('password_set_confirmation').n.n.gTxt('log_in_at').': '.hu.'textpattern/index.php');
 346                              $message = ($p_alter) ? gTxt('password_changed') : gTxt('password_set');
 347                              txpMail($row['email'], "[$sitename] ".$message, $body);
 348  
 349                              // Invalidate all tokens in the wild for this user.
 350                              safe_delete("txp_token", "reference_id = $uid AND type IN ('password_reset', 'account_activation')");
 351                          }
 352                      } else {
 353                          $message = array(gTxt('invalid_token'), E_ERROR);
 354                      }
 355                  }
 356              } else {
 357                  $message = array(gTxt('invalid_token'), E_ERROR);
 358              }
 359          }
 360      }
 361  
 362      $txp_user = '';
 363  
 364      return $message;
 365  }

title

Description

title

Description

title

Description

title

title

Body