From 09bde14e50d548b3c17024b421fff51074d86863 Mon Sep 17 00:00:00 2001 From: Unrud Date: Wed, 31 May 2017 02:05:55 +0200 Subject: [PATCH] Allow auth backends to provide login and password This is used to implement an auth backend that takes the credentials from an HTTP header (e.g. accounts are managed by an reverse proxy) --- config | 2 +- radicale/__init__.py | 14 +++++++++----- radicale/auth.py | 22 ++++++++++++++++++++++ 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/config b/config index 0d17dae..bd35574 100644 --- a/config +++ b/config @@ -66,7 +66,7 @@ [auth] # Authentication method -# Value: None | htpasswd +# Value: None | htpasswd | remote_user | http_x_remote_user #type = None # Htpasswd filename diff --git a/radicale/__init__.py b/radicale/__init__.py index 9b85c6b..5137434 100644 --- a/radicale/__init__.py +++ b/radicale/__init__.py @@ -371,15 +371,19 @@ class Application: function = getattr(self, "do_%s" % environ["REQUEST_METHOD"].upper()) # Ask authentication backend to check rights - authorization = environ.get("HTTP_AUTHORIZATION", None) - if authorization and authorization.startswith("Basic"): + external_login = self.Auth.get_external_login(environ) + authorization = environ.get("HTTP_AUTHORIZATION", "") + if external_login: + login, password = external_login + elif authorization.startswith("Basic"): authorization = authorization[len("Basic"):].strip() login, password = self.decode(base64.b64decode( authorization.encode("ascii")), environ).split(":", 1) - user = self.Auth.map_login_to_user(login) else: - user = self.Auth.map_login_to_user(environ.get("REMOTE_USER", "")) + # DEPRECATED: use remote_user backend instead + login = environ.get("REMOTE_USER", "") password = "" + user = self.Auth.map_login_to_user(login) # If "/.well-known" is not available, clients query "/" if path == "/.well-known" or path.startswith("/.well-known/"): @@ -437,7 +441,7 @@ class Application: status, headers, answer = NOT_ALLOWED if (status, headers, answer) == NOT_ALLOWED and not ( - user and is_authenticated): + user and is_authenticated) 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 b202c5d..06f15f1 100644 --- a/radicale/auth.py +++ b/radicale/auth.py @@ -67,6 +67,10 @@ def load(configuration, logger): logger.debug("Authentication type is %s", auth_type) if auth_type == "None": class_ = NoneAuth + elif auth_type == "remote_user": + class_ = RemoteUserAuth + elif auth_type == "http_x_remote_user": + class_ = HttpXRemoteUserAuth elif auth_type == "htpasswd": class_ = Auth else: @@ -79,6 +83,14 @@ class BaseAuth: self.configuration = configuration self.logger = logger + def get_external_login(self, environ): + """Optionally provide the login and password externally. + + Returns a tuple (login, password) or (). + + """ + return () + def is_authenticated(self, user, password): """Validate credentials. @@ -201,3 +213,13 @@ class Auth(BaseAuth): if login_ok & password_ok: return True return False + + +class RemoteUserAuth(NoneAuth): + def get_external_login(self, environ): + return environ.get("REMOTE_USER", ""), "" + + +class HttpXRemoteUserAuth(NoneAuth): + def get_external_login(self, environ): + return environ.get("HTTP_X_REMOTE_USER", ""), ""