mirror of
https://github.com/Kozea/Radicale.git
synced 2025-08-01 18:18:31 +00:00
[auth] htpasswd: module 'bcrypt' is no longer mandatory in case digest method not used in file
This commit is contained in:
parent
9cac3008b7
commit
5357e692d9
1 changed files with 35 additions and 9 deletions
|
@ -70,6 +70,8 @@ class Auth(auth.BaseAuth):
|
||||||
_htpasswd_ok: bool
|
_htpasswd_ok: bool
|
||||||
_htpasswd_not_ok_seconds: int
|
_htpasswd_not_ok_seconds: int
|
||||||
_htpasswd_not_ok_reminder_seconds: int
|
_htpasswd_not_ok_reminder_seconds: int
|
||||||
|
_htpasswd_bcrypt_use: int
|
||||||
|
_has_bcrypt: bool
|
||||||
_lock: threading.Lock
|
_lock: threading.Lock
|
||||||
|
|
||||||
def __init__(self, configuration: config.Configuration) -> None:
|
def __init__(self, configuration: config.Configuration) -> None:
|
||||||
|
@ -79,9 +81,10 @@ class Auth(auth.BaseAuth):
|
||||||
encryption: str = configuration.get("auth", "htpasswd_encryption")
|
encryption: str = configuration.get("auth", "htpasswd_encryption")
|
||||||
logger.info("auth htpasswd encryption is 'radicale.auth.htpasswd_encryption.%s'", encryption)
|
logger.info("auth htpasswd encryption is 'radicale.auth.htpasswd_encryption.%s'", encryption)
|
||||||
|
|
||||||
|
self._has_bcrypt = False
|
||||||
self._htpasswd_ok = False
|
self._htpasswd_ok = False
|
||||||
self._htpasswd_not_ok_reminder_seconds = 60 # currently hardcoded
|
self._htpasswd_not_ok_reminder_seconds = 60 # currently hardcoded
|
||||||
self._htpasswd_read = self._read_htpasswd(True)
|
(self._htpasswd_ok, self._htpasswd_bcrypt_use) = self._read_htpasswd(True)
|
||||||
self._lock = threading.Lock()
|
self._lock = threading.Lock()
|
||||||
|
|
||||||
if encryption == "plain":
|
if encryption == "plain":
|
||||||
|
@ -96,14 +99,24 @@ class Auth(auth.BaseAuth):
|
||||||
try:
|
try:
|
||||||
import bcrypt
|
import bcrypt
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
raise RuntimeError(
|
if (encryption == "autodetect") and (self._htpasswd_bcrypt_use == 0):
|
||||||
"The htpasswd encryption method 'bcrypt' or 'autodetect' requires "
|
logger.warning("auth htpasswd encryption is 'radicale.auth.htpasswd_encryption.%s' which can require bycrypt module, but currently no entries found", encryption)
|
||||||
"the bcrypt module.") from e
|
else:
|
||||||
|
raise RuntimeError(
|
||||||
|
"The htpasswd encryption method 'bcrypt' or 'autodetect' requires "
|
||||||
|
"the bcrypt module (entries found: %d)." % self._htpasswd_bcrypt_use) from e
|
||||||
|
else:
|
||||||
|
if encryption == "autodetect":
|
||||||
|
if self._htpasswd_bcrypt_use == 0:
|
||||||
|
logger.info("auth htpasswd encryption is 'radicale.auth.htpasswd_encryption.%s' and bycrypt module found, but currently not required", encryption)
|
||||||
|
else:
|
||||||
|
logger.info("auth htpasswd encryption is 'radicale.auth.htpasswd_encryption.%s' and bycrypt module found (entries found: %d)", encryption, self._htpasswd_bcrypt_use)
|
||||||
if encryption == "bcrypt":
|
if encryption == "bcrypt":
|
||||||
self._verify = functools.partial(self._bcrypt, bcrypt)
|
self._verify = functools.partial(self._bcrypt, bcrypt)
|
||||||
else:
|
else:
|
||||||
self._verify = self._autodetect
|
self._verify = self._autodetect
|
||||||
self._verify_bcrypt = functools.partial(self._bcrypt, bcrypt)
|
self._verify_bcrypt = functools.partial(self._bcrypt, bcrypt)
|
||||||
|
self._has_bcrypt = True
|
||||||
else:
|
else:
|
||||||
raise RuntimeError("The htpasswd encryption method %r is not "
|
raise RuntimeError("The htpasswd encryption method %r is not "
|
||||||
"supported." % encryption)
|
"supported." % encryption)
|
||||||
|
@ -141,7 +154,7 @@ class Auth(auth.BaseAuth):
|
||||||
# assumed plaintext
|
# assumed plaintext
|
||||||
return self._plain(hash_value, password)
|
return self._plain(hash_value, password)
|
||||||
|
|
||||||
def _read_htpasswd(self, init: bool) -> bool:
|
def _read_htpasswd(self, init: bool) -> (bool, int):
|
||||||
"""Read htpasswd file
|
"""Read htpasswd file
|
||||||
|
|
||||||
init == True: stop on error
|
init == True: stop on error
|
||||||
|
@ -149,6 +162,7 @@ class Auth(auth.BaseAuth):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
htpasswd_ok = True
|
htpasswd_ok = True
|
||||||
|
bcrypt_use = 0
|
||||||
if init is True:
|
if init is True:
|
||||||
info = "Read"
|
info = "Read"
|
||||||
else:
|
else:
|
||||||
|
@ -166,12 +180,14 @@ class Auth(auth.BaseAuth):
|
||||||
if line.lstrip() and not line.lstrip().startswith("#"):
|
if line.lstrip() and not line.lstrip().startswith("#"):
|
||||||
try:
|
try:
|
||||||
login, digest = line.split( ":", maxsplit=1)
|
login, digest = line.split( ":", maxsplit=1)
|
||||||
|
skip = False
|
||||||
if login == "" or digest == "":
|
if login == "" or digest == "":
|
||||||
if init is True:
|
if init is True:
|
||||||
raise ValueError("htpasswd file contains problematic line not matching <login>:<digest> in line: %d" % line_num)
|
raise ValueError("htpasswd file contains problematic line not matching <login>:<digest> in line: %d" % line_num)
|
||||||
else:
|
else:
|
||||||
logger.warning("htpasswd file contains problematic line not matching <login>:<digest> in line: %d (ignored)", line_num)
|
logger.warning("htpasswd file contains problematic line not matching <login>:<digest> in line: %d (ignored)", line_num)
|
||||||
htpasswd_ok = False
|
htpasswd_ok = False
|
||||||
|
skip = True
|
||||||
else:
|
else:
|
||||||
if htpasswd.get(login):
|
if htpasswd.get(login):
|
||||||
duplicates += 1
|
duplicates += 1
|
||||||
|
@ -180,9 +196,19 @@ class Auth(auth.BaseAuth):
|
||||||
else:
|
else:
|
||||||
logger.warning("htpasswd file contains duplicate login: '%s' (line: %d / ignored)", login, line_num)
|
logger.warning("htpasswd file contains duplicate login: '%s' (line: %d / ignored)", login, line_num)
|
||||||
htpasswd_ok = False
|
htpasswd_ok = False
|
||||||
|
skip = True
|
||||||
else:
|
else:
|
||||||
htpasswd[login] = digest
|
if digest.startswith("$2y$", 0, 4) and len(digest) == 60:
|
||||||
entries += 1
|
if init is True:
|
||||||
|
bcrypt_use += 1
|
||||||
|
else:
|
||||||
|
if self._has_bcrypt is False:
|
||||||
|
logger.warning("htpasswd file contains bcrypt digest login: '%s' (line: %d / ignored because module is not loaded)", login, line_num)
|
||||||
|
skip = True
|
||||||
|
htpasswd_ok = False
|
||||||
|
if skip is False:
|
||||||
|
htpasswd[login] = digest
|
||||||
|
entries += 1
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
if init is True:
|
if init is True:
|
||||||
raise RuntimeError("Invalid htpasswd file %r: %s" % (self._filename, e)) from e
|
raise RuntimeError("Invalid htpasswd file %r: %s" % (self._filename, e)) from e
|
||||||
|
@ -201,7 +227,7 @@ class Auth(auth.BaseAuth):
|
||||||
self._htpasswd_not_ok_time = 0
|
self._htpasswd_not_ok_time = 0
|
||||||
else:
|
else:
|
||||||
self._htpasswd_not_ok_time = time.time()
|
self._htpasswd_not_ok_time = time.time()
|
||||||
return htpasswd_ok
|
return (htpasswd_ok, bcrypt_use)
|
||||||
|
|
||||||
def _login(self, login: str, password: str) -> str:
|
def _login(self, login: str, password: str) -> str:
|
||||||
"""Validate credentials.
|
"""Validate credentials.
|
||||||
|
@ -219,7 +245,7 @@ class Auth(auth.BaseAuth):
|
||||||
htpasswd_time_ns = os.stat(self._filename).st_mtime_ns
|
htpasswd_time_ns = os.stat(self._filename).st_mtime_ns
|
||||||
if (htpasswd_size != self._htpasswd_size) or (htpasswd_time_ns != self._htpasswd_time_ns):
|
if (htpasswd_size != self._htpasswd_size) or (htpasswd_time_ns != self._htpasswd_time_ns):
|
||||||
with self._lock:
|
with self._lock:
|
||||||
self._htpasswd_ok = self._read_htpasswd(False)
|
(self._htpasswd_ok, self._htpasswd_bcrypt_use) = self._read_htpasswd(False)
|
||||||
else:
|
else:
|
||||||
# log reminder of problemantic file every interval
|
# log reminder of problemantic file every interval
|
||||||
if (self._htpasswd_ok is False) and (self._htpasswd_not_ok_time > 0):
|
if (self._htpasswd_ok is False) and (self._htpasswd_not_ok_time > 0):
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue