diff --git a/controlpanel/frontend/views/__init__.py b/controlpanel/frontend/views/__init__.py index d08bcede4..355944bea 100644 --- a/controlpanel/frontend/views/__init__.py +++ b/controlpanel/frontend/views/__init__.py @@ -85,13 +85,17 @@ UserDetail, UserList, ) -from controlpanel.oidc import OIDCLoginRequiredMixin, oauth +from controlpanel.oidc import OIDCLoginRequiredMixin, get_code_challenge, oauth class IndexView(OIDCLoginRequiredMixin, TemplateView): template_name = "home.html" def get_template_names(self): + """ + Returns the template to instruct users to authenticate with their Justice + account, unless this has already been captured. + """ if not self.request.user.justice_email: return ["justice_email.html"] @@ -99,9 +103,11 @@ def get_template_names(self): def get(self, request, *args, **kwargs): """ - If the user is a superuser display the home page (containing useful - admin related links). Otherwise, redirect the user to the list of the - tools they currently have available on the platform. + If the user has not authenticated with their Justice account, displays page to + ask them to authenticate, to allow us to capture their email address. + If their Justice email has been captured, normal users are redirected to their + tools. Superusers are displayed the home page (containing useful + admin related links). """ if request.user.is_superuser: @@ -115,18 +121,16 @@ def get(self, request, *args, **kwargs): return HttpResponseRedirect(reverse("list-tools")) def post(self, request): - code_challenge = self._get_code_challenge() + """ + Redirects user to authenticate with Azure EntraID. + """ redirect_uri = request.build_absolute_uri(reverse("entraid-auth")) return oauth.azure.authorize_redirect( request, redirect_uri, - code_challenge=code_challenge, + code_challenge=get_code_challenge(), ) - def _get_code_challenge(self): - code_verifier = generate_token(64) - digest = hashlib.sha256(code_verifier.encode()).digest() - return base64.urlsafe_b64encode(digest).rstrip(b"=").decode() class LogoutView(OIDCLogoutView): def get(self, request): diff --git a/controlpanel/frontend/views/auth.py b/controlpanel/frontend/views/auth.py index 4f5c63264..38e7961c0 100644 --- a/controlpanel/frontend/views/auth.py +++ b/controlpanel/frontend/views/auth.py @@ -13,9 +13,16 @@ class EntraIdAuthView(OIDCLoginRequiredMixin, View): + """ + This view is used as the callback after a user authenticates with their Justice + identity via Azure EntraID, in order to capture a users Justice email address. + """ http_method_names = ["get"] def _authorize_token(self): + """ + Attempts to valiate and return the access token + """ try: token = oauth.azure.authorize_access_token(self.request) except OAuthError as error: @@ -24,19 +31,26 @@ def _authorize_token(self): return token def get(self, request, *args, **kwargs): + """ + Attempts to retrieve the auth token, and update the user. + """ token = self._authorize_token() if not token: - messages.error(self.request, "Something went wrong, please try again soon") + messages.error(request, "Something went wrong, please try again") return HttpResponseRedirect(reverse("index")) self.update_user(token=token) + messages.success( + request=request, + message=f"Successfully authenticated with your email {request.user.justice_email}", + ) return HttpResponseRedirect(reverse("index")) def update_user(self, token): + """ + Update user with details from the ID token returned by the provided EntraID + access token + """ email = token["userinfo"]["email"] self.request.user.justice_email = email self.request.user.save() - messages.success( - request=self.request, - message=f"Successfully authenticated with your email {email}", - ) diff --git a/controlpanel/oidc.py b/controlpanel/oidc.py index bc90fafbf..0f923566a 100644 --- a/controlpanel/oidc.py +++ b/controlpanel/oidc.py @@ -1,8 +1,11 @@ # Standard library +import base64 +import hashlib from urllib.parse import urlencode # Third-party import structlog +from authlib.common.security import generate_token from authlib.integrations.django_client import OAuth from django.conf import settings from django.contrib.auth.mixins import LoginRequiredMixin @@ -99,6 +102,12 @@ def dispatch(self, request, *args, **kwargs): return super().dispatch(request, *args, **kwargs) +def get_code_challenge(): + code_verifier = generate_token(64) + digest = hashlib.sha256(code_verifier.encode()).digest() + return base64.urlsafe_b64encode(digest).rstrip(b"=").decode() + + oauth = OAuth() oauth.register( name="azure",