From 00d1f5ea3a5bd4fea66978db56cddb05f2e00e52 Mon Sep 17 00:00:00 2001 From: rbinrais Date: Fri, 19 Jul 2019 01:30:32 -0400 Subject: [PATCH] Adds middlware that enables automatic login based on HTTP headers --- app/app/settings.py | 9 +++++++ app/server/middleware.py | 42 +++++++++++++++++++++++++++++ app/server/tests/test_middleware.py | 27 +++++++++++++++++++ authproxy/Dockerfile | 13 +++++++++ authproxy/nginx.conf | 23 ++++++++++++++++ authproxy/proxy.conf | 10 +++++++ 6 files changed, 124 insertions(+) create mode 100644 app/server/middleware.py create mode 100644 app/server/tests/test_middleware.py create mode 100644 authproxy/Dockerfile create mode 100644 authproxy/nginx.conf create mode 100644 authproxy/proxy.conf diff --git a/app/app/settings.py b/app/app/settings.py index b5b16640..0a9c4343 100644 --- a/app/app/settings.py +++ b/app/app/settings.py @@ -143,6 +143,15 @@ AUTHENTICATION_BACKENDS = [ 'django.contrib.auth.backends.ModelBackend', ] +HEADER_AUTH_USER_NAME = env('HEADER_AUTH_USER_NAME', '') +HEADER_AUTH_USER_GROUPS = env('HEADER_AUTH_USER_GROUPS', '') +HEADER_AUTH_ADMIN_GROUP_NAME = env('HEADER_AUTH_ADMIN_GROUP_NAME', '') +HEADER_AUTH_GROUPS_SEPERATOR = env('HEADER_AUTH_GROUPS_SEPERATOR', default=',') + +if HEADER_AUTH_USER_NAME and HEADER_AUTH_USER_GROUPS and HEADER_AUTH_ADMIN_GROUP_NAME: + MIDDLEWARE.append('server.middleware.HeaderAuthMiddleware') + AUTHENTICATION_BACKENDS.append('django.contrib.auth.backends.RemoteUserBackend') + SOCIAL_AUTH_GITHUB_KEY = env('OAUTH_GITHUB_KEY', None) SOCIAL_AUTH_GITHUB_SECRET = env('OAUTH_GITHUB_SECRET', None) GITHUB_ADMIN_ORG_NAME = env('GITHUB_ADMIN_ORG_NAME', None) diff --git a/app/server/middleware.py b/app/server/middleware.py new file mode 100644 index 00000000..f1c3097a --- /dev/null +++ b/app/server/middleware.py @@ -0,0 +1,42 @@ +from django.conf import settings +from django.contrib.auth.middleware import RemoteUserMiddleware + + +def to_django_header(header): + return f"HTTP_{header.replace('-', '_').upper()}" + + +class HeaderAuthMiddleware(RemoteUserMiddleware): + header = to_django_header(settings.HEADER_AUTH_USER_NAME) + + def process_request(self, request): + if request.user.is_authenticated: + return + + username = request.META.get(self.header) + if not username: + return + + super().process_request(request) + self.process_user_groups(request.user, request.META) + + @classmethod + def process_user_groups(cls, user, headers): + if not user.is_authenticated: + return + + groups = cls.parse_user_groups_from_header(headers) + + is_superuser = settings.HEADER_AUTH_ADMIN_GROUP_NAME in groups + if user.is_superuser != is_superuser: + user.is_superuser = is_superuser + user.save() + + @classmethod + def parse_user_groups_from_header(cls, headers): + try: + groups_header = headers[to_django_header(settings.HEADER_AUTH_USER_GROUPS)] + except KeyError: + return [] + else: + return groups_header.split(settings.HEADER_AUTH_GROUPS_SEPERATOR) diff --git a/app/server/tests/test_middleware.py b/app/server/tests/test_middleware.py new file mode 100644 index 00000000..8d556222 --- /dev/null +++ b/app/server/tests/test_middleware.py @@ -0,0 +1,27 @@ +from django.test import TestCase, override_settings +from django.contrib.auth.models import User + +from ..middleware import HeaderAuthMiddleware + + +@override_settings(HEADER_AUTH_USER_GROUPS='X-AuthProxy-Groups') +@override_settings(HEADER_AUTH_ADMIN_GROUP_NAME='Admin') +@override_settings(HEADER_AUTH_GROUPS_SEPERATOR=';') +class HeaderAuthMiddlewareTest(TestCase): + def test_process_user_groups_is_super(self): + user = User.objects.create_user(username='TestUser') + user.is_superuser = False + + middleware = HeaderAuthMiddleware() + middleware.process_user_groups(user, {'HTTP_X_AUTHPROXY_GROUPS': 'Admin;Reader'}) + + self.assertTrue(user.is_superuser) + + def test_process_user_groups_is_not_super(self): + user = User.objects.create_user(username='TestUser') + user.is_superuser = True + + middleware = HeaderAuthMiddleware() + middleware.process_user_groups(user, {'HTTP_X_AUTHPROXY_GROUPS': 'Guest;Reader'}) + + self.assertFalse(user.is_superuser) diff --git a/authproxy/Dockerfile b/authproxy/Dockerfile new file mode 100644 index 00000000..8b8dac75 --- /dev/null +++ b/authproxy/Dockerfile @@ -0,0 +1,13 @@ +FROM nginx:stable-alpine + +COPY nginx.conf /etc/nginx/nginx.conf +COPY proxy.conf /app/proxy.conf + +ENV PORT=8888 +ENV PROXY_PASS="" +ENV USER_NAME="" +ENV USER_GROUPS="" + +EXPOSE ${PORT} + +CMD envsubst < /app/proxy.conf > /etc/nginx/conf.d/default.conf && exec nginx -g 'daemon off;' diff --git a/authproxy/nginx.conf b/authproxy/nginx.conf new file mode 100644 index 00000000..57af12a8 --- /dev/null +++ b/authproxy/nginx.conf @@ -0,0 +1,23 @@ +worker_processes auto; + +error_log /var/log/nginx/error.log warn; +pid /app/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + keepalive_timeout 65; + + include /etc/nginx/conf.d/*.conf; +} diff --git a/authproxy/proxy.conf b/authproxy/proxy.conf new file mode 100644 index 00000000..662381a1 --- /dev/null +++ b/authproxy/proxy.conf @@ -0,0 +1,10 @@ +server { + listen ${PORT}; + + location / { + proxy_pass ${PROXY_PASS}; + proxy_set_header X-Auth-UserName ${USER_NAME}; + proxy_set_header X-Auth-Groups ${USER_GROUPS}; + } +} +