@@ -172,3 +172,168 @@ def authenticate(*args, **kwars):
172
172
# Expect the log we emit
173
173
with expected_log ('ansible_base.authentication.backend.logger' , "exception" , "Exception raised while trying to authenticate with" ):
174
174
backend .AnsibleBaseAuth ().authenticate (None )
175
+
176
+
177
+ @pytest .mark .django_db
178
+ def test_last_login_from_with_attribute (local_authenticator , random_user , expected_log ):
179
+ """Test that last_login_from is set when user has the attribute."""
180
+ # Add last_login_from attribute to user
181
+ random_user .last_login_from = None
182
+
183
+ # Create a mock authenticator plugin object with proper structure
184
+ mock_authenticator_plugin = mock .MagicMock ()
185
+ mock_authenticator_plugin .authenticate .return_value = random_user
186
+ mock_authenticator_plugin .database_instance = local_authenticator
187
+
188
+ # Mock the save method to track calls
189
+ with mock .patch .object (random_user , 'save' ) as mock_save :
190
+ with mock .patch (
191
+ "ansible_base.authentication.backend.get_authentication_backends" ,
192
+ return_value = {local_authenticator .id : mock_authenticator_plugin },
193
+ ):
194
+ # Expected log message when user logs in
195
+ with expected_log (
196
+ 'ansible_base.authentication.backend.logger' ,
197
+ "info" ,
198
+ f'User { random_user .username } logged in from authenticator with ID "{ local_authenticator .id } "' ,
199
+ ):
200
+ auth_return = backend .AnsibleBaseAuth ().authenticate (None )
201
+
202
+ # Verify the user is returned
203
+ assert auth_return == random_user
204
+
205
+ # Verify last_login_from was set to the authenticator database instance
206
+ assert random_user .last_login_from == local_authenticator
207
+
208
+ # Verify save was called with the correct update_fields
209
+ mock_save .assert_called_once_with (update_fields = ['last_login_from' ])
210
+
211
+
212
+ @pytest .mark .django_db
213
+ def test_last_login_from_without_attribute (local_authenticator , random_user , expected_log ):
214
+ """Test that authentication works normally when user doesn't have last_login_from attribute."""
215
+ # Ensure user doesn't have last_login_from attribute
216
+ if hasattr (random_user , 'last_login_from' ):
217
+ delattr (random_user , 'last_login_from' )
218
+
219
+ # Create a mock authenticator plugin object with proper structure
220
+ mock_authenticator_plugin = mock .MagicMock ()
221
+ mock_authenticator_plugin .authenticate .return_value = random_user
222
+ mock_authenticator_plugin .database_instance = local_authenticator
223
+
224
+ # Mock the save method to track calls
225
+ with mock .patch .object (random_user , 'save' ) as mock_save :
226
+ with mock .patch (
227
+ "ansible_base.authentication.backend.get_authentication_backends" ,
228
+ return_value = {local_authenticator .id : mock_authenticator_plugin },
229
+ ):
230
+ with expected_log (
231
+ 'ansible_base.authentication.backend.logger' ,
232
+ "info" ,
233
+ f'User { random_user .username } logged in from authenticator with ID "{ local_authenticator .id } "' ,
234
+ ):
235
+ auth_return = backend .AnsibleBaseAuth ().authenticate (None )
236
+
237
+ # Verify the user is returned
238
+ assert auth_return == random_user
239
+
240
+ # Verify save was never called since user doesn't have last_login_from
241
+ mock_save .assert_not_called ()
242
+
243
+
244
+ @pytest .mark .django_db
245
+ def test_last_login_from_multiple_authenticators (local_authenticator , github_enterprise_authenticator , random_user , expected_log ):
246
+ """Test that last_login_from is set correctly when multiple authenticators are present."""
247
+ # Add last_login_from attribute to user
248
+ random_user .last_login_from = None
249
+
250
+ # Create mock authenticator plugin objects with proper structure
251
+ mock_github_plugin = mock .MagicMock ()
252
+ mock_github_plugin .authenticate .return_value = None
253
+ mock_github_plugin .database_instance = github_enterprise_authenticator
254
+
255
+ mock_local_plugin = mock .MagicMock ()
256
+ mock_local_plugin .authenticate .return_value = random_user
257
+ mock_local_plugin .database_instance = local_authenticator
258
+
259
+ # Mock the save method to track calls
260
+ with mock .patch .object (random_user , 'save' ) as mock_save :
261
+ with mock .patch (
262
+ "ansible_base.authentication.backend.get_authentication_backends" ,
263
+ return_value = {github_enterprise_authenticator .id : mock_github_plugin , local_authenticator .id : mock_local_plugin },
264
+ ):
265
+ # Expected log message when user logs in
266
+ with expected_log (
267
+ 'ansible_base.authentication.backend.logger' ,
268
+ "info" ,
269
+ f'User { random_user .username } logged in from authenticator with ID "{ local_authenticator .id } "' ,
270
+ ):
271
+ auth_return = backend .AnsibleBaseAuth ().authenticate (None )
272
+
273
+ # Verify the user is returned
274
+ assert auth_return == random_user
275
+
276
+ # Verify last_login_from was set to the local authenticator (the one that succeeded)
277
+ assert random_user .last_login_from == local_authenticator
278
+
279
+ # Verify save was called with the correct update_fields
280
+ mock_save .assert_called_once_with (update_fields = ['last_login_from' ])
281
+
282
+
283
+ @pytest .mark .django_db
284
+ def test_last_login_from_inactive_user (local_authenticator , random_user ):
285
+ """Test that last_login_from is not set when user is inactive."""
286
+ # Add last_login_from attribute to user but make user inactive
287
+ random_user .last_login_from = None
288
+ random_user .is_active = False
289
+
290
+ # Create a mock authenticator plugin object with proper structure
291
+ mock_authenticator_plugin = mock .MagicMock ()
292
+ mock_authenticator_plugin .authenticate .return_value = random_user
293
+ mock_authenticator_plugin .database_instance = local_authenticator
294
+
295
+ # Mock the save method to track calls
296
+ with mock .patch .object (random_user , 'save' ) as mock_save :
297
+ with mock .patch (
298
+ "ansible_base.authentication.backend.get_authentication_backends" ,
299
+ return_value = {local_authenticator .id : mock_authenticator_plugin },
300
+ ):
301
+ auth_return = backend .AnsibleBaseAuth ().authenticate (None )
302
+
303
+ # Verify authentication failed (returns None for inactive user)
304
+ assert auth_return is None
305
+
306
+ # Verify save was never called since authentication failed
307
+ mock_save .assert_not_called ()
308
+
309
+ # Verify last_login_from was not changed
310
+ assert random_user .last_login_from is None
311
+
312
+
313
+ @pytest .mark .django_db
314
+ def test_last_login_from_social_auth_failed (local_authenticator , random_user ):
315
+ """Test that last_login_from is not set when social auth pipeline fails."""
316
+ # Add last_login_from attribute to user
317
+ random_user .last_login_from = None
318
+
319
+ # Create a mock authenticator plugin object with proper structure
320
+ mock_authenticator_plugin = mock .MagicMock ()
321
+ mock_authenticator_plugin .authenticate .return_value = SOCIAL_AUTH_PIPELINE_FAILED_STATUS
322
+ mock_authenticator_plugin .database_instance = local_authenticator
323
+
324
+ # Mock the save method to track calls
325
+ with mock .patch .object (random_user , 'save' ) as mock_save :
326
+ with mock .patch (
327
+ "ansible_base.authentication.backend.get_authentication_backends" ,
328
+ return_value = {local_authenticator .id : mock_authenticator_plugin },
329
+ ):
330
+ auth_return = backend .AnsibleBaseAuth ().authenticate (None )
331
+
332
+ # Verify authentication failed (returns None)
333
+ assert auth_return is None
334
+
335
+ # Verify save was never called since authentication failed
336
+ mock_save .assert_not_called ()
337
+
338
+ # Verify last_login_from was not changed
339
+ assert random_user .last_login_from is None
0 commit comments