Skip to content

Commit 93467c4

Browse files
committed
Fix SAML: don't require AttributeStatement, use NameID for email
Google SAML often doesn't include AttributeStatement. Changes: - Set wantAttributeStatement: False in security config - Use NameID as email fallback when attributes are empty - Check multiple attribute name variations for email/firstName/lastName Bumped to 0.5.17
1 parent b55cc66 commit 93467c4

File tree

4 files changed

+32
-6
lines changed

4 files changed

+32
-6
lines changed

django_forms_workflows/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
Enterprise-grade, database-driven form builder with approval workflows
44
"""
55

6-
__version__ = "0.5.16"
6+
__version__ = "0.5.17"
77
__author__ = "Django Forms Workflows Contributors"
88
__license__ = "LGPL-3.0-only"
99

django_forms_workflows/sso_backends.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@ def get_saml_config():
290290
"wantAssertionsSigned": True,
291291
"wantMessagesSigned": False,
292292
"wantNameIdEncrypted": False,
293+
"wantAttributeStatement": False, # Don't require attributes (Google may not send them)
293294
},
294295
),
295296
}

django_forms_workflows/sso_views.py

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -150,18 +150,43 @@ def post(self, request):
150150
return HttpResponseBadRequest("SAML authentication failed")
151151

152152
# Get user attributes from SAML response
153-
attributes = auth.get_attributes()
153+
# Note: Google SAML may not include AttributeStatement, so attributes may be empty
154+
attributes = auth.get_attributes() or {}
154155
name_id = auth.get_nameid()
155-
email = attributes.get("email", [name_id])[0]
156+
157+
# Try to get email from attributes, fall back to NameID
158+
email = None
159+
for attr_name in ["email", "Email", "emailAddress", "mail"]:
160+
if attr_name in attributes and attributes[attr_name]:
161+
email = attributes[attr_name][0]
162+
break
163+
if not email:
164+
email = name_id # NameID should be the email for Google SAML
165+
166+
if not email:
167+
return HttpResponseBadRequest("SAML response missing email/NameID")
168+
169+
# Get first/last name from attributes if available
170+
first_name = ""
171+
for attr_name in ["firstName", "FirstName", "first_name", "givenName"]:
172+
if attr_name in attributes and attributes[attr_name]:
173+
first_name = attributes[attr_name][0]
174+
break
175+
176+
last_name = ""
177+
for attr_name in ["lastName", "LastName", "last_name", "sn", "surname"]:
178+
if attr_name in attributes and attributes[attr_name]:
179+
last_name = attributes[attr_name][0]
180+
break
156181

157182
# Find or create user
158183
user_model = get_user_model()
159184
user, created = user_model.objects.get_or_create(
160185
email=email,
161186
defaults={
162187
"username": email.split("@")[0],
163-
"first_name": attributes.get("firstName", [""])[0],
164-
"last_name": attributes.get("lastName", [""])[0],
188+
"first_name": first_name,
189+
"last_name": last_name,
165190
},
166191
)
167192

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "django-forms-workflows"
3-
version = "0.5.16"
3+
version = "0.5.17"
44
description = "Enterprise-grade, database-driven form builder with approval workflows and external data integration"
55
license = "LGPL-3.0-only"
66
readme = "README.md"

0 commit comments

Comments
 (0)