From 3586e6c9a5705337f2650b5defb7862405d34bc5 Mon Sep 17 00:00:00 2001 From: Andy Babic Date: Fri, 27 May 2022 09:06:45 +0100 Subject: [PATCH] Create new app for auth0-specific login functionality --- etna/auth0/__init__.py | 0 etna/auth0/auth_backend.py | 13 +++++++ etna/auth0/urls.py | 9 +++++ etna/auth0/views.py | 69 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 91 insertions(+) create mode 100644 etna/auth0/__init__.py create mode 100644 etna/auth0/auth_backend.py create mode 100644 etna/auth0/urls.py create mode 100644 etna/auth0/views.py diff --git a/etna/auth0/__init__.py b/etna/auth0/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/etna/auth0/auth_backend.py b/etna/auth0/auth_backend.py new file mode 100644 index 0000000000..2ff7a3d310 --- /dev/null +++ b/etna/auth0/auth_backend.py @@ -0,0 +1,13 @@ +from django.contrib.auth.backends import ModelBackend + + +class Auth0Backend(ModelBackend): + """ + A backend that overrides the `authenticate()` method to prevent succesfull + use with anything other than ``views.authorize()``, which specifies this + as the ``backend`` for users when calling `django.contrib.auth.login()` + (after successful authentication with Auth0). + """ + + def authenticate(self, request, **kwargs): + return None diff --git a/etna/auth0/urls.py b/etna/auth0/urls.py new file mode 100644 index 0000000000..95020b25f8 --- /dev/null +++ b/etna/auth0/urls.py @@ -0,0 +1,9 @@ +from django.urls import path + +from . import views + +urlpatterns = [ + path("login/", views.login, name="account_login"), + path("logout/", views.logout, name="account_logout"), + path("authorize/", views.authorize, name="account_authorize"), +] diff --git a/etna/auth0/views.py b/etna/auth0/views.py new file mode 100644 index 0000000000..8d211e9936 --- /dev/null +++ b/etna/auth0/views.py @@ -0,0 +1,69 @@ +from urllib.parse import quote_plus, urlencode + +from django.conf import settings +from django.contrib.auth import get_user_model +from django.contrib.auth import login as auth_login +from django.contrib.auth import logout as auth_logout +from django.http import HttpResponseRedirect +from django.shortcuts import redirect +from django.urls import reverse + +from authlib.integrations.django_client import OAuth + +User = get_user_model() + +oauth = OAuth() +oauth.register( + "auth0", + client_id=settings.AUTH0_CLIENT_ID, + client_secret=settings.AUTH0_CLIENT_SECRET, + client_kwargs={ + "scope": "openid profile email", + }, + server_metadata_url=f"https://{settings.AUTH0_DOMAIN}/.well-known/openid-configuration", +) + + +def login(request): + callback_url = reverse("account_authorize") + if next := request.GET.get("next"): + callback_url += "?" + urlencode(next) + return oauth.auth0.authorize_redirect( + request, request.build_absolute_uri(callback_url) + ) + + +def authorize(request): + token = oauth.auth0.authorize_access_token(request) + user_info = token["userinfo"] + user, created = User.objects.update_or_create( + username=user_info["email"], + defaults={ + "email": user_info["email"], + "first_name": user_info.get("given_name"), + "last_name": user_info.get("family_name"), + }, + ) + if created: + user.set_unusable_password() + user.save(update_fields=["password"]) + auth_login(request, user, backend="etna.auth0.auth_backend.Auth0Backend") + return HttpResponseRedirect(request.GET.get("next") or "/") + + +def logout(request): + auth_logout(request) + redirect_to = "/" + if settings.TERMINATE_SSO_SESSION_ON_LOGOUT: + return redirect( + f"https://{settings.AUTH0_DOMAIN}/v2/logout?" + + urlencode( + { + "returnTo": request.build_absolute_uri(redirect_to), + "client_id": settings.AUTH0_CLIENT_ID, + }, + quote_via=quote_plus, + ), + ) + else: + return HttpResponseRedirect(redirect_to)