-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
✨[#114] add optional setup config support
- Loading branch information
Showing
6 changed files
with
195 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,11 @@ | |
Changelog | ||
========= | ||
|
||
0.20.0 (????) | ||
============= | ||
|
||
|
||
|
||
0.19.0 (2024-07-02) | ||
=================== | ||
|
||
|
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |