mirror of
https://github.com/Kozea/Radicale.git
synced 2025-04-04 13:47:37 +03:00
add chache cleanup and locking
This commit is contained in:
parent
79ba07e16b
commit
5ce0cee8bf
1 changed files with 34 additions and 12 deletions
|
@ -31,6 +31,7 @@ Take a look at the class ``BaseAuth`` if you want to implement your own.
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import time
|
import time
|
||||||
|
import threading
|
||||||
from typing import Sequence, Set, Tuple, Union, final
|
from typing import Sequence, Set, Tuple, Union, final
|
||||||
|
|
||||||
from radicale import config, types, utils
|
from radicale import config, types, utils
|
||||||
|
@ -63,9 +64,10 @@ class BaseAuth:
|
||||||
_cache_logins: bool
|
_cache_logins: bool
|
||||||
_cache_successful: dict # login -> (digest, time_ns)
|
_cache_successful: dict # login -> (digest, time_ns)
|
||||||
_cache_successful_logins_expiry: int
|
_cache_successful_logins_expiry: int
|
||||||
_cache_failed: dict # digest_failed -> (time_ns)
|
_cache_failed: dict # digest_failed -> (time_ns, login)
|
||||||
_cache_failed_logins_expiry: int
|
_cache_failed_logins_expiry: int
|
||||||
_cache_failed_logins_salt_ns: int # persistent over runtime
|
_cache_failed_logins_salt_ns: int # persistent over runtime
|
||||||
|
_lock: threading.Lock
|
||||||
|
|
||||||
def __init__(self, configuration: "config.Configuration") -> None:
|
def __init__(self, configuration: "config.Configuration") -> None:
|
||||||
"""Initialize BaseAuth.
|
"""Initialize BaseAuth.
|
||||||
|
@ -105,6 +107,7 @@ class BaseAuth:
|
||||||
self._cache_successful = dict()
|
self._cache_successful = dict()
|
||||||
self._cache_failed = dict()
|
self._cache_failed = dict()
|
||||||
self._cache_failed_logins_salt_ns = time.time_ns()
|
self._cache_failed_logins_salt_ns = time.time_ns()
|
||||||
|
self._lock = threading.Lock()
|
||||||
|
|
||||||
def _cache_digest(self, login: str, password: str, salt: str) -> str:
|
def _cache_digest(self, login: str, password: str, salt: str) -> str:
|
||||||
h = hashlib.sha3_512()
|
h = hashlib.sha3_512()
|
||||||
|
@ -152,19 +155,34 @@ class BaseAuth:
|
||||||
result = ""
|
result = ""
|
||||||
digest = ""
|
digest = ""
|
||||||
time_ns = time.time_ns()
|
time_ns = time.time_ns()
|
||||||
|
# cleanup failed login cache to avoid out-of-memory
|
||||||
|
cache_failed_entries = len(self._cache_failed)
|
||||||
|
if cache_failed_entries > 0:
|
||||||
|
logger.debug("Login failed cache investigation start (entries: %d)", cache_failed_entries)
|
||||||
|
self._lock.acquire()
|
||||||
|
cache_failed_cleanup = dict()
|
||||||
|
for digest in self._cache_failed:
|
||||||
|
(time_ns_cache, login_cache) = self._cache_failed[digest]
|
||||||
|
age_failed = int((time_ns - time_ns_cache) / 1000 / 1000 / 1000)
|
||||||
|
if age_failed > self._cache_failed_logins_expiry:
|
||||||
|
cache_failed_cleanup[digest] = (login_cache, age_failed)
|
||||||
|
cache_failed_cleanup_entries = len(cache_failed_cleanup)
|
||||||
|
logger.debug("Login failed cache cleanup start (entries: %d)", cache_failed_cleanup_entries)
|
||||||
|
if cache_failed_cleanup_entries > 0:
|
||||||
|
for digest in cache_failed_cleanup:
|
||||||
|
(login, age_failed) = cache_failed_cleanup[digest]
|
||||||
|
logger.debug("Login failed cache entry for user+password expired: '%s' (age: %d > %d sec)", login_cache, age_failed, self._cache_failed_logins_expiry)
|
||||||
|
del self._cache_failed[digest]
|
||||||
|
self._lock.release()
|
||||||
|
logger.debug("Login failed cache investigation finished")
|
||||||
|
# check for cache failed login
|
||||||
digest_failed = login + ":" + self._cache_digest(login, password, str(self._cache_failed_logins_salt_ns))
|
digest_failed = login + ":" + self._cache_digest(login, password, str(self._cache_failed_logins_salt_ns))
|
||||||
if self._cache_failed.get(digest_failed):
|
if self._cache_failed.get(digest_failed):
|
||||||
# login+password found in cache "failed"
|
# login+password found in cache "failed" -> shortcut return
|
||||||
time_ns_cache = self._cache_failed[digest_failed]
|
(time_ns_cache, login_cache) = self._cache_failed[digest]
|
||||||
age_failed = int((time_ns - time_ns_cache) / 1000 / 1000 / 1000)
|
age_failed = int((time_ns - time_ns_cache) / 1000 / 1000 / 1000)
|
||||||
if age_failed > self._cache_failed_logins_expiry:
|
logger.debug("Login failed cache entry for user+password found: '%s' (age: %d sec)", login_cache, age_failed)
|
||||||
logger.debug("Login failed cache entry for user+password found but expired: '%s' (age: %d > %d sec)", login, age_failed, self._cache_failed_logins_expiry)
|
return ""
|
||||||
# delete expired failed from cache
|
|
||||||
del self._cache_failed[digest_failed]
|
|
||||||
else:
|
|
||||||
# shortcut return
|
|
||||||
logger.debug("Login failed cache entry for user+password found: '%s' (age: %d sec)", login, age_failed)
|
|
||||||
return ""
|
|
||||||
if self._cache_successful.get(login):
|
if self._cache_successful.get(login):
|
||||||
# login found in cache "successful"
|
# login found in cache "successful"
|
||||||
(digest_cache, time_ns_cache) = self._cache_successful[login]
|
(digest_cache, time_ns_cache) = self._cache_successful[login]
|
||||||
|
@ -194,14 +212,18 @@ class BaseAuth:
|
||||||
# successful login, but expired, digest must be recalculated
|
# successful login, but expired, digest must be recalculated
|
||||||
digest = self._cache_digest(login, password, str(time_ns))
|
digest = self._cache_digest(login, password, str(time_ns))
|
||||||
# store successful login in cache
|
# store successful login in cache
|
||||||
|
self._lock.acquire()
|
||||||
self._cache_successful[login] = (digest, time_ns)
|
self._cache_successful[login] = (digest, time_ns)
|
||||||
|
self._lock.release()
|
||||||
logger.debug("Login successful cache for user set: '%s'", login)
|
logger.debug("Login successful cache for user set: '%s'", login)
|
||||||
if self._cache_failed.get(digest_failed):
|
if self._cache_failed.get(digest_failed):
|
||||||
logger.debug("Login failed cache for user cleared: '%s'", login)
|
logger.debug("Login failed cache for user cleared: '%s'", login)
|
||||||
del self._cache_failed[digest_failed]
|
del self._cache_failed[digest_failed]
|
||||||
else:
|
else:
|
||||||
logger.debug("Login failed for user+password via backend: '%s'", login)
|
logger.debug("Login failed for user+password via backend: '%s'", login)
|
||||||
self._cache_failed[digest_failed] = time_ns
|
self._lock.acquire()
|
||||||
|
self._cache_failed[digest_failed] = (time_ns, login)
|
||||||
|
self._lock.release()
|
||||||
logger.debug("Login failed cache for user set: '%s'", login)
|
logger.debug("Login failed cache for user set: '%s'", login)
|
||||||
return result
|
return result
|
||||||
else:
|
else:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue