From e73270bbe5db2cbfe9257efffa2af67318a8cf0f Mon Sep 17 00:00:00 2001 From: Unrud Date: Mon, 30 Apr 2018 00:18:36 +0200 Subject: [PATCH] Auth: Introduce login(login, password) method This deprecates map_login_to_user, is_authenticated and is_authenticated2 --- radicale/__init__.py | 45 ++++++++++++++++++++++---------------------- radicale/auth.py | 28 +++++++++++++++++---------- 2 files changed, 40 insertions(+), 33 deletions(-) diff --git a/radicale/__init__.py b/radicale/__init__.py index 93f069a..40bfa1c 100644 --- a/radicale/__init__.py +++ b/radicale/__init__.py @@ -431,6 +431,7 @@ class Application: authorization = environ.get("HTTP_AUTHORIZATION", "") if external_login: login, password = external_login + login, password = login or "", password or "" elif authorization.startswith("Basic"): authorization = authorization[len("Basic"):].strip() login, password = self.decode(base64.b64decode( @@ -439,30 +440,28 @@ class Application: # DEPRECATED: use remote_user backend instead login = environ.get("REMOTE_USER", "") password = "" - user = self.Auth.map_login_to_user(login) - if not user: - is_authenticated = True - elif not storage.is_safe_path_component(user): + user = self.Auth.login(login, password) or "" if login else "" + if user and login == user: + self.logger.info("Successful login: %r", user) + elif user: + self.logger.info("Successful login: %r -> %r", login, user) + elif login: + self.logger.info("Failed login attempt: %r", login) + # Random delay to avoid timing oracles and bruteforce attacks + delay = self.configuration.getfloat("auth", "delay") + if delay > 0: + random_delay = delay * (0.5 + random.random()) + self.logger.debug("Sleeping %.3f seconds", random_delay) + time.sleep(random_delay) + + if user and not storage.is_safe_path_component(user): # Prevent usernames like "user/calendar.ics" self.logger.info("Refused unsafe username: %r", user) - is_authenticated = False - else: - is_authenticated = self.Auth.is_authenticated2(login, user, - password) - if not is_authenticated: - self.logger.info("Failed login attempt: %r", user) - # Random delay to avoid timing oracles and bruteforce attacks - delay = self.configuration.getfloat("auth", "delay") - if delay > 0: - random_delay = delay * (0.5 + random.random()) - self.logger.debug("Sleeping %.3f seconds", random_delay) - time.sleep(random_delay) - else: - self.logger.info("Successful login: %r", user) + user = "" # Create principal collection - if user and is_authenticated: + if user: principal_path = "/%s/" % user if self.Rights.authorized(user, principal_path, "w"): with self.Collection.acquire_lock("r", user): @@ -476,7 +475,7 @@ class Application: except ValueError as e: self.logger.warning("Failed to create principal " "collection %r: %s", user, e) - is_authenticated = False + user = "" else: self.logger.warning("Access to principal path %r denied by " "rights backend", principal_path) @@ -491,7 +490,7 @@ class Application: "Request body too large: %d", content_length) return response(*REQUEST_ENTITY_TOO_LARGE) - if is_authenticated: + if not login or user: status, headers, answer = function( environ, base_prefix, path, user) if (status, headers, answer) == NOT_ALLOWED: @@ -500,8 +499,8 @@ class Application: else: status, headers, answer = NOT_ALLOWED - if (status, headers, answer) == NOT_ALLOWED and not ( - user and is_authenticated) and not external_login: + if ((status, headers, answer) == NOT_ALLOWED and not user and + not external_login): # Unknown or unauthorized user self.logger.debug("Asking client for authentication") status = client.UNAUTHORIZED diff --git a/radicale/auth.py b/radicale/auth.py index fc5d425..16704bb 100644 --- a/radicale/auth.py +++ b/radicale/auth.py @@ -102,14 +102,26 @@ class BaseAuth: """ return () - def is_authenticated2(self, login, user, password): - """Validate credentials. + def login(self, login, password): + """Check credentials and map login to internal user ``login`` the login name - ``user`` the user from ``map_login_to_user(login)``. + ``password`` the password - ``password`` the login password + Returns the user name or ``""`` for invalid credentials. + + """ + + user = self.map_login_to_user(login) + if user and self.is_authenticated2(login, user, password): + return user + return "" + + def is_authenticated2(self, login, user, password): + """Validate credentials. + + DEPRECATED: use ``login`` instead """ return self.is_authenticated(user, password) @@ -117,7 +129,7 @@ class BaseAuth: def is_authenticated(self, user, password): """Validate credentials. - DEPRECATED: use ``is_authenticated2`` instead + DEPRECATED: use ``login`` instead """ raise NotImplementedError @@ -125,11 +137,7 @@ class BaseAuth: def map_login_to_user(self, login): """Map login name to internal user. - ``login`` the login name, ``""`` for anonymous users - - Returns a string with the user name. - If a login can't be mapped to an user, return ``login`` and - return ``False`` in ``is_authenticated2(...)``. + DEPRECATED: use ``login`` instead """ return login