mirror of
https://gitlab.com/davical-project/davical.git
synced 2026-01-27 00:33:34 +00:00
Cache password credentials received, and if they succeed or fail.
This is intended to reduce the load on external authentication sources as most (all?) CalDAV clients use HTTPBasicAuth to authenticate and if an external source is used, every request we receive requires external authentication. This can place a large load on those external sources. Closes #254.
This commit is contained in:
parent
8f7da93a0d
commit
5826f5c3cf
@ -1,3 +1,6 @@
|
||||
2024-04-15 Andrew Ruthven <andrew@etc.gen.nz>
|
||||
* Add caching of user credential success/failure
|
||||
|
||||
2024-04-14 Stonewall Jackson <stonewall@sacredheartsc.com>
|
||||
* Add support for fallback to LDAP password even if Kerberos is
|
||||
active.
|
||||
|
||||
@ -616,6 +616,33 @@ $c->admin_email = 'calendar-admin@example.com';
|
||||
// $_SERVER['REMOTE_ADDR'] = $_SERVER['Client-IP'];
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* *
|
||||
* Authentication Settings *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
/**
|
||||
* Cache credentials, requires memcached to be enabled.
|
||||
*
|
||||
* Default: false
|
||||
*/
|
||||
// $c->auth_cache = false;
|
||||
|
||||
/**
|
||||
* How long to cache credentials which username & password match.
|
||||
*
|
||||
* Default: 15 minutes
|
||||
*/
|
||||
// $c->auth_cache_pass = 15 * 60;
|
||||
|
||||
/**
|
||||
* How long to cache credentials which username & password don't match.
|
||||
*
|
||||
* Default: 15 minutes
|
||||
*/
|
||||
// $c->auth_cache_fail = 15 * 60;
|
||||
|
||||
/***************************************************************************
|
||||
* *
|
||||
* External Authentication Sources *
|
||||
|
||||
@ -165,6 +165,11 @@ $c->readonly_webdav_collections = true; // WebDAV access is readonly
|
||||
// find more instances.
|
||||
$c->rrule_loop_limit = 100;
|
||||
|
||||
// Authentication caching details
|
||||
$c->auth_cache = false; // Default to off
|
||||
$c->auth_cache_pass = 15 * 60; // 15 minutes
|
||||
$c->auth_cache_fail = 15 * 60; // 15 minutes
|
||||
|
||||
// Kind of private configuration values
|
||||
$c->total_query_time = 0;
|
||||
|
||||
|
||||
@ -314,6 +314,34 @@ class HTTPAuthSession {
|
||||
if(isset($c->login_append_domain_if_missing) && $c->login_append_domain_if_missing && !preg_match('/@/',$username))
|
||||
$username.='@'.$c->domain_name;
|
||||
|
||||
$cache_result = $this->CheckCache($username, $password);
|
||||
|
||||
if ($cache_result == 0) {
|
||||
# noop as CheckCache has nothing for this username/password combo
|
||||
# and we need to perform full authentication check.
|
||||
|
||||
} else if ($cache_result == 1) {
|
||||
# cached credentials match and are valid
|
||||
|
||||
if ( $principal = new Principal('username', $username) ) {
|
||||
if ( isset($c->dbg['password']) ) dbg_error_log( "password", ":CheckPassword (cached): Name:%s, Pass:%s, File:%s, Active:%s", $username, $password, $principal->password, ($principal->user_active?'Yes':'No') );
|
||||
if ( $principal->user_active ) {
|
||||
return $principal;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
|
||||
# If we fail to find the Principal, then fall through to full authentication below.
|
||||
dbg_error_log('password', 'Cache check: Failed to find principal after valid cache result, falling through to full authentication check.');
|
||||
}
|
||||
|
||||
} else if ($cache_result == 2) {
|
||||
# cached credentials match and are invalid
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( !isset($c->authenticate_hook) || !isset($c->authenticate_hook['call'])
|
||||
|| !function_exists($c->authenticate_hook['call'])
|
||||
|| (isset($c->authenticate_hook['optional']) && $c->authenticate_hook['optional']) )
|
||||
@ -321,6 +349,7 @@ class HTTPAuthSession {
|
||||
if ( $principal = new Principal('username', $username) ) {
|
||||
if ( isset($c->dbg['password']) ) dbg_error_log( "password", ":CheckPassword: Name:%s, Pass:%s, File:%s, Active:%s", $username, $password, $principal->password, ($principal->user_active?'Yes':'No') );
|
||||
if ( $principal->user_active && session_validate_password( $password, $principal->password ) ) {
|
||||
$this->SetCache($username, $password, 'pass');
|
||||
return $principal;
|
||||
}
|
||||
}
|
||||
@ -339,12 +368,17 @@ class HTTPAuthSession {
|
||||
* - Configuration data will be in $c->authenticate_hook['config'], which might be an array, or whatever is needed.
|
||||
*/
|
||||
$principal = call_user_func( $c->authenticate_hook['call'], $username, $password );
|
||||
if ( $principal !== false && !($principal instanceof Principal) ) {
|
||||
if ( $principal === false ) {
|
||||
$this->SetCache($username, $password, 'fail');
|
||||
} else if (!($principal instanceof Principal) ) {
|
||||
$this->SetCache($username, $password, 'pass');
|
||||
$principal = new Principal('username', $username);
|
||||
}
|
||||
|
||||
return $principal;
|
||||
}
|
||||
|
||||
$this->SetCache($username, $password, 'fail');
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -405,6 +439,96 @@ class HTTPAuthSession {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal function used to check a cache for username/password details. This is
|
||||
* intended to reduce the load on external authentication sources.
|
||||
*/
|
||||
function CheckCache( $username, $password ) {
|
||||
global $c;
|
||||
|
||||
if (! $c->auth_cache) return 0;
|
||||
|
||||
$cache = getCacheInstance();
|
||||
if ($cache->isActive() === false) return 0;
|
||||
|
||||
$cache_ns = 'auth-' . $username;
|
||||
|
||||
$salt = $cache->get($cache_ns, 'salt');
|
||||
|
||||
if (isset($salt)) {
|
||||
$sha1_sent = session_salted_sha1($password, $salt);
|
||||
$cached_credentials = $cache->get($cache_ns, $sha1_sent);
|
||||
|
||||
if (isset($cached_credentials)) {
|
||||
if ($cached_credentials == 'pass') {
|
||||
dbg_error_log('HTTPAuthLogin', 'CheckCache: Cached credentials are good and valid');
|
||||
return 1;
|
||||
|
||||
} else if ($cached_credentials == 'fail') {
|
||||
dbg_error_log('HTTPAuthLogin', 'CheckCache: Cached credentials are good and invalid');
|
||||
return 2;
|
||||
}
|
||||
|
||||
} else {
|
||||
dbg_error_log('HTTPAuthLogin', 'CheckCache: No stored salted password, need to use auth source');
|
||||
}
|
||||
|
||||
} else {
|
||||
dbg_error_log('HTTPAuthLogin', 'CheckCache: No salt, assuming no cached credentials, need to use auth source');
|
||||
}
|
||||
|
||||
# All hope is lost, we must have failed to find anything decent.
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal function used to set a cache for username/password details. This is
|
||||
* intended to reduce the load on external authentication sources.
|
||||
*/
|
||||
function SetCache( $username, $password, $state ) {
|
||||
global $c;
|
||||
|
||||
if (! $c->auth_cache) return 0;
|
||||
|
||||
$cache = getCacheInstance();
|
||||
if ($cache->isActive() === false) return 0;
|
||||
|
||||
$cache_ns = 'auth-' . $username;
|
||||
|
||||
$salt = $cache->get($cache_ns, 'salt');
|
||||
|
||||
if (!isset($salt) || $salt == '') {
|
||||
$salt = substr( str_replace('*','',base64_encode(sha1(rand(100000,9999999),true))), 2, 9);
|
||||
|
||||
# We use the default expiry setting for the salt, because the worse
|
||||
# case scenario is that we won't access the cached credentials and
|
||||
# need to fail to the full authentication source.
|
||||
if (! $cache->set($cache_ns, 'salt', $salt) ) {
|
||||
dbg_error_log('ERROR', 'HTTPCheckCache: SetCache: Failed to store salt, bailing out from caching credential.');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
# The hashed password to store
|
||||
$sha1_sent = session_salted_sha1($password, $salt);
|
||||
|
||||
# Work out the expiry to use, some sites might prefer different TTLs for
|
||||
# pass/fail results.
|
||||
if ($state == 'pass') {
|
||||
$expiry = $c->auth_cache_pass;
|
||||
} else if ($state == 'fail') {
|
||||
$expiry = $c->auth_cache_fail;
|
||||
} else {
|
||||
dbg_error_log('ERROR', 'HTTPCheckCache: SetCache: Unexpected state %s, bailing out from caching credential.', $state);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (! $cache->set($cache_ns, $sha1_sent, $state, $expiry) ) {
|
||||
dbg_error_log('ERROR', 'HTTPCheckCache: SetCache: Failed to store credential.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -161,6 +161,14 @@ $c->template_usr = array( 'active' => true,
|
||||
$c->hide_TODO = true; // VTODO only visible to collection owner
|
||||
$c->readonly_webdav_collections = true; // WebDAV access is readonly
|
||||
|
||||
// Authentication caching details
|
||||
$c->auth_cache = false; // Default to off
|
||||
$c->auth_cache_pass = 15 * 60; // 15 minutes
|
||||
$c->auth_cache_fail = 15 * 60; // 15 minutes
|
||||
|
||||
// Kind of private configuration values
|
||||
$c->total_query_time = 0;
|
||||
|
||||
// Any many times GetMoreInstances in inc/RRule.php should loop trying to
|
||||
// find more instances.
|
||||
$c->rrule_loop_limit = 100;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user