This commit is contained in:
2026-10-23 17:23:29 -04:00
parent ae25026d9f
commit caf8839edd
4 changed files with 34 additions and 4 deletions
+2 -1
View File
@@ -13,7 +13,8 @@ POSTGRES_HOST=membership-db
# Compose's local database is on an isolated network. For a remote database use
# verify-full and mount its trusted root certificate via PGSSLROOTCERT.
POSTGRES_SSLMODE=prefer
DEX_ISSUER=https://dex.eom.dev
# Must match discovery's issuer exactly, including its trailing slash.
DEX_ISSUER=https://dex.eom.dev/
DEX_CLIENT_ID=www-membership
DEX_CLIENT_SECRET=
DEX_CONNECTOR_ID=www-ldap
+3 -1
View File
@@ -77,7 +77,9 @@ EMAIL_USE_TLS = os.environ.get("EMAIL_USE_TLS", "1") == "1"
EMAIL_TIMEOUT = 10
DEFAULT_FROM_EMAIL = os.environ.get("DEFAULT_FROM_EMAIL", "accounts@eom.dev")
DEX_ISSUER = os.environ.get("DEX_ISSUER", "https://dex.eom.dev").rstrip("/")
# OIDC issuer identifiers are exact strings, including a trailing slash.
# Never normalize this value: discovery and ID-token iss must match it exactly.
DEX_ISSUER = os.environ.get("DEX_ISSUER", "https://dex.eom.dev/")
if urlsplit(DEX_ISSUER).scheme != "https" or not urlsplit(DEX_ISSUER).hostname:
raise ImproperlyConfigured("DEX_ISSUER must use HTTPS.")
DEX_CLIENT_ID = os.environ.get("DEX_CLIENT_ID", "www-membership")
+28 -1
View File
@@ -6,7 +6,7 @@ from urllib.parse import parse_qs, urlsplit
from authlib.integrations.django_client import OAuth
from django.conf import settings
from django.contrib.sessions.backends.db import SessionStore
from django.test import RequestFactory, TestCase
from django.test import RequestFactory, TestCase, override_settings
from joserfc import jwt
from joserfc.jwk import RSAKey
@@ -71,3 +71,30 @@ class AuthlibFlowTests(TestCase):
token["id_token"] = jwt.encode({"alg": "RS256", "kid": "test-key"}, claims, other)
with self.assertRaises(Exception):
self.exchange(remote, callback, token)
class DiscoveryIssuerTests(TestCase):
def test_settings_preserve_exact_configured_issuer(self):
import importlib
import os
settings_module = importlib.import_module("config.settings")
try:
with patch.dict(os.environ, {"DEX_ISSUER": "https://dex.example.org/"}):
importlib.reload(settings_module)
self.assertEqual(settings_module.DEX_ISSUER, "https://dex.example.org/")
finally:
importlib.reload(settings_module)
@override_settings(DEX_ISSUER="https://dex.example.org/")
@patch("members.views.OAuth")
def test_discovery_uses_single_slash_and_exact_issuer(self, oauth):
from members.views import dex
metadata = {"issuer": "https://dex.example.org/", "authorization_endpoint": "https://dex.example.org/auth", "token_endpoint": "https://dex.example.org/token", "jwks_uri": "https://dex.example.org/keys"}
oauth.return_value.register.return_value.load_server_metadata.return_value = metadata
dex()
self.assertEqual(oauth.return_value.register.call_args.kwargs["server_metadata_url"], "https://dex.example.org/.well-known/openid-configuration")
metadata["issuer"] = "https://dex.example.org"
with self.assertRaisesRegex(ValueError, "issuer mismatch"):
dex()
+1 -1
View File
@@ -140,7 +140,7 @@ def finish_registration(request):
def dex():
oauth = OAuth()
remote = oauth.register("dex", client_id=settings.DEX_CLIENT_ID, client_secret=settings.DEX_CLIENT_SECRET, server_metadata_url=settings.DEX_ISSUER + "/.well-known/openid-configuration", client_kwargs={"scope": "openid email profile groups federated:id", "code_challenge_method": "S256", "timeout": 10})
remote = oauth.register("dex", client_id=settings.DEX_CLIENT_ID, client_secret=settings.DEX_CLIENT_SECRET, server_metadata_url=settings.DEX_ISSUER.rstrip("/") + "/.well-known/openid-configuration", client_kwargs={"scope": "openid email profile groups federated:id", "code_challenge_method": "S256", "timeout": 10})
metadata = remote.load_server_metadata()
if metadata.get("issuer") != settings.DEX_ISSUER:
raise ValueError("Dex discovery issuer mismatch.")