From 8a47506096a0431d6c3219f55ebbaecc57a6ef2e Mon Sep 17 00:00:00 2001 From: Dennis Buchhorn Date: Sun, 10 Dec 2023 22:03:29 +0100 Subject: [PATCH 1/4] feat[__init__.py]: add user cache --- radicale-auth-ldap/__init__.py | 91 +++++++++++++++++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/radicale-auth-ldap/__init__.py b/radicale-auth-ldap/__init__.py index beea2d0..6447390 100644 --- a/radicale-auth-ldap/__init__.py +++ b/radicale-auth-ldap/__init__.py @@ -2,6 +2,8 @@ from radicale.auth import BaseAuth from radicale.log import logger import ldap3 +import hashlib +import datetime PLUGIN_CONFIG_SCHEMA = {"auth": { "ldap_server_url": { @@ -27,12 +29,83 @@ PLUGIN_CONFIG_SCHEMA = {"auth": { "type": str}, "ldap_access_group_attribute": { "value": "None", - "type": str}}} + "type": str}, + "ldap_user_cache_in_seconds": { + "value": 0, + "type": int}}} + +class UserCacheEntry: + def __init__(self, secret, expireDatetime): + self.secret = secret + self.expireDatetime = expireDatetime + + def __str__(self): + temp = "secret [" + self.secret + "]\n" + temp += "expireDatetime [" + str(self.expireDatetime) + "]\n" + + return temp + + def checkSecret(self, secret): + if self.secret == secret: + return True + else: + return False + +class UserCache: + def __init__(self, cacheTimeInSeconds): + self.salt = str(datetime.datetime.now(tz=datetime.UTC)) + self.cacheTimeInSeconds = cacheTimeInSeconds + self.cache = {} + + def clean(self): + now = datetime.datetime.now(tz=datetime.UTC) + + entriesToDelete = [] + + for entry in self.cache: + if self.cache.get(entry).expireDatetime < now: + entriesToDelete.append(entry) + + for entry in entriesToDelete: + self.cache.pop(entry, None) + + def addEntry(self, userName, password): + self.clean() + + hashedUserName = hashlib.sha256((userName + self.salt).encode()).hexdigest() + hashedPassword = hashlib.sha256((password + self.salt).encode()).hexdigest() + expireDatetime = datetime.datetime.now(tz=datetime.UTC) + datetime.timedelta(seconds=self.cacheTimeInSeconds) + + self.cache[hashedUserName] = UserCacheEntry(hashedPassword, expireDatetime) + + def checkIfEntryIsCached(self, userName, password): + self.clean() + + hashedUserName = hashlib.sha256((userName + self.salt).encode()).hexdigest() + hashedPassword = hashlib.sha256((password + self.salt).encode()).hexdigest() + + if hashedUserName in self.cache: + return self.cache[hashedUserName].checkSecret(hashedPassword) + else: + return False class Auth(BaseAuth): def __init__(self, configuration): super().__init__(configuration.copy(PLUGIN_CONFIG_SCHEMA)) + userCacheInSeconds = self.configuration.get("auth", "ldap_user_cache_in_seconds") + + if userCacheInSeconds > 0: + self.grantedUserAccessCache = UserCache(userCacheInSeconds) + + logger.info("LDAP: use user cache") + logger.debug("LDAP: user cache salt: %s" % self.grantedUserAccessCache.salt) + logger.debug("LDAP: user cache cacheTimeInSeconds: %s" % str(self.grantedUserAccessCache.cacheTimeInSeconds)) + else: + self.grantedUserAccessCache = None + + logger.info("LDAP: do not use cache") + def login(self, user, password): """Check if ``user``/``password`` couple is valid.""" serverUrl = self.configuration.get("auth", "ldap_server_url") @@ -44,6 +117,16 @@ class Auth(BaseAuth): accessGroupFilter = self.configuration.get("auth", "ldap_access_group_filter") accessGroupAttribute = self.configuration.get("auth", "ldap_access_group_attribute") + if self.grantedUserAccessCache is not None: + logger.debug("LDAP: check if user is cached") + + if self.grantedUserAccessCache.checkIfEntryIsCached(user, password): + logger.debug("LDAP: user is cached") + + return user + else: + logger.debug("LDAP: user is not cached") + logger.info("LDAP: start connection") logger.debug("LDAP: server URL: %s" % serverUrl) logger.debug("LDAP: binddn: %s" % binddn) @@ -138,4 +221,10 @@ class Auth(BaseAuth): return "" else: logger.info("LDAP: user successful verified") + + if self.grantedUserAccessCache is not None: + logger.debug("LDAP: add user to cache") + + self.grantedUserAccessCache.addEntry(user, password) + return userAttributeValue -- 2.26.2 From 8d569ceabed8da0630fd4c4ad4fc605bc86a1b07 Mon Sep 17 00:00:00 2001 From: Dennis Buchhorn Date: Mon, 11 Dec 2023 08:39:50 +0100 Subject: [PATCH 2/4] feat[__init__.py]: change some LDAP logs from 'debug' to 'info' --- radicale-auth-ldap/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/radicale-auth-ldap/__init__.py b/radicale-auth-ldap/__init__.py index 6447390..d361529 100644 --- a/radicale-auth-ldap/__init__.py +++ b/radicale-auth-ldap/__init__.py @@ -118,14 +118,14 @@ class Auth(BaseAuth): accessGroupAttribute = self.configuration.get("auth", "ldap_access_group_attribute") if self.grantedUserAccessCache is not None: - logger.debug("LDAP: check if user is cached") + logger.info("LDAP: check if user is cached") if self.grantedUserAccessCache.checkIfEntryIsCached(user, password): - logger.debug("LDAP: user is cached") + logger.info("LDAP: user is cached") return user else: - logger.debug("LDAP: user is not cached") + logger.info("LDAP: user is not cached") logger.info("LDAP: start connection") logger.debug("LDAP: server URL: %s" % serverUrl) @@ -223,7 +223,7 @@ class Auth(BaseAuth): logger.info("LDAP: user successful verified") if self.grantedUserAccessCache is not None: - logger.debug("LDAP: add user to cache") + logger.info("LDAP: add user to cache") self.grantedUserAccessCache.addEntry(user, password) -- 2.26.2 From a9ce7687d114fcc9c9d5dc7fff27b5a01d831cb7 Mon Sep 17 00:00:00 2001 From: Dennis Buchhorn Date: Mon, 11 Dec 2023 09:12:22 +0100 Subject: [PATCH 3/4] feat[__init__.py]: add cache entry drop if password is wrong --- radicale-auth-ldap/__init__.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/radicale-auth-ldap/__init__.py b/radicale-auth-ldap/__init__.py index d361529..7fbebbc 100644 --- a/radicale-auth-ldap/__init__.py +++ b/radicale-auth-ldap/__init__.py @@ -84,8 +84,16 @@ class UserCache: hashedUserName = hashlib.sha256((userName + self.salt).encode()).hexdigest() hashedPassword = hashlib.sha256((password + self.salt).encode()).hexdigest() + ## Check if user is in cache if hashedUserName in self.cache: - return self.cache[hashedUserName].checkSecret(hashedPassword) + ## Check if user password is correct + if self.cache[hashedUserName].checkSecret(hashedPassword): + return True + else + ## Delete cache entry if password is wrong + self.cache.pop(hashedUserName) + + return False else: return False -- 2.26.2 From b94ea541cb77a59ddbcb72d0bf3a3b8362e0fab9 Mon Sep 17 00:00:00 2001 From: Dennis Buchhorn Date: Mon, 11 Dec 2023 09:15:40 +0100 Subject: [PATCH 4/4] fix[__init__.py]: syntax error --- radicale-auth-ldap/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/radicale-auth-ldap/__init__.py b/radicale-auth-ldap/__init__.py index 7fbebbc..65989cc 100644 --- a/radicale-auth-ldap/__init__.py +++ b/radicale-auth-ldap/__init__.py @@ -89,7 +89,7 @@ class UserCache: ## Check if user password is correct if self.cache[hashedUserName].checkSecret(hashedPassword): return True - else + else: ## Delete cache entry if password is wrong self.cache.pop(hashedUserName) -- 2.26.2