Auth: Introduce login(login, password) method

This deprecates map_login_to_user, is_authenticated and is_authenticated2
This commit is contained in:
Unrud 2018-04-30 00:18:36 +02:00
parent 3455ab4ba9
commit 6c9299cf16
2 changed files with 40 additions and 33 deletions

View File

@ -440,6 +440,7 @@ class Application:
authorization = environ.get("HTTP_AUTHORIZATION", "") authorization = environ.get("HTTP_AUTHORIZATION", "")
if external_login: if external_login:
login, password = external_login login, password = external_login
login, password = login or "", password or ""
elif authorization.startswith("Basic"): elif authorization.startswith("Basic"):
authorization = authorization[len("Basic"):].strip() authorization = authorization[len("Basic"):].strip()
login, password = self.decode(base64.b64decode( login, password = self.decode(base64.b64decode(
@ -448,30 +449,28 @@ class Application:
# DEPRECATED: use remote_user backend instead # DEPRECATED: use remote_user backend instead
login = environ.get("REMOTE_USER", "") login = environ.get("REMOTE_USER", "")
password = "" password = ""
user = self.Auth.map_login_to_user(login)
if not user: user = self.Auth.login(login, password) or "" if login else ""
is_authenticated = True if user and login == user:
elif not storage.is_safe_path_component(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" # Prevent usernames like "user/calendar.ics"
self.logger.info("Refused unsafe username: %r", user) self.logger.info("Refused unsafe username: %r", user)
is_authenticated = False user = ""
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)
# Create principal collection # Create principal collection
if user and is_authenticated: if user:
principal_path = "/%s/" % user principal_path = "/%s/" % user
if self.Rights.authorized(user, principal_path, "w"): if self.Rights.authorized(user, principal_path, "w"):
with self.Collection.acquire_lock("r", user): with self.Collection.acquire_lock("r", user):
@ -485,7 +484,7 @@ class Application:
except ValueError as e: except ValueError as e:
self.logger.warning("Failed to create principal " self.logger.warning("Failed to create principal "
"collection %r: %s", user, e) "collection %r: %s", user, e)
is_authenticated = False user = ""
else: else:
self.logger.warning("Access to principal path %r denied by " self.logger.warning("Access to principal path %r denied by "
"rights backend", principal_path) "rights backend", principal_path)
@ -500,7 +499,7 @@ class Application:
"Request body too large: %d", content_length) "Request body too large: %d", content_length)
return response(*REQUEST_ENTITY_TOO_LARGE) return response(*REQUEST_ENTITY_TOO_LARGE)
if is_authenticated: if not login or user:
status, headers, answer = function( status, headers, answer = function(
environ, base_prefix, path, user) environ, base_prefix, path, user)
if (status, headers, answer) == NOT_ALLOWED: if (status, headers, answer) == NOT_ALLOWED:
@ -509,8 +508,8 @@ class Application:
else: else:
status, headers, answer = NOT_ALLOWED status, headers, answer = NOT_ALLOWED
if (status, headers, answer) == NOT_ALLOWED and not ( if ((status, headers, answer) == NOT_ALLOWED and not user and
user and is_authenticated) and not external_login: not external_login):
# Unknown or unauthorized user # Unknown or unauthorized user
self.logger.debug("Asking client for authentication") self.logger.debug("Asking client for authentication")
status = client.UNAUTHORIZED status = client.UNAUTHORIZED

View File

@ -102,14 +102,26 @@ class BaseAuth:
""" """
return () return ()
def is_authenticated2(self, login, user, password): def login(self, login, password):
"""Validate credentials. """Check credentials and map login to internal user
``login`` the login name ``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) return self.is_authenticated(user, password)
@ -117,7 +129,7 @@ class BaseAuth:
def is_authenticated(self, user, password): def is_authenticated(self, user, password):
"""Validate credentials. """Validate credentials.
DEPRECATED: use ``is_authenticated2`` instead DEPRECATED: use ``login`` instead
""" """
raise NotImplementedError raise NotImplementedError
@ -125,11 +137,7 @@ class BaseAuth:
def map_login_to_user(self, login): def map_login_to_user(self, login):
"""Map login name to internal user. """Map login name to internal user.
``login`` the login name, ``""`` for anonymous users DEPRECATED: use ``login`` instead
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(...)``.
""" """
return login return login