|
@ -2,6 +2,8 @@ from radicale.auth import BaseAuth |
|
|
from radicale.log import logger |
|
|
from radicale.log import logger |
|
|
|
|
|
|
|
|
import ldap3 |
|
|
import ldap3 |
|
|
|
|
|
import hashlib |
|
|
|
|
|
import datetime |
|
|
|
|
|
|
|
|
PLUGIN_CONFIG_SCHEMA = {"auth": { |
|
|
PLUGIN_CONFIG_SCHEMA = {"auth": { |
|
|
"ldap_server_url": { |
|
|
"ldap_server_url": { |
|
@ -27,12 +29,83 @@ PLUGIN_CONFIG_SCHEMA = {"auth": { |
|
|
"type": str}, |
|
|
"type": str}, |
|
|
"ldap_access_group_attribute": { |
|
|
"ldap_access_group_attribute": { |
|
|
"value": "None", |
|
|
"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): |
|
|
class Auth(BaseAuth): |
|
|
def __init__(self, configuration): |
|
|
def __init__(self, configuration): |
|
|
super().__init__(configuration.copy(PLUGIN_CONFIG_SCHEMA)) |
|
|
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): |
|
|
def login(self, user, password): |
|
|
"""Check if ``user``/``password`` couple is valid.""" |
|
|
"""Check if ``user``/``password`` couple is valid.""" |
|
|
serverUrl = self.configuration.get("auth", "ldap_server_url") |
|
|
serverUrl = self.configuration.get("auth", "ldap_server_url") |
|
@ -44,6 +117,16 @@ class Auth(BaseAuth): |
|
|
accessGroupFilter = self.configuration.get("auth", "ldap_access_group_filter") |
|
|
accessGroupFilter = self.configuration.get("auth", "ldap_access_group_filter") |
|
|
accessGroupAttribute = self.configuration.get("auth", "ldap_access_group_attribute") |
|
|
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.info("LDAP: start connection") |
|
|
logger.debug("LDAP: server URL: %s" % serverUrl) |
|
|
logger.debug("LDAP: server URL: %s" % serverUrl) |
|
|
logger.debug("LDAP: binddn: %s" % binddn) |
|
|
logger.debug("LDAP: binddn: %s" % binddn) |
|
@ -138,4 +221,10 @@ class Auth(BaseAuth): |
|
|
return "" |
|
|
return "" |
|
|
else: |
|
|
else: |
|
|
logger.info("LDAP: user successful verified") |
|
|
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 |
|
|
return userAttributeValue |