(backend) add option to configure list of required OIDC claims

We want to be able to refuse connection for users who have missing
claims from a list of required keys.
This commit is contained in:
Samuel Paccoud - DINUM
2024-12-21 08:55:26 +01:00
committed by Samuel Paccoud
parent 02a4740c66
commit c879f82114
4 changed files with 100 additions and 0 deletions

View File

@@ -11,6 +11,7 @@ and this project adheres to
## Added ## Added
🔧(backend) add option to configure list of required OIDC claims #525
🔧(helm) add option to disable default tls setting by @dominikkaminski #519 🔧(helm) add option to disable default tls setting by @dominikkaminski #519
## Changed ## Changed

View File

@@ -57,6 +57,18 @@ class OIDCAuthenticationBackend(MozillaOIDCAuthenticationBackend):
_("Invalid response format or token verification failed") _("Invalid response format or token verification failed")
) from e ) from e
# Validate required claims
missing_claims = [
claim
for claim in settings.USER_OIDC_REQUIRED_CLAIMS
if claim not in userinfo
]
if missing_claims:
raise SuspiciousOperation(
_("Missing required claims in user info: %(claims)s")
% {"claims": ", ".join(missing_claims)}
)
return userinfo return userinfo
def get_or_create_user(self, access_token, id_token, payload): def get_or_create_user(self, access_token, id_token, payload):

View File

@@ -365,3 +365,87 @@ def test_authentication_getter_existing_disabled_user_via_email(
klass.get_or_create_user(access_token="test-token", id_token=None, payload=None) klass.get_or_create_user(access_token="test-token", id_token=None, payload=None)
assert models.User.objects.count() == 1 assert models.User.objects.count() == 1
# Required claims
@override_settings(
OIDC_OP_USER_ENDPOINT="http://oidc.endpoint.test/userinfo",
USER_OIDC_REQUIRED_CLAIMS=["email", "sub", "address"],
)
@responses.activate
def test_authentication_get_userinfo_required_claims_missing():
"""Ensure SuspiciousOperation is raised if required claims are missing."""
responses.add(
responses.GET,
re.compile(r".*/userinfo"),
json={
"last_name": "Doe",
"email": "john.doe@example.com",
},
status=200,
)
oidc_backend = OIDCAuthenticationBackend()
with pytest.raises(
SuspiciousOperation, match="Missing required claims in user info: sub, address"
):
oidc_backend.get_userinfo("fake_access_token", None, None)
@override_settings(
OIDC_OP_USER_ENDPOINT="http://oidc.endpoint.test/userinfo",
USER_OIDC_REQUIRED_CLAIMS=["email", "Sub"],
)
@responses.activate
def test_authentication_get_userinfo_required_claims_case_sensitivity():
"""Ensure the system respects case sensitivity for required claims."""
responses.add(
responses.GET,
re.compile(r".*/userinfo"),
json={
"sub": "123",
"last_name": "Doe",
"email": "john.doe@example.com",
},
status=200,
)
oidc_backend = OIDCAuthenticationBackend()
with pytest.raises(
SuspiciousOperation, match="Missing required claims in user info: Sub"
):
oidc_backend.get_userinfo("fake_access_token", None, None)
@override_settings(
OIDC_OP_USER_ENDPOINT="http://oidc.endpoint.test/userinfo",
USER_OIDC_REQUIRED_CLAIMS=["email", "sub"],
)
@responses.activate
def test_authentication_get_userinfo_required_claims_success():
"""Ensure user is authenticated when required claims are present."""
responses.add(
responses.GET,
re.compile(r".*/userinfo"),
json={
"sub": "123",
"last_name": "Doe",
"email": "john.doe@example.com",
},
status=200,
)
oidc_backend = OIDCAuthenticationBackend()
result = oidc_backend.get_userinfo("fake_access_token", None, None)
assert result["sub"] == "123"
assert result.get("first_name") is None
assert result["last_name"] == "Doe"
assert result["email"] == "john.doe@example.com"

View File

@@ -474,6 +474,9 @@ class Base(Configuration):
environ_prefix=None, environ_prefix=None,
) )
USER_OIDC_REQUIRED_CLAIMS = values.ListValue(
default=[], environ_name="USER_OIDC_REQUIRED_CLAIMS", environ_prefix=None
)
USER_OIDC_FIELDS_TO_FULLNAME = values.ListValue( USER_OIDC_FIELDS_TO_FULLNAME = values.ListValue(
default=["first_name", "last_name"], default=["first_name", "last_name"],
environ_name="USER_OIDC_FIELDS_TO_FULLNAME", environ_name="USER_OIDC_FIELDS_TO_FULLNAME",