Fixed #28207 -- Fixed contrib.auth.authenticate() if multiple auth backends don't accept a request.

This commit is contained in:
Tamas Szabo 2017-05-15 06:22:58 +08:00 committed by Tim Graham
parent a7975260b5
commit 3008f30f19
3 changed files with 55 additions and 31 deletions

View File

@ -66,38 +66,8 @@ def authenticate(request=None, **credentials):
If the given credentials are valid, return a User object. If the given credentials are valid, return a User object.
""" """
for backend, backend_path in _get_backends(return_tuples=True): for backend, backend_path in _get_backends(return_tuples=True):
args = (request,)
# Does the backend accept a request argument?
try: try:
inspect.getcallargs(backend.authenticate, request, **credentials) user = _authenticate_with_backend(backend, backend_path, request, **credentials)
except TypeError:
args = ()
# Does the backend accept a request keyword argument?
try:
inspect.getcallargs(backend.authenticate, request=request, **credentials)
except TypeError:
# Does the backend accept credentials without request?
try:
inspect.getcallargs(backend.authenticate, **credentials)
except TypeError:
# This backend doesn't accept these credentials as arguments. Try the next one.
continue
else:
warnings.warn(
"Update %s.authenticate() to accept a positional "
"`request` argument." % backend_path,
RemovedInDjango21Warning
)
else:
credentials['request'] = request
warnings.warn(
"In %s.authenticate(), move the `request` keyword argument "
"to the first positional argument." % backend_path,
RemovedInDjango21Warning
)
try:
user = backend.authenticate(*args, **credentials)
except PermissionDenied: except PermissionDenied:
# This backend says to stop in our tracks - this user should not be allowed in at all. # This backend says to stop in our tracks - this user should not be allowed in at all.
break break
@ -111,6 +81,39 @@ def authenticate(request=None, **credentials):
user_login_failed.send(sender=__name__, credentials=_clean_credentials(credentials), request=request) user_login_failed.send(sender=__name__, credentials=_clean_credentials(credentials), request=request)
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 = ()
# Does the backend accept a request keyword argument?
try:
inspect.getcallargs(backend.authenticate, request=request, **credentials)
except TypeError:
# Does the backend accept credentials without request?
try:
inspect.getcallargs(backend.authenticate, **credentials)
except TypeError:
# This backend doesn't accept these credentials as arguments. Try the next one.
return None
else:
warnings.warn(
"Update %s.authenticate() to accept a positional "
"`request` argument." % backend_path,
RemovedInDjango21Warning
)
else:
credentials['request'] = request
warnings.warn(
"In %s.authenticate(), move the `request` keyword argument "
"to the first positional argument." % backend_path,
RemovedInDjango21Warning
)
return backend.authenticate(*args, **credentials)
def login(request, user, backend=None): def login(request, user, backend=None):
""" """
Persist a user id and a backend in the request. This way a user doesn't Persist a user id and a backend in the request. This way a user doesn't

View File

@ -20,3 +20,6 @@ Bugfixes
(:ticket:`28142`). (:ticket:`28142`).
* Fixed regression causing pickling of model fields to crash (:ticket:`28188`). * Fixed regression causing pickling of model fields to crash (:ticket:`28188`).
* Fixed ``django.contrib.auth.authenticate()`` when multiple authentication
backends don't accept a positional ``request`` argument (:ticket:`28207`).

View File

@ -50,3 +50,21 @@ class AcceptsRequestBackendTest(SimpleTestCase):
"In %s.authenticate(), move the `request` keyword argument to the " "In %s.authenticate(), move the `request` keyword argument to the "
"first positional argument." % self.request_not_positional_backend "first positional argument." % self.request_not_positional_backend
) )
@override_settings(AUTHENTICATION_BACKENDS=[request_not_positional_backend, no_request_backend])
def test_both_types_of_deprecation_warning(self):
with warnings.catch_warnings(record=True) as warns:
warnings.simplefilter('always')
authenticate(mock_request, username='username', password='pass')
self.assertEqual(len(warns), 2)
self.assertEqual(
str(warns[0].message),
"In %s.authenticate(), move the `request` keyword argument to the "
"first positional argument." % self.request_not_positional_backend
)
self.assertEqual(
str(warns[1].message),
"Update %s.authenticate() to accept a positional `request` "
"argument." % self.no_request_backend
)