# HG changeset patch # User Dan # Date 1235628452 18000 # Node ID 4415e50e4e84ca0744d73b3c66336c85d6dec77b # Parent f13bb4f218902190f03fbd6bb037ad4b82fdaaab Added possibility for auth plugins, which can log a user in using non-standard authentication methods. diff -r f13bb4f21890 -r 4415e50e4e84 includes/clientside/static/crypto.js --- a/includes/clientside/static/crypto.js Thu Feb 26 01:06:58 2009 -0500 +++ b/includes/clientside/static/crypto.js Thu Feb 26 01:07:32 2009 -0500 @@ -1981,7 +1981,6 @@ var hexch = text.substr(i, 3); if ( hexch.match(/^%[a-f0-9][a-f0-9]$/i) ) { - console.debug('hexch: ', hexch); result[result.length] = (unescape(hexch)).charCodeAt(0); a = true; i += 2; diff -r f13bb4f21890 -r 4415e50e4e84 includes/clientside/static/login.js --- a/includes/clientside/static/login.js Thu Feb 26 01:06:58 2009 -0500 +++ b/includes/clientside/static/login.js Thu Feb 26 01:07:32 2009 -0500 @@ -347,6 +347,10 @@ break; + default: + eval(setHook('login_set_status')); + break; + case AJAX_STATUS_DESTROY: case null: case undefined: @@ -556,6 +560,8 @@ tr2.appendChild(td2_2); table.appendChild(tr2); + eval(setHook('login_build_form')); + // Field - captcha if ( show_captcha ) { @@ -806,11 +812,11 @@ } } - if ( !username ) + if ( typeof(username) != 'string' ) { var username = document.getElementById('ajax_login_field_username').value; } - if ( !password ) + if ( typeof(password) != 'string' ) { var password = document.getElementById('ajax_login_field_password').value; } @@ -851,10 +857,14 @@ ajaxLoginSetStatus(AJAX_STATUS_LOGGING_IN); // Encrypt the password and username - var userinfo = toJSONString({ + var userinfo = { username: username, password: password - }); + }; + + eval(setHook('login_build_userinfo')); + + userinfo = toJSONString(userinfo); var crypt_key_ba = hexToByteArray(crypt_key); userinfo = stringToByteArray(userinfo); @@ -957,10 +967,18 @@ window.ajaxLoginGetErrorText = function(response) { + if ( !response.error_code.match(/^[a-z0-9]+_[a-z0-9_]+$/) ) + { + return response.error_code; + } switch ( response.error_code ) { default: - return $lang.get('user_err_' + response.error_code); + var ls = $lang.get('user_err_' + response.error_code); + if ( ls == 'user_err_' + response.error_code ) + ls = $lang.get(response.error_code); + + return ls; break; case 'locked_out': if ( response.respawn_info.lockout_info.lockout_policy == 'lockout' ) @@ -1005,13 +1023,18 @@ switch ( response.respawn_info.lockout_info.lockout_policy ) { case 'captcha': - base += $lang.get('user_err_invalid_credentials_lockout', { + base += $lang.get('user_err_invalid_credentials_lockout_captcha', { fails: response.respawn_info.lockout_info.lockout_fails, lockout_threshold: response.respawn_info.lockout_info.lockout_threshold, lockout_duration: response.respawn_info.lockout_info.lockout_duration }); break; case 'lockout': + base += $lang.get('user_err_invalid_credentials_lockout', { + fails: response.respawn_info.lockout_info.lockout_fails, + lockout_threshold: response.respawn_info.lockout_info.lockout_threshold, + lockout_duration: response.respawn_info.lockout_info.lockout_duration + }); break; } } diff -r f13bb4f21890 -r 4415e50e4e84 includes/paths.php --- a/includes/paths.php Thu Feb 26 01:06:58 2009 -0500 +++ b/includes/paths.php Thu Feb 26 01:07:32 2009 -0500 @@ -476,12 +476,12 @@ foreach($k as $key) { $i++; - $name = ( preg_match('/^[a-z0-9_]+$/', $key) ) ? $lang->get($key) : $key; + $name = $lang->get($key); $ret .= "['".$name."', 'javascript:trees[0].toggle($i)', \n"; foreach($this->admin_tree[$key] as $c) { $i++; - $name = ( preg_match('/^[a-z0-9_]+$/', $key) ) ? $lang->get($c['name']) : $c['name']; + $name = $lang->get($c['name']); if ( $c['icon'] && $c['icon'] != cdnPath . '/images/spacer.gif' ) { if ( is_array($c['icon']) ) diff -r f13bb4f21890 -r 4415e50e4e84 includes/sessions.php --- a/includes/sessions.php Thu Feb 26 01:06:58 2009 -0500 +++ b/includes/sessions.php Thu Feb 26 01:07:32 2009 -0500 @@ -658,20 +658,10 @@ if ( !defined('IN_ENANO_INSTALL') ) { - // Lockout stuff - $threshold = ( $_ = getConfig('lockout_threshold') ) ? intval($_) : 5; - $duration = ( $_ = getConfig('lockout_duration') ) ? intval($_) : 15; - // convert to minutes - $duration = $duration * 60; - $policy = ( $x = getConfig('lockout_policy') && in_array(getConfig('lockout_policy'), array('lockout', 'disable', 'captcha')) ) ? getConfig('lockout_policy') : 'lockout'; - - $timestamp_cutoff = time() - $duration; - $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']); - $q = $this->sql('SELECT timestamp FROM '.table_prefix.'lockout WHERE timestamp > ' . $timestamp_cutoff . ' AND ipaddr = \'' . $ipaddr . '\' ORDER BY timestamp DESC;'); - $fails = $db->numrows(); + $locked_out = $this->get_lockout_info($lockout_data); $captcha_good = false; - if ( $policy == 'captcha' && $captcha_hash && $captcha_code ) + if ( $lockout_data['lockout_policy'] == 'captcha' && $captcha_hash && $captcha_code ) { // policy is captcha -- check if it's correct, and if so, bypass lockout check $real_code = $this->get_captcha($captcha_hash); @@ -682,7 +672,7 @@ } if ( $policy != 'disable' && !$captcha_good ) { - if ( $fails >= $threshold ) + if ( $lockout_data['lockout_fails'] >= $lockout_data['lockout_threshold'] ) { // ooh boy, somebody's in trouble ;-) $row = $db->fetchrow(); @@ -690,12 +680,12 @@ return array( 'success' => false, 'error' => 'locked_out', - 'lockout_threshold' => $threshold, - 'lockout_duration' => ( $duration / 60 ), - 'lockout_fails' => $fails, - 'lockout_policy' => $policy, - 'time_rem' => ( $duration / 60 ) - round( ( time() - $row['timestamp'] ) / 60 ), - 'lockout_last_time' => $row['timestamp'] + 'lockout_threshold' => $lockout_data['lockout_threshold'], + 'lockout_duration' => ( $lockout_data['lockout_duration'] ), + 'lockout_fails' => $lockout_data['lockout_fails'], + 'lockout_policy' => $lockout_data['lockout_policy'], + 'time_rem' => $lockout_data['lockout_time_rem'], + 'lockout_last_time' => $lockout_data['lockout_last_time'] ); } } @@ -729,19 +719,19 @@ . '\''.$db->escape($_SERVER['REMOTE_ADDR']).'\')'); // Do we also need to increment the lockout countdown? - if ( @$policy != 'disable' && !defined('IN_ENANO_INSTALL') ) + if ( @$lockout_data['lockout_policy'] != 'disable' && !defined('IN_ENANO_INSTALL') ) { $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']); // increment fail count $this->sql('INSERT INTO '.table_prefix.'lockout(ipaddr, timestamp, action) VALUES(\'' . $ipaddr . '\', ' . time() . ', \'credential\');'); - $fails++; + $lockout_data['lockout_fails']++; return array( 'success' => false, - 'error' => ( $fails >= $threshold ) ? 'locked_out' : 'invalid_credentials', - 'lockout_threshold' => $threshold, - 'lockout_duration' => ( $duration / 60 ), - 'lockout_fails' => $fails, - 'lockout_policy' => $policy + 'error' => ( $lockout_data['lockout_fails'] >= $lockout_data['lockout_threshold'] ) ? 'locked_out' : 'invalid_credentials', + 'lockout_threshold' => $lockout_data['lockout_threshold'], + 'lockout_duration' => ( $lockout_data['lockout_duration'] ), + 'lockout_fails' => $lockout_data['lockout_fails'], + 'lockout_policy' => $lockout_data['lockout_policy'] ); } @@ -851,19 +841,19 @@ $this->sql('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,date_string,author,edit_summary) VALUES(\'security\', \'auth_bad\', '.time().', \''.enano_date('d M Y h:i a').'\', \''.$db->escape($username).'\', \''.$db->escape($_SERVER['REMOTE_ADDR']).'\')'); // Do we also need to increment the lockout countdown? - if ( !defined('IN_ENANO_INSTALL') && $policy != 'disable' ) + if ( !defined('IN_ENANO_INSTALL') && $lockout_data['lockout_policy'] != 'disable' ) { $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']); // increment fail count $this->sql('INSERT INTO '.table_prefix.'lockout(ipaddr, timestamp, action) VALUES(\'' . $ipaddr . '\', ' . time() . ', \'credential\');'); - $fails++; + $lockout_data['lockout_fails']++; return array( 'success' => false, - 'error' => ( $fails >= $threshold ) ? 'locked_out' : 'invalid_credentials', - 'lockout_threshold' => $threshold, - 'lockout_duration' => ( $duration / 60 ), - 'lockout_fails' => $fails, - 'lockout_policy' => $policy + 'error' => ( $lockout_data['lockout_fails'] >= $lockout_data['lockout_threshold'] ) ? 'locked_out' : 'invalid_credentials', + 'lockout_threshold' => $lockout_data['lockout_threshold'], + 'lockout_duration' => ( $lockout_data['lockout_duration'] ), + 'lockout_fails' => $lockout_data['lockout_fails'], + 'lockout_policy' => $lockout_data['lockout_policy'] ); } @@ -1009,6 +999,49 @@ } /** + * Tells us if we're locked out from logging in or not. + * @param reference will be filled with information regarding in-progress lockout + * @return bool True if locked out, false otherwise + */ + + function get_lockout_info(&$lockdata) + { + global $db; + + // this has to be initialized to hide warnings + $lockdata = null; + + // Query database for lockout info + $locked_out = false; + $threshold = ( $_ = getConfig('lockout_threshold') ) ? intval($_) : 5; + $duration = ( $_ = getConfig('lockout_duration') ) ? intval($_) : 15; + // convert to minutes + $duration = $duration * 60; + $policy = ( $x = getConfig('lockout_policy') && in_array(getConfig('lockout_policy'), array('lockout', 'disable', 'captcha')) ) ? getConfig('lockout_policy') : 'lockout'; + if ( $policy != 'disable' ) + { + $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']); + $timestamp_cutoff = time() - $duration; + $q = $this->sql('SELECT timestamp FROM ' . table_prefix . 'lockout WHERE timestamp > ' . $timestamp_cutoff . ' AND ipaddr = \'' . $ipaddr . '\' ORDER BY timestamp DESC;'); + $fails = $db->numrows(); + $row = $db->fetchrow(); + $locked_out = ( $fails >= $threshold ); + $lockdata = array( + 'locked_out' => $locked_out, + 'lockout_threshold' => $threshold, + 'lockout_duration' => ( $duration / 60 ), + 'lockout_fails' => $fails, + 'lockout_policy' => $policy, + 'lockout_last_time' => $row['timestamp'], + 'time_rem' => ( $duration / 60 ) - round( ( time() - $row['timestamp'] ) / 60 ), + 'captcha' => '' + ); + $db->free_result(); + } + return $locked_out; + } + + /** * Creates/restores a guest session * @todo implement real session management for guests */ @@ -1038,7 +1071,7 @@ @setlocale(LC_ALL, $lang->lang_code); } // make a CSRF token - $this->csrf_token = sha1($_SERVER['REMOTE_ADDR'] . sha1($this->private_key)); + $this->csrf_token = hmac_sha1($_SERVER['REMOTE_ADDR'], sha1($this->private_key)); } /** @@ -3821,34 +3854,7 @@ $this->start(); - // Query database for lockout info - $locked_out = false; - // are we locked out? - $threshold = ( $_ = getConfig('lockout_threshold') ) ? intval($_) : 5; - $duration = ( $_ = getConfig('lockout_duration') ) ? intval($_) : 15; - // convert to minutes - $duration = $duration * 60; - $policy = ( $x = getConfig('lockout_policy') && in_array(getConfig('lockout_policy'), array('lockout', 'disable', 'captcha')) ) ? getConfig('lockout_policy') : 'lockout'; - if ( $policy != 'disable' ) - { - $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']); - $timestamp_cutoff = time() - $duration; - $q = $this->sql('SELECT timestamp FROM '.table_prefix.'lockout WHERE timestamp > ' . $timestamp_cutoff . ' AND ipaddr = \'' . $ipaddr . '\' ORDER BY timestamp DESC;'); - $fails = $db->numrows(); - $row = $db->fetchrow(); - $locked_out = ( $fails >= $threshold ); - $lockdata = array( - 'locked_out' => $locked_out, - 'lockout_threshold' => $threshold, - 'lockout_duration' => ( $duration / 60 ), - 'lockout_fails' => $fails, - 'lockout_policy' => $policy, - 'lockout_last_time' => $row['timestamp'], - 'time_rem' => ( $duration / 60 ) - round( ( time() - $row['timestamp'] ) / 60 ), - 'captcha' => '' - ); - $db->free_result(); - } + $locked_out = $this->get_lockout_info($lockdata); $response = array('mode' => 'build_box'); $response['allow_diffiehellman'] = $dh_supported; @@ -3862,7 +3868,7 @@ $response['locked_out'] = $locked_out; $response['lockout_info'] = $lockdata; - if ( $policy == 'captcha' && $locked_out ) + if ( $lockdata['lockout_policy'] == 'captcha' && $locked_out ) { $response['lockout_info']['captcha'] = $this->make_captcha(); } @@ -3992,6 +3998,41 @@ $username =& $userinfo['username']; $password =& $userinfo['password']; + // At this point if any extra info was injected into the login data packet, we need to let plugins process it + /** + * Called upon processing an incoming login request. If you added anything to the userinfo object during the jshook + * login_build_userinfo, that will be in the $userinfo array here. Expected return values are: true if your plugin has + * not only succeeded but ALSO issued a session key (bypass the whole Enano builtin login process) and an associative array + * with "mode" set to "error" and an error string in "error" to send an error back to the client. Any return value other + * than these will be ignored. + * @hook login_process_userdata_json + */ + + $code = $plugins->setHook('login_process_userdata_json'); + foreach ( $code as $cmd ) + { + $result = eval($cmd); + if ( $result === true ) + { + return array( + 'mode' => 'login_success', + 'key' => ( $this->sid_super ) ? $this->sid_super : false + ); + } + else if ( is_array($result) ) + { + if ( isset($result['mode']) && $result['mode'] === 'error' && isset($result['error']) ) + { + return array( + 'mode' => 'login_failure', + 'error_code' => $result['error'], + // Use this to provide a way to respawn the login box + 'respawn_info' => $this->process_login_request(array('mode' => 'getkey')) + ); + } + } + } + // If we're logging in with a temp password, attach to the login_password_reset hook to send our JSON response // A bit hackish since it just dies with the response :-( $plugins->attachHook('login_password_reset', '$this->process_login_request(array(\'mode\' => \'respond_password_reset\', \'user_id\' => $row[\'user_id\'], \'temp_password\' => $this->pk_encrypt($password)));'); diff -r f13bb4f21890 -r 4415e50e4e84 includes/template.php --- a/includes/template.php Thu Feb 26 01:06:58 2009 -0500 +++ b/includes/template.php Thu Feb 26 01:07:32 2009 -0500 @@ -1499,7 +1499,7 @@ } // Full pathname of template file - $tpl_file_fullpath = ENANO_ROOT . '/themes/' . $this->theme . '/' . $file; + $tpl_file_fullpath = ( strstr($file, '/') ) ? $file : ENANO_ROOT . '/themes/' . $this->theme . '/' . $file; // Make sure the template even exists if ( !is_file($tpl_file_fullpath) ) diff -r f13bb4f21890 -r 4415e50e4e84 plugins/SpecialUserFuncs.php --- a/plugins/SpecialUserFuncs.php Thu Feb 26 01:06:58 2009 -0500 +++ b/plugins/SpecialUserFuncs.php Thu Feb 26 01:07:32 2009 -0500 @@ -264,6 +264,9 @@ $errstring = $lang->get('user_err_locked_out', array('plural' => $s, 'captcha_blurb' => $captcha_string, 'time_rem' => $time_rem)); break; + default: + $errstring = $lang->get($errstring); + break; } echo '
'.$errstring.'
'; } @@ -343,6 +346,11 @@ } ?> setHook('login_form_html'); + foreach ( $code as $cmd ) + { + eval($cmd); + } if ( $level <= USER_LEVEL_MEMBER ) { // "remember me" switch @@ -504,7 +512,53 @@ return false; } - $result = $session->login_without_crypto($_POST['username'], $password, false, intval($_POST['auth_level']), $captcha_hash, $captcha_code, isset($_POST['remember'])); + // These are to allow auth plugins to work universally between JSON and HTML login forms + $userinfo =& $_POST; + $req = array( + 'level' => intval($_POST['auth_level']), + 'remember' => isset($_POST['remember']) + ); + + // At this point if any extra fields were injected into the login form, we need to let plugins process it + + /** + * Called upon processing an incoming login request from the plain HTML login form.. If you added anything to the form, + * that will be in the $userinfo array here and on $_POST. Expected return values are: true if your plugin has + * not only succeeded but ALSO issued a session key (bypass the whole Enano builtin login process) and an associative array + * with "mode" set to "error" and an error string in "error" to send an error back to the client. Any return value other + * than these will be ignored. + * @hook login_process_userdata_json + */ + + $skip_normal_login = false; + + $code = $plugins->setHook('login_process_userdata_json'); + foreach ( $code as $cmd ) + { + $result = eval($cmd); + if ( $result === true ) + { + $skip_normal_login = true; + $result = array('success' => true); + break; + } + else if ( is_array($result) ) + { + if ( isset($result['mode']) && $result['mode'] === 'error' && isset($result['error']) ) + { + $__login_status = array( + 'mode' => 'error', + 'error' => $result['error'] + ); + return false; + } + } + } + + if ( !$skip_normal_login ) + { + $result = $session->login_without_crypto($_POST['username'], $password, false, intval($_POST['auth_level']), $captcha_hash, $captcha_code, isset($_POST['remember'])); + } if($result['success']) {