Skip to content

Commit 57bbb17

Browse files
authored
Feature: make token field optional (#23)
* [feat] Add REQUIRE_TOKEN to make token field optional * [refactor] Rename REQUIRE_TOKEN to TOKEN_REQUIRED * [test] Add test to verify if token is correctly ignored in user_identity if TOKEN_REQUIRED is False
1 parent e846401 commit 57bbb17

File tree

4 files changed

+32
-10
lines changed

4 files changed

+32
-10
lines changed

README.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ How to use?
133133
'username': 'UserName',
134134
'first_name': 'FirstName',
135135
'last_name': 'LastName',
136-
'token': 'Token', # Mandatory
136+
'token': 'Token', # Mandatory, can be unrequired if TOKEN_REQUIRED is False
137137
'groups': 'Groups', # Optional
138138
},
139139
'GROUPS_MAP': { # Optionally allow mapping SAML2 Groups to Django Groups
@@ -158,6 +158,7 @@ How to use?
158158
'WANT_ASSERTIONS_SIGNED': True, # Require each assertion to be signed
159159
'WANT_RESPONSE_SIGNED': False, # Require response to be signed
160160
'ALLOWED_REDIRECT_HOSTS': ["https://myfrontendclient.com"] # Allowed hosts to redirect to using the ?next parameter
161+
'TOKEN_REQUIRED': True, # Whether or not to require the token parameter in the SAML assertion
161162
}
162163
163164
#. In your SAML2 SSO identity provider, set the Single-sign-on URL and Audience URI (SP Entity ID) to http://your-domain/saml2_auth/acs/
@@ -221,6 +222,8 @@ With these params your client can now authenticate with server resources.
221222

222223
**ACCEPTED_TIME_DIFF** Sets the accepted time diff in seconds `PySaml2 Accepted Time Diff <https://pysaml2.readthedocs.io/en/latest/howto/config.html#accepted-time-diff>`_
223224

225+
**TOKEN_REQUIRED** Set this to the boolean False if you don't require the token parameter in the SAML assertion.
226+
224227
Customize
225228
=========
226229

django_saml2_auth/saml.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -292,18 +292,26 @@ def extract_user_identity(user_identity: Dict[str, Any]) -> Dict[str, Optional[A
292292
for backwards compatibility
293293
"""
294294
saml2_auth_settings = settings.SAML2_AUTH
295-
email_field = dictor(saml2_auth_settings, "ATTRIBUTES_MAP.email", default="user.email")
296-
username_field = dictor(saml2_auth_settings, "ATTRIBUTES_MAP.username", default="user.username")
297-
firstname_field = dictor(saml2_auth_settings, "ATTRIBUTES_MAP.first_name", default="user.first_name")
298-
lastname_field = dictor(saml2_auth_settings, "ATTRIBUTES_MAP.last_name", default="user.last_name")
299-
token_field = dictor(saml2_auth_settings, "ATTRIBUTES_MAP.token", default="token")
295+
296+
email_field = dictor(
297+
saml2_auth_settings, "ATTRIBUTES_MAP.email", default="user.email")
298+
username_field = dictor(
299+
saml2_auth_settings, "ATTRIBUTES_MAP.username", default="user.username")
300+
firstname_field = dictor(
301+
saml2_auth_settings, "ATTRIBUTES_MAP.first_name", default="user.first_name")
302+
lastname_field = dictor(
303+
saml2_auth_settings, "ATTRIBUTES_MAP.last_name", default="user.last_name")
300304

301305
user = {}
302306
user["email"] = dictor(user_identity, f"{email_field}/0", pathsep="/") # Path includes "."
303307
user["username"] = dictor(user_identity, f"{username_field}/0", pathsep="/")
304308
user["first_name"] = dictor(user_identity, f"{firstname_field}/0", pathsep="/")
305309
user["last_name"] = dictor(user_identity, f"{lastname_field}/0", pathsep="/")
306-
user["token"] = dictor(user_identity, f"{token_field}.0")
310+
311+
TOKEN_REQUIRED = dictor(saml2_auth_settings, "TOKEN_REQUIRED", default=True)
312+
if TOKEN_REQUIRED:
313+
token_field = dictor(saml2_auth_settings, "ATTRIBUTES_MAP.token", default="token")
314+
user["token"] = dictor(user_identity, f"{token_field}.0")
307315

308316
if user["email"]:
309317
user["email"] = user["email"].lower()
@@ -321,7 +329,7 @@ def extract_user_identity(user_identity: Dict[str, Any]) -> Dict[str, Optional[A
321329
"status_code": 422
322330
})
323331

324-
if not user["token"]:
332+
if TOKEN_REQUIRED and not user.get("token"):
325333
raise SAMLAuthError("No token specified.", extra={
326334
"exc_type": ValueError,
327335
"error_code": NO_TOKEN_SPECIFIED,

django_saml2_auth/tests/settings.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,5 +99,6 @@
9999
"WANT_RESPONSE_SIGNED": True,
100100
"ALLOWED_REDIRECT_HOSTS": ["https://app.example.com",
101101
"https://api.example.com",
102-
"https://example.com"]
102+
"https://example.com"],
103+
"TOKEN_REQUIRED": True
103104
}

django_saml2_auth/tests/test_saml.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ def test_validate_metadata_url_failure():
185185
URL."""
186186
responses.add(responses.GET, METADATA_URL1)
187187
result = validate_metadata_url(METADATA_URL1)
188-
assert result == False
188+
assert result is False
189189

190190

191191
@responses.activate
@@ -365,3 +365,13 @@ def test_extract_user_identity_success():
365365
assert result["last_name"] == "Doe"
366366
assert result["token"] == "TOKEN"
367367
assert result["user_identity"] == get_user_identity()
368+
369+
370+
def test_extract_user_identity_token_not_required(settings: SettingsWrapper):
371+
"""Test extract_user_identity function to verify if it correctly extracts user identity
372+
information from a (pysaml2) parsed SAML response when token is not required."""
373+
settings.SAML2_AUTH["TOKEN_REQUIRED"] = False
374+
375+
result = extract_user_identity(get_user_identity())
376+
assert len(result) == 5
377+
assert "token" not in result

0 commit comments

Comments
 (0)