diff --git a/django/contrib/auth/__init__.py b/django/contrib/auth/__init__.py index 94153d243d..a0a2cb7aff 100644 --- a/django/contrib/auth/__init__.py +++ b/django/contrib/auth/__init__.py @@ -67,7 +67,7 @@ def authenticate(request=None, **credentials): """ for backend, backend_path in _get_backends(return_tuples=True): try: - user = _authenticate_with_backend(backend, backend_path, request, **credentials) + user = _authenticate_with_backend(backend, backend_path, request, credentials) except PermissionDenied: # This backend says to stop in our tracks - this user should not be allowed in at all. break @@ -81,13 +81,14 @@ def authenticate(request=None, **credentials): user_login_failed.send(sender=__name__, credentials=_clean_credentials(credentials), request=request) -def _authenticate_with_backend(backend, backend_path, request, **credentials): +def _authenticate_with_backend(backend, backend_path, request, credentials): args = (request,) # Does the backend accept a request argument? try: inspect.getcallargs(backend.authenticate, request, **credentials) except TypeError: args = () + credentials.pop('request', None) # Does the backend accept a request keyword argument? try: inspect.getcallargs(backend.authenticate, request=request, **credentials) diff --git a/tests/auth_tests/test_auth_backends_deprecation.py b/tests/auth_tests/test_auth_backends_deprecation.py index 7ee53a0bc6..675e185e9f 100644 --- a/tests/auth_tests/test_auth_backends_deprecation.py +++ b/tests/auth_tests/test_auth_backends_deprecation.py @@ -4,6 +4,7 @@ from django.contrib.auth import authenticate from django.test import SimpleTestCase, override_settings mock_request = object() +mock_backend = object() class NoRequestBackend(object): @@ -19,6 +20,14 @@ class RequestNotPositionArgBackend: assert request is mock_request +class RequestNotPositionArgWithUsedKwargBackend: + def authenticate(self, username=None, password=None, request=None, backend=None): + assert username == 'username' + assert password == 'pass' + assert request is mock_request + assert backend is mock_backend + + class AcceptsRequestBackendTest(SimpleTestCase): """ A deprecation warning is shown for backends that have an authenticate() @@ -26,6 +35,7 @@ class AcceptsRequestBackendTest(SimpleTestCase): """ no_request_backend = '%s.NoRequestBackend' % __name__ request_not_positional_backend = '%s.RequestNotPositionArgBackend' % __name__ + request_not_positional_with_used_kwarg_backend = '%s.RequestNotPositionArgWithUsedKwargBackend' % __name__ @override_settings(AUTHENTICATION_BACKENDS=[no_request_backend]) def test_no_request_deprecation_warning(self): @@ -68,3 +78,15 @@ class AcceptsRequestBackendTest(SimpleTestCase): "Update %s.authenticate() to accept a positional `request` " "argument." % self.no_request_backend ) + + @override_settings(AUTHENTICATION_BACKENDS=[request_not_positional_with_used_kwarg_backend]) + def test_handles_backend_in_kwargs(self): + with warnings.catch_warnings(record=True) as warns: + warnings.simplefilter('always') + authenticate(username='username', password='pass', request=mock_request, backend=mock_backend) + self.assertEqual(len(warns), 1) + self.assertEqual( + str(warns[0].message), + "In %s.authenticate(), move the `request` keyword argument to the " + "first positional argument." % self.request_not_positional_with_used_kwarg_backend + )