Skip to content

Commit

Permalink
✨[#114] add optional setup config support
Browse files Browse the repository at this point in the history
  • Loading branch information
Coperh committed Jul 10, 2024
1 parent ed93d42 commit fc242eb
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 0 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
Changelog
=========

0.20.0 (????)
=============



0.19.0 (2024-07-02)
===================

Expand Down
Empty file.
89 changes: 89 additions & 0 deletions mozilla_django_oidc_db/setupconfig/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
from django.conf import settings
from django.contrib.auth.models import Group

from django_setup_configuration.configuration import BaseConfigurationStep
from django_setup_configuration.exceptions import ConfigurationRunFailed

from ..forms import OpenIDConnectConfigForm
from ..models import OpenIDConnectConfig


class AdminOIDCConfigurationStep(BaseConfigurationStep):
"""
Configure admin login via OpenID Connect
"""

verbose_name = "Configuration for admin login via OpenID Connect"
required_settings = [
"ADMIN_OIDC_OIDC_RP_CLIENT_ID",
"ADMIN_OIDC_OIDC_RP_CLIENT_SECRET",
]
all_settings = required_settings + [
"ADMIN_OIDC_OIDC_RP_SCOPES_LIST",
"ADMIN_OIDC_OIDC_RP_SIGN_ALGO",
"ADMIN_OIDC_OIDC_RP_IDP_SIGN_KEY",
"ADMIN_OIDC_OIDC_OP_DISCOVERY_ENDPOINT",
"ADMIN_OIDC_OIDC_OP_JWKS_ENDPOINT",
"ADMIN_OIDC_OIDC_OP_AUTHORIZATION_ENDPOINT",
"ADMIN_OIDC_OIDC_OP_TOKEN_ENDPOINT",
"ADMIN_OIDC_OIDC_OP_USER_ENDPOINT",
"ADMIN_OIDC_USERNAME_CLAIM",
"ADMIN_OIDC_GROUPS_CLAIM",
"ADMIN_OIDC_CLAIM_MAPPING",
"ADMIN_OIDC_SYNC_GROUPS",
"ADMIN_OIDC_SYNC_GROUPS_GLOB_PATTERN",
"ADMIN_OIDC_DEFAULT_GROUPS",
"ADMIN_OIDC_MAKE_USERS_STAFF",
"ADMIN_OIDC_SUPERUSER_GROUP_NAMES",
"ADMIN_OIDC_OIDC_USE_NONCE",
"ADMIN_OIDC_OIDC_NONCE_SIZE",
"ADMIN_OIDC_OIDC_STATE_SIZE",
"ADMIN_OIDC_OIDC_EXEMPT_URLS",
"ADMIN_OIDC_USERINFO_CLAIMS_SOURCE",
]
enable_setting = "ADMIN_OIDC_CONFIG_ENABLE"

def is_configured(self) -> bool:
return OpenIDConnectConfig.get_solo().enabled

def configure(self):
config = OpenIDConnectConfig.get_solo()

# Use the model defaults
form_data = {
field.name: getattr(config, field.name)
for field in OpenIDConnectConfig._meta.fields
}

# `email` is in the claim_mapping by default, but email is used as the username field
# by OIP, and you cannot map the username field when using OIDC
if "email" in form_data["claim_mapping"]:
del form_data["claim_mapping"]["email"]

# Only override field values with settings if they are defined
for setting in self.all_settings:
value = getattr(settings, setting, None)
if value is not None:
model_field_name = setting.split("ADMIN_OIDC_")[1].lower()
if model_field_name == "default_groups":
for group_name in value:
Group.objects.get_or_create(name=group_name)
value = Group.objects.filter(name__in=value)

form_data[model_field_name] = value
form_data["enabled"] = True

# Use the admin form to apply validation and fetch URLs from the discovery endpoint
form = OpenIDConnectConfigForm(data=form_data)
if not form.is_valid():
raise ConfigurationRunFailed(
f"Something went wrong while saving configuration: {form.errors.as_json()}"
)

form.save()

def test_configuration(self):
"""
TODO not sure if it is feasible (because there are different possible IdPs),
but it would be nice if we could test the login automatically
"""
34 changes: 34 additions & 0 deletions testapp/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,37 @@
LOGIN_REDIRECT_URL = reverse_lazy("admin:index")

STATIC_URL = "/static/"


# Setup Configuration Settings

IDENTITY_PROVIDER = "https://keycloak.local/realms/digid/"

ADMIN_OIDC_OIDC_RP_CLIENT_ID = "client-id"
ADMIN_OIDC_OIDC_RP_CLIENT_SECRET = "secret"
ADMIN_OIDC_OIDC_RP_SCOPES_LIST = ["open_id", "email", "profile", "extra_scope"]
ADMIN_OIDC_OIDC_RP_SIGN_ALGO = "RS256"
ADMIN_OIDC_OIDC_RP_IDP_SIGN_KEY = "key"
ADMIN_OIDC_OIDC_OP_DISCOVERY_ENDPOINT = None
ADMIN_OIDC_OIDC_OP_JWKS_ENDPOINT = f"{IDENTITY_PROVIDER}protocol/openid-connect/certs"
ADMIN_OIDC_OIDC_OP_AUTHORIZATION_ENDPOINT = (
f"{IDENTITY_PROVIDER}protocol/openid-connect/auth"
)
ADMIN_OIDC_OIDC_OP_TOKEN_ENDPOINT = (
f"{IDENTITY_PROVIDER}protocol/openid-connect/token"
)
ADMIN_OIDC_OIDC_OP_USER_ENDPOINT = (
f"{IDENTITY_PROVIDER}protocol/openid-connect/userinfo"
)
ADMIN_OIDC_USERNAME_CLAIM = ["claim_name"]
ADMIN_OIDC_GROUPS_CLAIM = ["groups_claim_name"]
ADMIN_OIDC_CLAIM_MAPPING = {"first_name": "given_name"}
ADMIN_OIDC_SYNC_GROUPS = False
ADMIN_OIDC_SYNC_GROUPS_GLOB_PATTERN = "local.groups.*"
ADMIN_OIDC_DEFAULT_GROUPS = ["Admins", "Read-only"]
ADMIN_OIDC_MAKE_USERS_STAFF = True
ADMIN_OIDC_SUPERUSER_GROUP_NAMES = ["superuser"]
ADMIN_OIDC_OIDC_USE_NONCE = False
ADMIN_OIDC_OIDC_NONCE_SIZE = 48
ADMIN_OIDC_OIDC_STATE_SIZE = 48
ADMIN_OIDC_USERINFO_CLAIMS_SOURCE = "id_token"
Empty file added tests/setupconfig/__init__.py
Empty file.
67 changes: 67 additions & 0 deletions tests/setupconfig/test_auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import pytest

from django.conf import settings

from mozilla_django_oidc_db.models import (
OpenIDConnectConfig,
UserInformationClaimsSources,
)
from mozilla_django_oidc_db.setupconfig.auth import AdminOIDCConfigurationStep

IDENTITY_PROVIDER = settings.IDENTITY_PROVIDER


@pytest.mark.django_db
def test_configure():
AdminOIDCConfigurationStep().configure()

config = OpenIDConnectConfig.get_solo()

assert config.enabled
assert config.oidc_rp_client_id == "client-id"
assert config.oidc_rp_client_secret == "secret"
assert config.oidc_rp_scopes_list == ["open_id", "email", "profile", "extra_scope"]
assert config.oidc_rp_sign_algo == "RS256"
assert config.oidc_rp_idp_sign_key == "key"
assert config.oidc_op_discovery_endpoint == ""
assert (
config.oidc_op_jwks_endpoint
== f"{IDENTITY_PROVIDER}protocol/openid-connect/certs"
)
assert (
config.oidc_op_authorization_endpoint
== f"{IDENTITY_PROVIDER}protocol/openid-connect/auth"
)
assert (
config.oidc_op_token_endpoint
== f"{IDENTITY_PROVIDER}protocol/openid-connect/token"
)
assert (
config.oidc_op_user_endpoint
== f"{IDENTITY_PROVIDER}protocol/openid-connect/userinfo"
)
assert config.username_claim == ["claim_name"]
assert config.groups_claim == ["groups_claim_name"]
assert config.claim_mapping == {"first_name": "given_name"}
assert not config.sync_groups
assert config.sync_groups_glob_pattern == "local.groups.*"
assert list(group.name for group in config.default_groups.all()) == [
"Admins",
"Read-only",
]
assert config.make_users_staff
assert config.superuser_group_names == ["superuser"]
assert not config.oidc_use_nonce
assert config.oidc_nonce_size == 48
assert config.oidc_state_size == 48
assert config.userinfo_claims_source == UserInformationClaimsSources.id_token


@pytest.mark.skip(reason="Testing config for DigiD OIDC is not implemented yet")
def test_configuration_check_ok():
raise NotImplementedError


@pytest.mark.skip(reason="Testing config for DigiD OIDC is not implemented yet")
def test_configuration_check_failures():
raise NotImplementedError

0 comments on commit fc242eb

Please sign in to comment.