✨(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:
committed by
Samuel Paccoud
parent
02a4740c66
commit
c879f82114
@@ -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
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user