Added possibility for auth plugins, which can log a user in using non-standard authentication methods.
--- 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;
--- 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;
}
}
--- 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']) )
--- 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)));');
--- 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) )
--- 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 '<div class="error-box-mini">'.$errstring.'</div>';
}
@@ -343,6 +346,11 @@
}
?>
<?php
+ $code = $plugins->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'])
{