Skip to content

Commit

Permalink
Replace Mozilla OIDC with authlib
Browse files Browse the repository at this point in the history
  • Loading branch information
trickeydan committed Dec 9, 2023
1 parent 3920c4c commit 1dac6ad
Show file tree
Hide file tree
Showing 11 changed files with 98 additions and 122 deletions.
9 changes: 9 additions & 0 deletions kmicms/accounts/oauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,12 @@
userinfo_endpoint=settings.DISCORD_USERINFO_ENDPOINT,
client_kwargs=settings.DISCORD_CLIENT_KWARGS,
)

# SOWN SSO
oauth_config.register(
"sown",
client_id=settings.SSO_OIDC_CLIENT_ID,
client_secret=settings.SSO_OIDC_CLIENT_SECRET,
server_metadata_url=settings.SSO_OIDC_CONFIGURATION_URL,
client_kwargs={"scope": settings.SSO_OIDC_SCOPES},
)
53 changes: 0 additions & 53 deletions kmicms/accounts/oidc.py

This file was deleted.

11 changes: 8 additions & 3 deletions kmicms/accounts/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,23 @@
path("logout/", LogoutView.as_view(), name="logout"),
path("profile/", views.profile.UserProfileView.as_view(), name="profile"),
path(
"integrations/discord/setup",
"integrations/discord/setup/",
views.discord.DiscordOAuthProfileAuthorizeView.as_view(),
name="discord_oauth_setup",
),
path(
"integrations/discord/redirect",
"integrations/discord/redirect/",
views.discord.DiscordOAuthProfileRedirectView.as_view(),
name="discord_oauth_redirect",
),
path(
"integrations/discord/disconnect",
"integrations/discord/disconnect/",
views.discord.DiscordAccountProfileDisconnectView.as_view(),
name="discord_oauth_disconnect",
),
path(
"oidc/redirect/",
views.auth.SSOOIDCRedirectView.as_view(),
name="sso_oidc_redirect",
),
]
71 changes: 70 additions & 1 deletion kmicms/accounts/views/auth.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
from __future__ import annotations

from typing import Any

from django import http
from django.conf import settings
from django.contrib import messages
from django.contrib.auth import login
from django.contrib.auth import views as auth_views
from django.contrib.auth.models import Group, Permission
from django.shortcuts import redirect
from django.urls import reverse
from django.views import View

from accounts.models import User
from accounts.oauth import oauth_config


class LoginView(auth_views.LoginView):
Expand All @@ -21,6 +31,65 @@ def dispatch(self, request: http.HttpRequest, *args: Any, **kwargs: Any) -> http

# If SSO is enabled, redirect immediately.
if not settings.USE_CONVENTIONAL_AUTH:
return redirect("oidc_authentication_init")
redirect_uri = request.build_absolute_uri(reverse("accounts:sso_oidc_redirect"))
return oauth_config.sown.authorize_redirect(request, redirect_uri)

return super().dispatch(request, *args, **kwargs)


class SSOOIDCRedirectView(View):
def get(self, request: http.HttpRequest) -> http.HttpResponseRedirect:
token = oauth_config.sown.authorize_access_token(request)
userinfo = token.get("userinfo", {})

try:
username = userinfo["sub"]
except KeyError:
messages.error(request, "Invalid response from SSO.")
return redirect("accounts:login")

user, _ = User.objects.get_or_create(username=username)

self.update_user(user, userinfo)

login(request, user)

messages.info(request, f"Signed in via SOWN SSO. Welcome {user.get_short_name()}")
return redirect("accounts:profile")

def update_user(self, user: User, claims: dict[str, bool | str | list[str]]) -> User:
full_name = claims.get("given_name", "")

name_parts = full_name.split(" ")

if len(name_parts) == 0:
return user
elif len(name_parts) == 1:
user.first_name = name_parts[0]
user.last_name = ""
else:
user.first_name = name_parts.pop(0)
user.last_name = " ".join(name_parts)

sso_groups = claims.get("groups", [])
is_staff = settings.SSO_STAFF_GROUP_NAME in sso_groups
is_superuser = settings.SSO_SUPERUSER_GROUP_NAME in sso_groups

user.email = claims.get("email")
user.is_staff = is_staff
user.is_superuser = is_superuser

user.save()

if is_staff:
user.groups.add(self._get_staff_group())
else:
user.groups.clear()

return user

def _get_staff_group(self) -> Group:
staff_group, _ = Group.objects.get_or_create(name="Wagtail Staff")
permission = Permission.objects.get(name="Can access Wagtail admin")
staff_group.permissions.add(permission)
return staff_group
32 changes: 6 additions & 26 deletions kmicms/kmicms/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@
"taggit",
"django.contrib.admin",
"django.contrib.auth",
"mozilla_django_oidc",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.sitemaps",
Expand All @@ -132,8 +131,6 @@
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"django.middleware.security.SecurityMiddleware",
"wagtail.contrib.redirects.middleware.RedirectMiddleware",
# Make sure to check for deauthentication during a session:
"mozilla_django_oidc.middleware.SessionRefresh",
]

if DEBUG:
Expand Down Expand Up @@ -186,31 +183,14 @@
},
]

AUTHENTICATION_BACKENDS = (
"django.contrib.auth.backends.ModelBackend",
"accounts.oidc.SOWNOIDCAuthenticationBackend",
)
# SSO configuration

# Mozilla OpenID Connect/Auth0 configuration
USE_CONVENTIONAL_AUTH = not getattr(configuration, "SSO_ENABLED", False)

USE_CONVENTIONAL_AUTH = not getattr(configuration, "OIDC_ENABLED", False)

# disable user creating during authentication
OIDC_CREATE_USER = True

# How frequently do we check with the provider that the user still exists.
OIDC_RENEW_ID_TOKEN_EXPIRY_SECONDS = 15 * 60

OIDC_RP_SIGN_ALGO = "RS256"
OIDC_RP_CLIENT_ID = getattr(configuration, "OIDC_RP_CLIENT_ID")
OIDC_RP_CLIENT_SECRET = getattr(configuration, "OIDC_RP_CLIENT_SECRET")

OIDC_OP_AUTHORIZATION_ENDPOINT = "https://sso.sown.org.uk/application/o/authorize/"
OIDC_OP_TOKEN_ENDPOINT = "https://sso.sown.org.uk/application/o/token/" # noqa: S105
OIDC_OP_USER_ENDPOINT = "https://sso.sown.org.uk/application/o/userinfo/"
OIDC_OP_DOMAIN = "sso.sown.org.uk"
OIDC_OP_JWKS_ENDPOINT = "https://sso.sown.org.uk/application/o/kmicms-staging/jwks/"
OIDC_RP_SCOPES = "openid email profile"
SSO_OIDC_CONFIGURATION_URL = getattr(configuration, "SSO_OIDC_CONFIGURATION_URL")
SSO_OIDC_CLIENT_ID = getattr(configuration, "SSO_OIDC_CLIENT_ID")
SSO_OIDC_CLIENT_SECRET = getattr(configuration, "SSO_OIDC_CLIENT_SECRET")
SSO_OIDC_SCOPES = "openid email profile"

SSO_STAFF_GROUP_NAME = getattr(configuration, "SSO_STAFF_GROUP_NAME", "kmicms:staff")
SSO_SUPERUSER_GROUP_NAME = getattr(configuration, "SSO_SUPERUSER_GROUP_NAME", "kmicms:superuser")
Expand Down
2 changes: 1 addition & 1 deletion kmicms/templates/registration/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ <h1 class="heading text-center mb-5">{% trans "Log in" %}</h1>
</form>
{% else %}
{# The view should automatically redirect to SSO anyway, but in case it doesn't... #}
<a href="{% url 'oidc_authentication_init' %}" class="btn btn-primary w-100">
<a href="{% url 'accounts:login' %}" class="btn btn-primary w-100">
<em>{% trans 'Sign in with SOWN SSO' %}</em>
</a>
{% endif %}
Expand Down
2 changes: 1 addition & 1 deletion kmicms/templates/wagtailadmin/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
{% if conventional_auth_enabled %}
{{ block.super }}
{% else %}
<a href="{% url 'oidc_authentication_init' %}" class="button button-longrunning" data-clicked-text="{% trans 'Signing in…' %}">
<a href="{% url 'accounts:login' %}" class="button button-longrunning" data-clicked-text="{% trans 'Signing in…' %}">
<span class="icon icon-spinner"></span>
<em>{% trans 'Sign in with SOWN SSO' %}</em>
</a>
Expand Down
4 changes: 2 additions & 2 deletions kmicms/templates/wagtailcore/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ <h1 class="heading text-center mb-5">{% trans "Log in" %}</h1>
{% endif %}

{% if conventional_auth_enabled %}
<form method="post" action="{% url 'wagtailcore_login' %}">
<form method="post" action="{% url 'accounts:login' %}">
{% csrf_token %}
{{ form|crispy }}
<input type="submit" value="{% trans "Log in" %}" class="btn btn-primary w-100" />
<input type="hidden" name="next" value="{{ next }}" />
</form>
{% else %}
<a href="{% url 'oidc_authentication_init' %}" class="btn btn-primary w-100">
<a href="{% url 'accounts:login' %}" class="btn btn-primary w-100">
<em>{% trans 'Sign in with SOWN SSO' %}</em>
</a>
{% endif %}
Expand Down
19 changes: 0 additions & 19 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@ asgiref==3.7.2
# via
# -r requirements.txt
# django
async-timeout==4.0.3
# via
# -r requirements.txt
# redis
authlib==1.2.1
# via -r requirements.txt
beautifulsoup4==4.11.2
Expand Down Expand Up @@ -50,9 +46,6 @@ cryptography==41.0.5
# via
# -r requirements.txt
# authlib
# josepy
# mozilla-django-oidc
# pyopenssl
defusedxml==0.7.1
# via
# -r requirements.txt
Expand All @@ -70,7 +63,6 @@ django==4.2.7
# django-taggit
# django-treebeard
# djangorestframework
# mozilla-django-oidc
# wagtail
django-appconf==1.0.5
# via
Expand Down Expand Up @@ -138,10 +130,6 @@ idna==3.4
# requests
iniconfig==2.0.0
# via pytest
josepy==1.14.0
# via
# -r requirements.txt
# mozilla-django-oidc
l18n==2021.3
# via
# -r requirements.txt
Expand All @@ -150,8 +138,6 @@ libsass==0.22.0
# via
# -r requirements.txt
# django-libsass
mozilla-django-oidc==3.0.0
# via -r requirements.txt
openpyxl==3.1.2
# via
# -r requirements.txt
Expand Down Expand Up @@ -195,10 +181,6 @@ pydantic-core==2.10.1
# via
# -r requirements.txt
# pydantic
pyopenssl==23.3.0
# via
# -r requirements.txt
# josepy
pyproject-hooks==1.0.0
# via build
pytest==7.4.3
Expand All @@ -225,7 +207,6 @@ redis==5.0.1
requests==2.31.0
# via
# -r requirements.txt
# mozilla-django-oidc
# wagtail
rjsmin==1.2.1
# via
Expand Down
1 change: 0 additions & 1 deletion requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ django_compressor
django-crispy-forms
django-libsass
crispy-bootstrap5
mozilla-django-oidc

authlib>=1.2.1,<2
pydantic>=2.4,<3
Expand Down
Loading

0 comments on commit 1dac6ad

Please sign in to comment.