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)
This commit is contained in:
Unrud 2017-05-31 02:05:55 +02:00
parent 8bc45aeb24
commit 09bde14e50
3 changed files with 32 additions and 6 deletions

2
config
View File

@ -66,7 +66,7 @@
[auth] [auth]
# Authentication method # Authentication method
# Value: None | htpasswd # Value: None | htpasswd | remote_user | http_x_remote_user
#type = None #type = None
# Htpasswd filename # Htpasswd filename

View File

@ -371,15 +371,19 @@ class Application:
function = getattr(self, "do_%s" % environ["REQUEST_METHOD"].upper()) function = getattr(self, "do_%s" % environ["REQUEST_METHOD"].upper())
# Ask authentication backend to check rights # Ask authentication backend to check rights
authorization = environ.get("HTTP_AUTHORIZATION", None) external_login = self.Auth.get_external_login(environ)
if authorization and authorization.startswith("Basic"): authorization = environ.get("HTTP_AUTHORIZATION", "")
if external_login:
login, password = external_login
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(
authorization.encode("ascii")), environ).split(":", 1) authorization.encode("ascii")), environ).split(":", 1)
user = self.Auth.map_login_to_user(login)
else: else:
user = self.Auth.map_login_to_user(environ.get("REMOTE_USER", "")) # DEPRECATED: use remote_user backend instead
login = environ.get("REMOTE_USER", "")
password = "" password = ""
user = self.Auth.map_login_to_user(login)
# If "/.well-known" is not available, clients query "/" # If "/.well-known" is not available, clients query "/"
if path == "/.well-known" or path.startswith("/.well-known/"): if path == "/.well-known" or path.startswith("/.well-known/"):
@ -437,7 +441,7 @@ class Application:
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 is_authenticated): user and is_authenticated) and 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

@ -67,6 +67,10 @@ def load(configuration, logger):
logger.debug("Authentication type is %s", auth_type) logger.debug("Authentication type is %s", auth_type)
if auth_type == "None": if auth_type == "None":
class_ = NoneAuth class_ = NoneAuth
elif auth_type == "remote_user":
class_ = RemoteUserAuth
elif auth_type == "http_x_remote_user":
class_ = HttpXRemoteUserAuth
elif auth_type == "htpasswd": elif auth_type == "htpasswd":
class_ = Auth class_ = Auth
else: else:
@ -79,6 +83,14 @@ class BaseAuth:
self.configuration = configuration self.configuration = configuration
self.logger = logger 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): def is_authenticated(self, user, password):
"""Validate credentials. """Validate credentials.
@ -201,3 +213,13 @@ class Auth(BaseAuth):
if login_ok & password_ok: if login_ok & password_ok:
return True return True
return False 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", ""), ""