Skip to content

Commit 969b09d

Browse files
committed
feat: Configurable LDAP profile mapping for SSO attribute sync
Add FORMS_WORKFLOWS_LDAP_PROFILE_MAPPING setting to configure which LDAP attributes map to UserProfile fields during SSO login. This allows deployments to customize the ID number attribute (e.g., extensionAttribute1 instead of employeeID) and other profile fields. Example configuration: FORMS_WORKFLOWS_LDAP_PROFILE_MAPPING = { 'employee_id': 'extensionAttribute1', 'department': 'department', 'title': 'title', 'phone': 'telephoneNumber', 'office_location': 'physicalDeliveryOfficeName', } Bump version to 0.6.3
1 parent f2ece6f commit 969b09d

3 files changed

Lines changed: 55 additions & 40 deletions

File tree

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.6.2"
6+
__version__ = "0.6.3"
77
__author__ = "Django Forms Workflows Contributors"
88
__license__ = "LGPL-3.0-only"
99

django_forms_workflows/sso_backends.py

Lines changed: 53 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -554,10 +554,19 @@ def sync_user_ldap_attributes(user):
554554
"""
555555
Sync LDAP attributes to UserProfile for a specific user.
556556
557-
This fetches key attributes from LDAP (mail, department, title, etc.)
558-
and stores them in the user's UserProfile. This is especially useful
559-
for SSO users who authenticate via Google/SAML but need LDAP attributes
560-
for form prefilling.
557+
This fetches key attributes from LDAP and stores them in the user's
558+
UserProfile. This is especially useful for SSO users who authenticate
559+
via Google/SAML but need LDAP attributes for form prefilling.
560+
561+
The mapping of UserProfile fields to LDAP attributes is configurable via:
562+
563+
FORMS_WORKFLOWS_LDAP_PROFILE_MAPPING = {
564+
'employee_id': 'extensionAttribute1', # Custom ID number attribute
565+
'department': 'department',
566+
'title': 'title',
567+
'phone': 'telephoneNumber',
568+
'office_location': 'physicalDeliveryOfficeName',
569+
}
561570
562571
Args:
563572
user: Django User object
@@ -582,6 +591,25 @@ def sync_user_ldap_attributes(user):
582591
logger.debug("python-ldap not installed, skipping attribute sync")
583592
return None
584593

594+
# Default mapping of UserProfile fields to LDAP attributes
595+
default_mapping = {
596+
"employee_id": "employeeID",
597+
"department": "department",
598+
"title": "title",
599+
"phone": "telephoneNumber",
600+
"office_location": "physicalDeliveryOfficeName",
601+
}
602+
603+
# Get custom mapping from settings, falling back to defaults
604+
profile_mapping = getattr(
605+
settings, "FORMS_WORKFLOWS_LDAP_PROFILE_MAPPING", default_mapping
606+
)
607+
608+
# Merge with defaults for any missing fields
609+
for key, value in default_mapping.items():
610+
if key not in profile_mapping:
611+
profile_mapping[key] = value
612+
585613
# Get LDAP connection settings
586614
bind_dn = getattr(settings, "AUTH_LDAP_BIND_DN", "")
587615
bind_password = getattr(settings, "AUTH_LDAP_BIND_PASSWORD", "")
@@ -628,19 +656,11 @@ def sync_user_ldap_attributes(user):
628656
raise
629657

630658
try:
631-
# Attributes to fetch from LDAP
632-
ldap_attrs = [
633-
"mail",
634-
"department",
635-
"title",
636-
"telephoneNumber",
637-
"mobile",
638-
"employeeID",
639-
"physicalDeliveryOfficeName",
640-
"company",
641-
"givenName",
642-
"sn",
643-
]
659+
# Build list of LDAP attributes to fetch from the mapping
660+
# Include additional common attributes for synced_attrs dict
661+
ldap_attrs = list(set(profile_mapping.values()))
662+
ldap_attrs.extend(["mail", "mobile", "company", "givenName", "sn"])
663+
ldap_attrs = list(set(ldap_attrs)) # Remove duplicates
644664

645665
# Search for the user
646666
search_filter = f"(sAMAccountName={escape_filter_chars(user.username)})"
@@ -668,37 +688,32 @@ def get_attr(attrs, name):
668688
return val[0]
669689
return None
670690

671-
# Map LDAP attributes
691+
# Build synced_attrs from LDAP response using the configured mapping
692+
for profile_field, ldap_attr in profile_mapping.items():
693+
synced_attrs[profile_field] = get_attr(user_attrs, ldap_attr)
694+
695+
# Also include common attributes for logging/debugging
672696
synced_attrs["mail"] = get_attr(user_attrs, "mail")
673-
synced_attrs["department"] = get_attr(user_attrs, "department")
674-
synced_attrs["title"] = get_attr(user_attrs, "title")
675-
synced_attrs["phone"] = get_attr(user_attrs, "telephoneNumber")
676697
synced_attrs["mobile"] = get_attr(user_attrs, "mobile")
677-
synced_attrs["employee_id"] = get_attr(user_attrs, "employeeID")
678-
synced_attrs["office_location"] = get_attr(
679-
user_attrs, "physicalDeliveryOfficeName"
680-
)
681698
synced_attrs["company"] = get_attr(user_attrs, "company")
682699
synced_attrs["first_name"] = get_attr(user_attrs, "givenName")
683700
synced_attrs["last_name"] = get_attr(user_attrs, "sn")
684701

685702
# Update or create UserProfile
686703
profile, created = UserProfile.objects.get_or_create(user=user)
687704

688-
# Sync attributes to profile
689-
profile_fields = {
690-
"department": synced_attrs.get("department"),
691-
"title": synced_attrs.get("title"),
692-
"phone": synced_attrs.get("phone"),
693-
"employee_id": synced_attrs.get("employee_id"),
694-
"office_location": synced_attrs.get("office_location"),
695-
}
696-
705+
# Sync attributes to profile using the configured mapping
697706
updated = False
698-
for field, value in profile_fields.items():
699-
if value and hasattr(profile, field):
700-
setattr(profile, field, value)
701-
updated = True
707+
for profile_field in profile_mapping.keys():
708+
value = synced_attrs.get(profile_field)
709+
if value and hasattr(profile, profile_field):
710+
current_value = getattr(profile, profile_field, None)
711+
if current_value != value:
712+
setattr(profile, profile_field, value)
713+
updated = True
714+
logger.debug(
715+
f"Updated {profile_field} for user '{user.username}': {value}"
716+
)
702717

703718
if updated or created:
704719
profile.save()

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.6.2"
3+
version = "0.6.3"
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)