@@ -32,24 +32,9 @@ def get_model(model_path: str):
32
32
try :
33
33
return apps .get_model (model_path )
34
34
except LookupError :
35
- raise ImproperlyConfigured ("SAML_USER_MODEL refers to model '%s ' that has not been installed" % model_path )
35
+ raise ImproperlyConfigured (f "SAML_USER_MODEL refers to model '{ model_path } ' that has not been installed" )
36
36
except ValueError :
37
- raise ImproperlyConfigured ("SAML_USER_MODEL must be of the form 'app_label.model_name'" )
38
-
39
-
40
- def get_saml_user_model ():
41
- """ Returns the user model specified in the settings, or the default one from this Django installation """
42
- if hasattr (settings , 'SAML_USER_MODEL' ):
43
- return get_model (settings .SAML_USER_MODEL )
44
- return auth .get_user_model ()
45
-
46
-
47
- def get_django_user_lookup_attribute (userModel ) -> str :
48
- """ Returns the attribute on which to match the identifier with for the user lookup
49
- """
50
- if hasattr (settings , 'SAML_DJANGO_USER_MAIN_ATTRIBUTE' ):
51
- return settings .SAML_DJANGO_USER_MAIN_ATTRIBUTE
52
- return getattr (userModel , 'USERNAME_FIELD' , 'username' )
37
+ raise ImproperlyConfigured (f"SAML_USER_MODEL is { model_path } , but must be of the form 'app_label.model_name'" )
53
38
54
39
55
40
def set_attribute (obj : Any , attr : str , new_value : Any ) -> bool :
@@ -70,26 +55,31 @@ def set_attribute(obj: Any, attr: str, new_value: Any) -> bool:
70
55
71
56
72
57
class Saml2Backend (ModelBackend ):
73
- def is_authorized (self , attributes , attribute_mapping ) -> bool :
74
- """ Hook to allow custom authorization policies based on SAML attributes. """
75
- return True
76
58
77
- def clean_attributes ( self , attributes : dict ) -> dict :
78
- """ Hook to clean attributes from the SAML response. """
79
- return attributes
59
+ # ############################################
60
+ # Internal logic, not meant to be overwritten
61
+ # ############################################
80
62
81
- def clean_user_main_attribute (self , main_attribute ):
82
- """ Clean the extracted user identifying value. No-op by default. """
83
- return main_attribute
63
+ @property
64
+ def _user_model (self ):
65
+ """ Returns the user model specified in the settings, or the default one from this Django installation """
66
+ if hasattr (settings , 'SAML_USER_MODEL' ):
67
+ return get_model (settings .SAML_USER_MODEL )
68
+ return auth .get_user_model ()
69
+
70
+ @property
71
+ def _user_lookup_attribute (self ) -> str :
72
+ """ Returns the attribute on which to match the identifier with when performing a user lookup """
73
+ if hasattr (settings , 'SAML_DJANGO_USER_MAIN_ATTRIBUTE' ):
74
+ return settings .SAML_DJANGO_USER_MAIN_ATTRIBUTE
75
+ return getattr (self ._user_model , 'USERNAME_FIELD' , 'username' )
84
76
85
77
def _extract_user_identifier_params (self , session_info , attributes , attribute_mapping ) -> Tuple [str , Optional [Any ]]:
86
78
""" Returns the attribute to perform a user lookup on, and the value to use for it.
87
79
The value could be the name_id, or any other saml attribute from the request.
88
80
"""
89
- UserModel = get_saml_user_model ()
90
-
91
81
# Lookup key
92
- user_lookup_key = get_django_user_lookup_attribute ( UserModel )
82
+ user_lookup_key = self . _user_lookup_attribute
93
83
94
84
# Lookup value
95
85
if getattr (settings , 'SAML_USE_NAME_ID_AS_USERNAME' , False ):
@@ -117,37 +107,6 @@ def _get_attribute_value(self, django_field, attributes, attribute_mapping):
117
107
'session is expired.' )
118
108
return saml_attribute
119
109
120
- def get_or_create_user (self , user_lookup_key , user_lookup_value , create_unknown_user , ** kwargs ) -> Tuple [Optional [settings .AUTH_USER_MODEL ], bool ]:
121
- """ Look up the user to authenticate. If he doesn't exist, this method creates him (if so desired).
122
- The default implementation looks only at the user_identifier. Override this method in order to do more complex behaviour,
123
- e.g. customize this per IdP. The kwargs contain these additional params: session_info, attribute_mapping, attributes, request.
124
- The identity provider id can be found in kwargs['session_info']['issuer]
125
- """
126
- UserModel = get_saml_user_model ()
127
-
128
- # Construct query parameters to query the userModel with. An additional lookup modifier could be specified in the settings.
129
- user_query_args = {
130
- user_lookup_key + getattr (settings , 'SAML_DJANGO_USER_MAIN_ATTRIBUTE_LOOKUP' , '' ): user_lookup_value
131
- }
132
-
133
- # Lookup existing user
134
- user , created = None , False
135
- try :
136
- user = UserModel .objects .get (** user_query_args )
137
- except MultipleObjectsReturned :
138
- logger .error ("Multiple users match, model: %s, lookup: %s" , str (UserModel ._meta ), user_query_args )
139
- except UserModel .DoesNotExist :
140
- # Create new one if desired by settings
141
- if create_unknown_user :
142
- user = UserModel (** user_query_args )
143
- created = True
144
- if created :
145
- logger .debug ('New user created: %s' , user )
146
- else :
147
- logger .error ('The user does not exist, model: %s, lookup: %s' , str (UserModel ._meta ), user_query_args )
148
-
149
- return user , created
150
-
151
110
def authenticate (self , request , session_info = None , attribute_mapping = None , create_unknown_user = True , ** kwargs ):
152
111
if session_info is None or attribute_mapping is None :
153
112
logger .info ('Session info or attribute mapping are None' )
@@ -223,8 +182,56 @@ def _update_user(self, user, attributes, attribute_mapping, force_save=False):
223
182
224
183
return user
225
184
226
- def send_user_update_signal (self , user , attributes , user_modified ) -> bool :
185
+ # ############################################
186
+ # Hooks to override by end-users in subclasses
187
+ # ############################################
188
+
189
+ def clean_attributes (self , attributes : dict ) -> dict :
190
+ """ Hook to clean or filter attributes from the SAML response. No-op by default. """
191
+ return attributes
192
+
193
+ def is_authorized (self , attributes : dict , attribute_mapping : dict ) -> bool :
194
+ """ Hook to allow custom authorization policies based on SAML attributes. True by default. """
195
+ return True
196
+
197
+ def clean_user_main_attribute (self , main_attribute : Any ) -> Any :
198
+ """ Hook to clean the extracted user-identifying value. No-op by default. """
199
+ return main_attribute
200
+
201
+ def get_or_create_user (self , user_lookup_key : str , user_lookup_value : Any , create_unknown_user : bool , ** kwargs ) -> Tuple [Optional [settings .AUTH_USER_MODEL ], bool ]:
202
+ """ Look up the user to authenticate. If he doesn't exist, this method creates him (if so desired).
203
+ The default implementation looks only at the user_identifier. Override this method in order to do more complex behaviour,
204
+ e.g. customize this per IdP. The kwargs contain these additional params: session_info, attribute_mapping, attributes, request.
205
+ The identity provider id can be found in kwargs['session_info']['issuer]
206
+ """
207
+ UserModel = self ._user_model
208
+
209
+ # Construct query parameters to query the userModel with. An additional lookup modifier could be specified in the settings.
210
+ user_query_args = {
211
+ user_lookup_key + getattr (settings , 'SAML_DJANGO_USER_MAIN_ATTRIBUTE_LOOKUP' , '' ): user_lookup_value
212
+ }
213
+
214
+ # Lookup existing user
215
+ user , created = None , False
216
+ try :
217
+ user = UserModel .objects .get (** user_query_args )
218
+ except MultipleObjectsReturned :
219
+ logger .error ("Multiple users match, model: %s, lookup: %s" , UserModel ._meta , user_query_args )
220
+ except UserModel .DoesNotExist :
221
+ # Create new one if desired by settings
222
+ if create_unknown_user :
223
+ user = UserModel (** user_query_args )
224
+ created = True
225
+ logger .debug ('New user created: %s' , user )
226
+ else :
227
+ logger .error ('The user does not exist, model: %s, lookup: %s' , UserModel ._meta , user_query_args )
228
+
229
+ return user , created
230
+
231
+ def send_user_update_signal (self , user : settings .AUTH_USER_MODEL , attributes : dict , user_modified : bool ) -> bool :
227
232
""" Send out a pre-save signal after the user has been updated with the SAML attributes.
233
+ This does not have to be overwritten, but depending on your custom implementation of get_or_create_user,
234
+ you might want to not send out this signal. In that case, just override this method to return False.
228
235
"""
229
236
logger .debug ('Sending the pre_save signal' )
230
237
signal_modified = any (
0 commit comments