@@ -52,7 +52,7 @@ def get_value(self, user, field_name: str, **kwargs) -> Any | None:
5252
5353 Args:
5454 user: Django User object
55- field_name: LDAP attribute name (e.g., 'department', 'title')
55+ field_name: LDAP attribute name (e.g., 'department', 'title', 'mail' )
5656 **kwargs: Unused
5757
5858 Returns:
@@ -61,13 +61,15 @@ def get_value(self, user, field_name: str, **kwargs) -> Any | None:
6161 if not user or not user .is_authenticated :
6262 return None
6363
64- if not self .is_available ():
65- logger .warning (
66- "LDAP data source is not available (django-auth-ldap not configured)"
67- )
68- return None
64+ # Map friendly name to LDAP attribute
65+ ldap_attr = self .ATTRIBUTE_MAP .get (field_name , field_name )
6966
7067 try :
68+ # For 'mail', try user.email first (usually synced from LDAP/SSO)
69+ if field_name == "mail" or ldap_attr == "mail" :
70+ if user .email :
71+ return user .email
72+
7173 # Try to get from user profile first (cached LDAP data)
7274 if hasattr (user , "forms_profile" ):
7375 profile = user .forms_profile
@@ -76,33 +78,107 @@ def get_value(self, user, field_name: str, **kwargs) -> Any | None:
7678 if value :
7779 return value
7880
79- # Try to get from LDAP backend directly
81+ # Try to get from LDAP backend directly (for LDAP-authenticated users)
8082 ldap_user = getattr (user , "ldap_user" , None )
81- if not ldap_user :
82- logger .debug (f"User { user .username } has no LDAP user object" )
83- return None
83+ if ldap_user :
84+ # Special handling for manager email
85+ if field_name == "manager_email" :
86+ return self ._get_manager_email (ldap_user )
87+
88+ # Get attribute from cached LDAP attrs
89+ if hasattr (ldap_user , "attrs" ) and ldap_attr in ldap_user .attrs :
90+ value = ldap_user .attrs [ldap_attr ]
91+ # LDAP attributes are often lists
92+ if isinstance (value , list ) and value :
93+ return value [0 ]
94+ return value
95+
96+ # For SSO users without ldap_user, query LDAP directly
97+ return self ._query_ldap_attribute (user .username , ldap_attr )
8498
85- # Map friendly name to LDAP attribute
86- ldap_attr = self .ATTRIBUTE_MAP .get (field_name , field_name )
99+ except Exception as e :
100+ logger .error (f"Error getting LDAP attribute { field_name } : { e } " )
101+ return None
87102
88- # Special handling for manager email
89- if field_name == "manager_email" :
90- return self . _get_manager_email ( ldap_user )
103+ def _query_ldap_attribute ( self , username : str , ldap_attr : str ) -> Any | None :
104+ """
105+ Query LDAP directly for a user attribute.
91106
92- # Get attribute from LDAP
93- if hasattr (ldap_user , "attrs" ) and ldap_attr in ldap_user .attrs :
94- value = ldap_user .attrs [ldap_attr ]
95- # LDAP attributes are often lists
96- if isinstance (value , list ) and value :
97- return value [0 ]
98- return value
107+ This is used for SSO users who don't have a cached ldap_user object.
108+ """
109+ import os
99110
100- logger .debug (f"LDAP attribute not found: { ldap_attr } " )
111+ try :
112+ import ldap
113+ from ldap .filter import escape_filter_chars
114+ except ImportError :
115+ logger .debug ("python-ldap not installed" )
101116 return None
102117
103- except Exception as e :
104- logger .error (f"Error getting LDAP attribute { field_name } : { e } " )
118+ ldap_server = getattr (settings , "AUTH_LDAP_SERVER_URI" , None )
119+ if not ldap_server :
120+ return None
121+
122+ bind_dn = getattr (settings , "AUTH_LDAP_BIND_DN" , "" )
123+ bind_password = getattr (settings , "AUTH_LDAP_BIND_PASSWORD" , "" )
124+
125+ # Get search base
126+ user_search = getattr (settings , "AUTH_LDAP_USER_SEARCH" , None )
127+ search_base = None
128+ if user_search and hasattr (user_search , "base_dn" ):
129+ search_base = user_search .base_dn
130+ if not search_base :
131+ search_base = getattr (settings , "AUTH_LDAP_USER_SEARCH_BASE" , None )
132+ if not search_base and bind_dn :
133+ parts = bind_dn .split ("," )
134+ dc_parts = [p for p in parts if p .strip ().upper ().startswith ("DC=" )]
135+ if dc_parts :
136+ search_base = "," .join (dc_parts )
137+
138+ if not search_base :
139+ logger .debug ("Could not determine LDAP search base" )
140+ return None
141+
142+ try :
143+ conn = ldap .initialize (ldap_server )
144+ conn .set_option (ldap .OPT_REFERRALS , 0 )
145+ conn .set_option (ldap .OPT_PROTOCOL_VERSION , ldap .VERSION3 )
146+
147+ # Configure TLS
148+ tls_require_cert = os .getenv ("LDAP_TLS_REQUIRE_CERT" , "demand" ).lower ()
149+ if tls_require_cert == "never" :
150+ conn .set_option (ldap .OPT_X_TLS_REQUIRE_CERT , ldap .OPT_X_TLS_NEVER )
151+
152+ if bind_dn and bind_password :
153+ conn .simple_bind_s (bind_dn , bind_password )
154+ else :
155+ conn .simple_bind_s ("" , "" )
156+
157+ search_filter = f"(sAMAccountName={ escape_filter_chars (username )} )"
158+ result = conn .search_s (
159+ search_base , ldap .SCOPE_SUBTREE , search_filter , [ldap_attr ]
160+ )
161+
162+ if result and result [0 ][0 ]:
163+ attrs = result [0 ][1 ]
164+ if ldap_attr in attrs :
165+ value = attrs [ldap_attr ]
166+ if isinstance (value , list ) and value :
167+ val = value [0 ]
168+ if isinstance (val , bytes ):
169+ return val .decode ("utf-8" )
170+ return val
171+
172+ return None
173+
174+ except ldap .LDAPError as e :
175+ logger .debug (f"LDAP query failed for { username } .{ ldap_attr } : { e } " )
105176 return None
177+ finally :
178+ try :
179+ conn .unbind_s ()
180+ except Exception :
181+ pass
106182
107183 def _get_manager_email (self , ldap_user ) -> str | None :
108184 """
0 commit comments