Added possibility for auth plugins, which can log a user in using non-standard authentication methods.
1.1 --- a/includes/clientside/static/crypto.js Thu Feb 26 01:06:58 2009 -0500
1.2 +++ b/includes/clientside/static/crypto.js Thu Feb 26 01:07:32 2009 -0500
1.3 @@ -1981,7 +1981,6 @@
1.4 var hexch = text.substr(i, 3);
1.5 if ( hexch.match(/^%[a-f0-9][a-f0-9]$/i) )
1.6 {
1.7 - console.debug('hexch: ', hexch);
1.8 result[result.length] = (unescape(hexch)).charCodeAt(0);
1.9 a = true;
1.10 i += 2;
2.1 --- a/includes/clientside/static/login.js Thu Feb 26 01:06:58 2009 -0500
2.2 +++ b/includes/clientside/static/login.js Thu Feb 26 01:07:32 2009 -0500
2.3 @@ -347,6 +347,10 @@
2.4
2.5 break;
2.6
2.7 + default:
2.8 + eval(setHook('login_set_status'));
2.9 + break;
2.10 +
2.11 case AJAX_STATUS_DESTROY:
2.12 case null:
2.13 case undefined:
2.14 @@ -556,6 +560,8 @@
2.15 tr2.appendChild(td2_2);
2.16 table.appendChild(tr2);
2.17
2.18 + eval(setHook('login_build_form'));
2.19 +
2.20 // Field - captcha
2.21 if ( show_captcha )
2.22 {
2.23 @@ -806,11 +812,11 @@
2.24 }
2.25 }
2.26
2.27 - if ( !username )
2.28 + if ( typeof(username) != 'string' )
2.29 {
2.30 var username = document.getElementById('ajax_login_field_username').value;
2.31 }
2.32 - if ( !password )
2.33 + if ( typeof(password) != 'string' )
2.34 {
2.35 var password = document.getElementById('ajax_login_field_password').value;
2.36 }
2.37 @@ -851,10 +857,14 @@
2.38 ajaxLoginSetStatus(AJAX_STATUS_LOGGING_IN);
2.39
2.40 // Encrypt the password and username
2.41 - var userinfo = toJSONString({
2.42 + var userinfo = {
2.43 username: username,
2.44 password: password
2.45 - });
2.46 + };
2.47 +
2.48 + eval(setHook('login_build_userinfo'));
2.49 +
2.50 + userinfo = toJSONString(userinfo);
2.51 var crypt_key_ba = hexToByteArray(crypt_key);
2.52 userinfo = stringToByteArray(userinfo);
2.53
2.54 @@ -957,10 +967,18 @@
2.55
2.56 window.ajaxLoginGetErrorText = function(response)
2.57 {
2.58 + if ( !response.error_code.match(/^[a-z0-9]+_[a-z0-9_]+$/) )
2.59 + {
2.60 + return response.error_code;
2.61 + }
2.62 switch ( response.error_code )
2.63 {
2.64 default:
2.65 - return $lang.get('user_err_' + response.error_code);
2.66 + var ls = $lang.get('user_err_' + response.error_code);
2.67 + if ( ls == 'user_err_' + response.error_code )
2.68 + ls = $lang.get(response.error_code);
2.69 +
2.70 + return ls;
2.71 break;
2.72 case 'locked_out':
2.73 if ( response.respawn_info.lockout_info.lockout_policy == 'lockout' )
2.74 @@ -1005,13 +1023,18 @@
2.75 switch ( response.respawn_info.lockout_info.lockout_policy )
2.76 {
2.77 case 'captcha':
2.78 - base += $lang.get('user_err_invalid_credentials_lockout', {
2.79 + base += $lang.get('user_err_invalid_credentials_lockout_captcha', {
2.80 fails: response.respawn_info.lockout_info.lockout_fails,
2.81 lockout_threshold: response.respawn_info.lockout_info.lockout_threshold,
2.82 lockout_duration: response.respawn_info.lockout_info.lockout_duration
2.83 });
2.84 break;
2.85 case 'lockout':
2.86 + base += $lang.get('user_err_invalid_credentials_lockout', {
2.87 + fails: response.respawn_info.lockout_info.lockout_fails,
2.88 + lockout_threshold: response.respawn_info.lockout_info.lockout_threshold,
2.89 + lockout_duration: response.respawn_info.lockout_info.lockout_duration
2.90 + });
2.91 break;
2.92 }
2.93 }
3.1 --- a/includes/paths.php Thu Feb 26 01:06:58 2009 -0500
3.2 +++ b/includes/paths.php Thu Feb 26 01:07:32 2009 -0500
3.3 @@ -476,12 +476,12 @@
3.4 foreach($k as $key)
3.5 {
3.6 $i++;
3.7 - $name = ( preg_match('/^[a-z0-9_]+$/', $key) ) ? $lang->get($key) : $key;
3.8 + $name = $lang->get($key);
3.9 $ret .= "['".$name."', 'javascript:trees[0].toggle($i)', \n";
3.10 foreach($this->admin_tree[$key] as $c)
3.11 {
3.12 $i++;
3.13 - $name = ( preg_match('/^[a-z0-9_]+$/', $key) ) ? $lang->get($c['name']) : $c['name'];
3.14 + $name = $lang->get($c['name']);
3.15 if ( $c['icon'] && $c['icon'] != cdnPath . '/images/spacer.gif' )
3.16 {
3.17 if ( is_array($c['icon']) )
4.1 --- a/includes/sessions.php Thu Feb 26 01:06:58 2009 -0500
4.2 +++ b/includes/sessions.php Thu Feb 26 01:07:32 2009 -0500
4.3 @@ -658,20 +658,10 @@
4.4
4.5 if ( !defined('IN_ENANO_INSTALL') )
4.6 {
4.7 - // Lockout stuff
4.8 - $threshold = ( $_ = getConfig('lockout_threshold') ) ? intval($_) : 5;
4.9 - $duration = ( $_ = getConfig('lockout_duration') ) ? intval($_) : 15;
4.10 - // convert to minutes
4.11 - $duration = $duration * 60;
4.12 - $policy = ( $x = getConfig('lockout_policy') && in_array(getConfig('lockout_policy'), array('lockout', 'disable', 'captcha')) ) ? getConfig('lockout_policy') : 'lockout';
4.13 -
4.14 - $timestamp_cutoff = time() - $duration;
4.15 - $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']);
4.16 - $q = $this->sql('SELECT timestamp FROM '.table_prefix.'lockout WHERE timestamp > ' . $timestamp_cutoff . ' AND ipaddr = \'' . $ipaddr . '\' ORDER BY timestamp DESC;');
4.17 - $fails = $db->numrows();
4.18 + $locked_out = $this->get_lockout_info($lockout_data);
4.19
4.20 $captcha_good = false;
4.21 - if ( $policy == 'captcha' && $captcha_hash && $captcha_code )
4.22 + if ( $lockout_data['lockout_policy'] == 'captcha' && $captcha_hash && $captcha_code )
4.23 {
4.24 // policy is captcha -- check if it's correct, and if so, bypass lockout check
4.25 $real_code = $this->get_captcha($captcha_hash);
4.26 @@ -682,7 +672,7 @@
4.27 }
4.28 if ( $policy != 'disable' && !$captcha_good )
4.29 {
4.30 - if ( $fails >= $threshold )
4.31 + if ( $lockout_data['lockout_fails'] >= $lockout_data['lockout_threshold'] )
4.32 {
4.33 // ooh boy, somebody's in trouble ;-)
4.34 $row = $db->fetchrow();
4.35 @@ -690,12 +680,12 @@
4.36 return array(
4.37 'success' => false,
4.38 'error' => 'locked_out',
4.39 - 'lockout_threshold' => $threshold,
4.40 - 'lockout_duration' => ( $duration / 60 ),
4.41 - 'lockout_fails' => $fails,
4.42 - 'lockout_policy' => $policy,
4.43 - 'time_rem' => ( $duration / 60 ) - round( ( time() - $row['timestamp'] ) / 60 ),
4.44 - 'lockout_last_time' => $row['timestamp']
4.45 + 'lockout_threshold' => $lockout_data['lockout_threshold'],
4.46 + 'lockout_duration' => ( $lockout_data['lockout_duration'] ),
4.47 + 'lockout_fails' => $lockout_data['lockout_fails'],
4.48 + 'lockout_policy' => $lockout_data['lockout_policy'],
4.49 + 'time_rem' => $lockout_data['lockout_time_rem'],
4.50 + 'lockout_last_time' => $lockout_data['lockout_last_time']
4.51 );
4.52 }
4.53 }
4.54 @@ -729,19 +719,19 @@
4.55 . '\''.$db->escape($_SERVER['REMOTE_ADDR']).'\')');
4.56
4.57 // Do we also need to increment the lockout countdown?
4.58 - if ( @$policy != 'disable' && !defined('IN_ENANO_INSTALL') )
4.59 + if ( @$lockout_data['lockout_policy'] != 'disable' && !defined('IN_ENANO_INSTALL') )
4.60 {
4.61 $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']);
4.62 // increment fail count
4.63 $this->sql('INSERT INTO '.table_prefix.'lockout(ipaddr, timestamp, action) VALUES(\'' . $ipaddr . '\', ' . time() . ', \'credential\');');
4.64 - $fails++;
4.65 + $lockout_data['lockout_fails']++;
4.66 return array(
4.67 'success' => false,
4.68 - 'error' => ( $fails >= $threshold ) ? 'locked_out' : 'invalid_credentials',
4.69 - 'lockout_threshold' => $threshold,
4.70 - 'lockout_duration' => ( $duration / 60 ),
4.71 - 'lockout_fails' => $fails,
4.72 - 'lockout_policy' => $policy
4.73 + 'error' => ( $lockout_data['lockout_fails'] >= $lockout_data['lockout_threshold'] ) ? 'locked_out' : 'invalid_credentials',
4.74 + 'lockout_threshold' => $lockout_data['lockout_threshold'],
4.75 + 'lockout_duration' => ( $lockout_data['lockout_duration'] ),
4.76 + 'lockout_fails' => $lockout_data['lockout_fails'],
4.77 + 'lockout_policy' => $lockout_data['lockout_policy']
4.78 );
4.79 }
4.80
4.81 @@ -851,19 +841,19 @@
4.82 $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']).'\')');
4.83
4.84 // Do we also need to increment the lockout countdown?
4.85 - if ( !defined('IN_ENANO_INSTALL') && $policy != 'disable' )
4.86 + if ( !defined('IN_ENANO_INSTALL') && $lockout_data['lockout_policy'] != 'disable' )
4.87 {
4.88 $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']);
4.89 // increment fail count
4.90 $this->sql('INSERT INTO '.table_prefix.'lockout(ipaddr, timestamp, action) VALUES(\'' . $ipaddr . '\', ' . time() . ', \'credential\');');
4.91 - $fails++;
4.92 + $lockout_data['lockout_fails']++;
4.93 return array(
4.94 'success' => false,
4.95 - 'error' => ( $fails >= $threshold ) ? 'locked_out' : 'invalid_credentials',
4.96 - 'lockout_threshold' => $threshold,
4.97 - 'lockout_duration' => ( $duration / 60 ),
4.98 - 'lockout_fails' => $fails,
4.99 - 'lockout_policy' => $policy
4.100 + 'error' => ( $lockout_data['lockout_fails'] >= $lockout_data['lockout_threshold'] ) ? 'locked_out' : 'invalid_credentials',
4.101 + 'lockout_threshold' => $lockout_data['lockout_threshold'],
4.102 + 'lockout_duration' => ( $lockout_data['lockout_duration'] ),
4.103 + 'lockout_fails' => $lockout_data['lockout_fails'],
4.104 + 'lockout_policy' => $lockout_data['lockout_policy']
4.105 );
4.106 }
4.107
4.108 @@ -1009,6 +999,49 @@
4.109 }
4.110
4.111 /**
4.112 + * Tells us if we're locked out from logging in or not.
4.113 + * @param reference will be filled with information regarding in-progress lockout
4.114 + * @return bool True if locked out, false otherwise
4.115 + */
4.116 +
4.117 + function get_lockout_info(&$lockdata)
4.118 + {
4.119 + global $db;
4.120 +
4.121 + // this has to be initialized to hide warnings
4.122 + $lockdata = null;
4.123 +
4.124 + // Query database for lockout info
4.125 + $locked_out = false;
4.126 + $threshold = ( $_ = getConfig('lockout_threshold') ) ? intval($_) : 5;
4.127 + $duration = ( $_ = getConfig('lockout_duration') ) ? intval($_) : 15;
4.128 + // convert to minutes
4.129 + $duration = $duration * 60;
4.130 + $policy = ( $x = getConfig('lockout_policy') && in_array(getConfig('lockout_policy'), array('lockout', 'disable', 'captcha')) ) ? getConfig('lockout_policy') : 'lockout';
4.131 + if ( $policy != 'disable' )
4.132 + {
4.133 + $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']);
4.134 + $timestamp_cutoff = time() - $duration;
4.135 + $q = $this->sql('SELECT timestamp FROM ' . table_prefix . 'lockout WHERE timestamp > ' . $timestamp_cutoff . ' AND ipaddr = \'' . $ipaddr . '\' ORDER BY timestamp DESC;');
4.136 + $fails = $db->numrows();
4.137 + $row = $db->fetchrow();
4.138 + $locked_out = ( $fails >= $threshold );
4.139 + $lockdata = array(
4.140 + 'locked_out' => $locked_out,
4.141 + 'lockout_threshold' => $threshold,
4.142 + 'lockout_duration' => ( $duration / 60 ),
4.143 + 'lockout_fails' => $fails,
4.144 + 'lockout_policy' => $policy,
4.145 + 'lockout_last_time' => $row['timestamp'],
4.146 + 'time_rem' => ( $duration / 60 ) - round( ( time() - $row['timestamp'] ) / 60 ),
4.147 + 'captcha' => ''
4.148 + );
4.149 + $db->free_result();
4.150 + }
4.151 + return $locked_out;
4.152 + }
4.153 +
4.154 + /**
4.155 * Creates/restores a guest session
4.156 * @todo implement real session management for guests
4.157 */
4.158 @@ -1038,7 +1071,7 @@
4.159 @setlocale(LC_ALL, $lang->lang_code);
4.160 }
4.161 // make a CSRF token
4.162 - $this->csrf_token = sha1($_SERVER['REMOTE_ADDR'] . sha1($this->private_key));
4.163 + $this->csrf_token = hmac_sha1($_SERVER['REMOTE_ADDR'], sha1($this->private_key));
4.164 }
4.165
4.166 /**
4.167 @@ -3821,34 +3854,7 @@
4.168
4.169 $this->start();
4.170
4.171 - // Query database for lockout info
4.172 - $locked_out = false;
4.173 - // are we locked out?
4.174 - $threshold = ( $_ = getConfig('lockout_threshold') ) ? intval($_) : 5;
4.175 - $duration = ( $_ = getConfig('lockout_duration') ) ? intval($_) : 15;
4.176 - // convert to minutes
4.177 - $duration = $duration * 60;
4.178 - $policy = ( $x = getConfig('lockout_policy') && in_array(getConfig('lockout_policy'), array('lockout', 'disable', 'captcha')) ) ? getConfig('lockout_policy') : 'lockout';
4.179 - if ( $policy != 'disable' )
4.180 - {
4.181 - $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']);
4.182 - $timestamp_cutoff = time() - $duration;
4.183 - $q = $this->sql('SELECT timestamp FROM '.table_prefix.'lockout WHERE timestamp > ' . $timestamp_cutoff . ' AND ipaddr = \'' . $ipaddr . '\' ORDER BY timestamp DESC;');
4.184 - $fails = $db->numrows();
4.185 - $row = $db->fetchrow();
4.186 - $locked_out = ( $fails >= $threshold );
4.187 - $lockdata = array(
4.188 - 'locked_out' => $locked_out,
4.189 - 'lockout_threshold' => $threshold,
4.190 - 'lockout_duration' => ( $duration / 60 ),
4.191 - 'lockout_fails' => $fails,
4.192 - 'lockout_policy' => $policy,
4.193 - 'lockout_last_time' => $row['timestamp'],
4.194 - 'time_rem' => ( $duration / 60 ) - round( ( time() - $row['timestamp'] ) / 60 ),
4.195 - 'captcha' => ''
4.196 - );
4.197 - $db->free_result();
4.198 - }
4.199 + $locked_out = $this->get_lockout_info($lockdata);
4.200
4.201 $response = array('mode' => 'build_box');
4.202 $response['allow_diffiehellman'] = $dh_supported;
4.203 @@ -3862,7 +3868,7 @@
4.204 $response['locked_out'] = $locked_out;
4.205
4.206 $response['lockout_info'] = $lockdata;
4.207 - if ( $policy == 'captcha' && $locked_out )
4.208 + if ( $lockdata['lockout_policy'] == 'captcha' && $locked_out )
4.209 {
4.210 $response['lockout_info']['captcha'] = $this->make_captcha();
4.211 }
4.212 @@ -3992,6 +3998,41 @@
4.213 $username =& $userinfo['username'];
4.214 $password =& $userinfo['password'];
4.215
4.216 + // At this point if any extra info was injected into the login data packet, we need to let plugins process it
4.217 + /**
4.218 + * Called upon processing an incoming login request. If you added anything to the userinfo object during the jshook
4.219 + * login_build_userinfo, that will be in the $userinfo array here. Expected return values are: true if your plugin has
4.220 + * not only succeeded but ALSO issued a session key (bypass the whole Enano builtin login process) and an associative array
4.221 + * with "mode" set to "error" and an error string in "error" to send an error back to the client. Any return value other
4.222 + * than these will be ignored.
4.223 + * @hook login_process_userdata_json
4.224 + */
4.225 +
4.226 + $code = $plugins->setHook('login_process_userdata_json');
4.227 + foreach ( $code as $cmd )
4.228 + {
4.229 + $result = eval($cmd);
4.230 + if ( $result === true )
4.231 + {
4.232 + return array(
4.233 + 'mode' => 'login_success',
4.234 + 'key' => ( $this->sid_super ) ? $this->sid_super : false
4.235 + );
4.236 + }
4.237 + else if ( is_array($result) )
4.238 + {
4.239 + if ( isset($result['mode']) && $result['mode'] === 'error' && isset($result['error']) )
4.240 + {
4.241 + return array(
4.242 + 'mode' => 'login_failure',
4.243 + 'error_code' => $result['error'],
4.244 + // Use this to provide a way to respawn the login box
4.245 + 'respawn_info' => $this->process_login_request(array('mode' => 'getkey'))
4.246 + );
4.247 + }
4.248 + }
4.249 + }
4.250 +
4.251 // If we're logging in with a temp password, attach to the login_password_reset hook to send our JSON response
4.252 // A bit hackish since it just dies with the response :-(
4.253 $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)));');
5.1 --- a/includes/template.php Thu Feb 26 01:06:58 2009 -0500
5.2 +++ b/includes/template.php Thu Feb 26 01:07:32 2009 -0500
5.3 @@ -1499,7 +1499,7 @@
5.4 }
5.5
5.6 // Full pathname of template file
5.7 - $tpl_file_fullpath = ENANO_ROOT . '/themes/' . $this->theme . '/' . $file;
5.8 + $tpl_file_fullpath = ( strstr($file, '/') ) ? $file : ENANO_ROOT . '/themes/' . $this->theme . '/' . $file;
5.9
5.10 // Make sure the template even exists
5.11 if ( !is_file($tpl_file_fullpath) )
6.1 --- a/plugins/SpecialUserFuncs.php Thu Feb 26 01:06:58 2009 -0500
6.2 +++ b/plugins/SpecialUserFuncs.php Thu Feb 26 01:07:32 2009 -0500
6.3 @@ -264,6 +264,9 @@
6.4 $errstring = $lang->get('user_err_locked_out', array('plural' => $s, 'captcha_blurb' => $captcha_string, 'time_rem' => $time_rem));
6.5
6.6 break;
6.7 + default:
6.8 + $errstring = $lang->get($errstring);
6.9 + break;
6.10 }
6.11 echo '<div class="error-box-mini">'.$errstring.'</div>';
6.12 }
6.13 @@ -343,6 +346,11 @@
6.14 }
6.15 ?>
6.16 <?php
6.17 + $code = $plugins->setHook('login_form_html');
6.18 + foreach ( $code as $cmd )
6.19 + {
6.20 + eval($cmd);
6.21 + }
6.22 if ( $level <= USER_LEVEL_MEMBER )
6.23 {
6.24 // "remember me" switch
6.25 @@ -504,7 +512,53 @@
6.26 return false;
6.27 }
6.28
6.29 - $result = $session->login_without_crypto($_POST['username'], $password, false, intval($_POST['auth_level']), $captcha_hash, $captcha_code, isset($_POST['remember']));
6.30 + // These are to allow auth plugins to work universally between JSON and HTML login forms
6.31 + $userinfo =& $_POST;
6.32 + $req = array(
6.33 + 'level' => intval($_POST['auth_level']),
6.34 + 'remember' => isset($_POST['remember'])
6.35 + );
6.36 +
6.37 + // At this point if any extra fields were injected into the login form, we need to let plugins process it
6.38 +
6.39 + /**
6.40 + * Called upon processing an incoming login request from the plain HTML login form.. If you added anything to the form,
6.41 + * that will be in the $userinfo array here and on $_POST. Expected return values are: true if your plugin has
6.42 + * not only succeeded but ALSO issued a session key (bypass the whole Enano builtin login process) and an associative array
6.43 + * with "mode" set to "error" and an error string in "error" to send an error back to the client. Any return value other
6.44 + * than these will be ignored.
6.45 + * @hook login_process_userdata_json
6.46 + */
6.47 +
6.48 + $skip_normal_login = false;
6.49 +
6.50 + $code = $plugins->setHook('login_process_userdata_json');
6.51 + foreach ( $code as $cmd )
6.52 + {
6.53 + $result = eval($cmd);
6.54 + if ( $result === true )
6.55 + {
6.56 + $skip_normal_login = true;
6.57 + $result = array('success' => true);
6.58 + break;
6.59 + }
6.60 + else if ( is_array($result) )
6.61 + {
6.62 + if ( isset($result['mode']) && $result['mode'] === 'error' && isset($result['error']) )
6.63 + {
6.64 + $__login_status = array(
6.65 + 'mode' => 'error',
6.66 + 'error' => $result['error']
6.67 + );
6.68 + return false;
6.69 + }
6.70 + }
6.71 + }
6.72 +
6.73 + if ( !$skip_normal_login )
6.74 + {
6.75 + $result = $session->login_without_crypto($_POST['username'], $password, false, intval($_POST['auth_level']), $captcha_hash, $captcha_code, isset($_POST['remember']));
6.76 + }
6.77
6.78 if($result['success'])
6.79 {