Textpattern | PHP Cross Reference | Content Management Systems |
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 <head> 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
Body
title
Description
Body
title
Description
Body
title
Body
title