From c0abd649a9c298396ac48f452e2871fde8556470 Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Thu, 13 Jun 2019 12:34:18 +0200 Subject: [PATCH 2/2] add request as first argument to all backends (#33992) --- src/authentic2/api_views.py | 8 +++++++- src/authentic2/backends/ldap_backend.py | 2 +- src/authentic2/backends/models_backend.py | 6 ++---- src/authentic2/forms/authentication.py | 4 +--- src/authentic2/utils.py | 9 +++++++-- src/authentic2/views.py | 4 ++-- src/authentic2_auth_fc/views.py | 16 +++++++++++----- src/authentic2_auth_oidc/backends.py | 2 +- src/authentic2_auth_oidc/views.py | 6 +++--- src/authentic2_auth_saml/backends.py | 2 +- tests/test_all.py | 6 ++++-- tests/test_api.py | 2 +- tests/test_backends.py | 2 +- tests/test_ldap.py | 3 ++- tests/test_utils.py | 3 +-- 15 files changed, 45 insertions(+), 30 deletions(-) diff --git a/src/authentic2/api_views.py b/src/authentic2/api_views.py index 439062ea..8599b13a 100644 --- a/src/authentic2/api_views.py +++ b/src/authentic2/api_views.py @@ -17,6 +17,7 @@ import logging import smtplib +import django from django.db import models from django.contrib.auth import get_user_model from django.core.exceptions import MultipleObjectsReturned @@ -34,7 +35,7 @@ from rest_framework.viewsets import ModelViewSet from rest_framework.routers import SimpleRouter from rest_framework.generics import GenericAPIView from rest_framework.response import Response -from rest_framework import permissions, status +from rest_framework import permissions, status, authentication from rest_framework.exceptions import PermissionDenied, AuthenticationFailed from rest_framework.fields import CreateOnlyDefault from rest_framework.decorators import list_route, detail_route @@ -52,6 +53,11 @@ from .models import Attribute, PasswordReset, Service from .a2_rbac.utils import get_default_ou +# Retro-compatibility with Django 1.8 +if django.VERSION < (1, 11): + authentication.authenticate = utils.authenticate + + class HookMixin(object): def get_serializer(self, *args, **kwargs): serializer = super(HookMixin, self).get_serializer(*args, **kwargs) diff --git a/src/authentic2/backends/ldap_backend.py b/src/authentic2/backends/ldap_backend.py index 47485c79..0e22fe81 100644 --- a/src/authentic2/backends/ldap_backend.py +++ b/src/authentic2/backends/ldap_backend.py @@ -1374,7 +1374,7 @@ class LDAPBackend(object): class LDAPBackendPasswordLost(LDAPBackend): - def authenticate(self, user=None): + def authenticate(self, request, user=None): if not user: return config = self.get_config() diff --git a/src/authentic2/backends/models_backend.py b/src/authentic2/backends/models_backend.py index 9d961e3f..de816885 100644 --- a/src/authentic2/backends/models_backend.py +++ b/src/authentic2/backends/models_backend.py @@ -68,10 +68,8 @@ class ModelBackend(ModelBackend): from .. import models return bool(models.PasswordReset.filter(user=user).count()) - def authenticate(self, username=None, password=None, realm=None, ou=None, **kwargs): + def authenticate(self, request, username=None, password=None, realm=None, ou=None): UserModel = get_user_model() - if username is None: - username = kwargs.get(UserModel.USERNAME_FIELD) if not username: return query = self.get_query(username=username, realm=realm, ou=ou) @@ -99,6 +97,6 @@ class ModelBackend(ModelBackend): class DummyModelBackend(ModelBackend): - def authenticate(self, user=None): + def authenticate(self, request, user=None): if user is not None: return user diff --git a/src/authentic2/forms/authentication.py b/src/authentic2/forms/authentication.py index e49bb73d..864c6549 100644 --- a/src/authentic2/forms/authentication.py +++ b/src/authentic2/forms/authentication.py @@ -21,8 +21,6 @@ from django.utils.translation import ugettext_lazy as _ from django.contrib.auth import forms as auth_forms from django.utils import html -from django.contrib.auth import authenticate - from authentic2.forms.fields import PasswordField from ..a2_rbac.models import OrganizationalUnit as OU @@ -98,7 +96,7 @@ class AuthenticationForm(auth_forms.AuthenticationForm): ou = self.cleaned_data.get('ou') if username is not None and password: - self.user_cache = authenticate(username=username, password=password, ou=ou, request=self.request) + self.user_cache = utils.authenticate(self.request, username=username, password=password, ou=ou) if self.user_cache is None: raise forms.ValidationError( self.error_messages['invalid_login'], diff --git a/src/authentic2/utils.py b/src/authentic2/utils.py index 6517db5b..983bba85 100644 --- a/src/authentic2/utils.py +++ b/src/authentic2/utils.py @@ -32,7 +32,7 @@ from django.http import HttpResponseRedirect, HttpResponse from django.core.exceptions import ImproperlyConfigured, PermissionDenied from django.http.request import QueryDict from django.contrib.auth import (REDIRECT_FIELD_NAME, login as auth_login, SESSION_KEY, - HASH_SESSION_KEY, BACKEND_SESSION_KEY, authenticate, + HASH_SESSION_KEY, BACKEND_SESSION_KEY, authenticate as dj_authenticate, get_user_model) from django import forms from django.forms.utils import ErrorList, to_current_timezone @@ -834,7 +834,7 @@ def switch_user(request, new_user): for key in (SESSION_KEY, BACKEND_SESSION_KEY, HASH_SESSION_KEY, constants.LAST_LOGIN_SESSION_KEY): switched[key] = request.session[key] - user = authenticate(user=new_user) + user = authenticate(request, user=new_user) login(request, user, 'switch') request.session[constants.SWITCH_USER_SESSION_KEY] = switched if constants.LAST_LOGIN_SESSION_KEY not in request.session: @@ -1144,3 +1144,8 @@ def get_authentication_events(request=None, session=None): if session is not None: return session.get(constants.AUTHENTICATION_EVENTS_SESSION_KEY, []) return [] + + +def authenticate(request=None, **kwargs): + # Compatibility layer with Django 1.8 + return dj_authenticate(request=request, **kwargs) diff --git a/src/authentic2/views.py b/src/authentic2/views.py index 21f7aa28..3174ce34 100644 --- a/src/authentic2/views.py +++ b/src/authentic2/views.py @@ -43,7 +43,7 @@ from django.views.decorators.debug import sensitive_post_parameters from django.contrib.auth.decorators import login_required from django.db.models.fields import FieldDoesNotExist from django.db.models.query import Q -from django.contrib.auth import get_user_model, authenticate +from django.contrib.auth import get_user_model from django.http import Http404 from django.utils.http import urlsafe_base64_decode from django.views.generic.edit import CreateView @@ -690,7 +690,7 @@ class PasswordResetConfirmView(cbv.RedirectToNextURLViewMixin, FormView): try: uid = urlsafe_base64_decode(uidb64) # use authenticate to eventually get an LDAPUser - self.user = authenticate(user=UserModel._default_manager.get(pk=uid)) + self.user = utils.authenticate(request, user=UserModel._default_manager.get(pk=uid)) except (TypeError, ValueError, OverflowError, UserModel.DoesNotExist): validlink = False diff --git a/src/authentic2_auth_fc/views.py b/src/authentic2_auth_fc/views.py index a47285df..6e3b4f54 100644 --- a/src/authentic2_auth_fc/views.py +++ b/src/authentic2_auth_fc/views.py @@ -26,7 +26,7 @@ from requests_oauthlib import OAuth2Session import django from django.views.generic import View, FormView from django.http import HttpResponseRedirect, Http404 -from django.contrib.auth import authenticate, REDIRECT_FIELD_NAME, get_user_model +from django.contrib.auth import REDIRECT_FIELD_NAME, get_user_model from django.contrib import messages from django.shortcuts import resolve_url, render from django.utils.translation import ugettext as _ @@ -402,8 +402,11 @@ class LoginOrLinkView(PopupViewMixin, FcOAuthSessionViewMixin, View): default_ou = get_default_ou() email_is_unique = a2_app_settings.A2_EMAIL_IS_UNIQUE or default_ou.email_is_unique - user = authenticate(sub=self.sub, user_info=self.user_info, - token=self.token) + user = a2_utils.authenticate( + request, + sub=self.sub, + user_info=self.user_info, + token=self.token) if not user and self.user_info.get('email') and email_is_unique: email = self.user_info['email'] User = get_user_model() @@ -428,8 +431,11 @@ class LoginOrLinkView(PopupViewMixin, FcOAuthSessionViewMixin, View): self.logger.info(u'fc link created sub %s user %s', self.sub, user) hooks.call_hooks('event', name='fc-link', user=user, sub=self.sub, request=request) - user = authenticate(sub=self.sub, user_info=self.user_info, - token=self.token) + user = a2_utils.authenticate( + request=request, + sub=self.sub, + user_info=self.user_info, + token=self.token) else: messages.warning( request, diff --git a/src/authentic2_auth_oidc/backends.py b/src/authentic2_auth_oidc/backends.py index 5720caba..5a20f24b 100644 --- a/src/authentic2_auth_oidc/backends.py +++ b/src/authentic2_auth_oidc/backends.py @@ -36,7 +36,7 @@ from . import models, utils class OIDCBackend(ModelBackend): - def authenticate(self, access_token=None, id_token=None, nonce=None, **kwargs): + def authenticate(self, request, access_token=None, id_token=None, nonce=None): logger = logging.getLogger(__name__) if id_token is None: return diff --git a/src/authentic2_auth_oidc/views.py b/src/authentic2_auth_oidc/views.py index 813fc000..b14b4276 100644 --- a/src/authentic2_auth_oidc/views.py +++ b/src/authentic2_auth_oidc/views.py @@ -23,13 +23,13 @@ import requests from django.core.urlresolvers import reverse from django.utils.translation import get_language, ugettext as _ from django.contrib import messages -from django.contrib.auth import REDIRECT_FIELD_NAME, authenticate +from django.contrib.auth import REDIRECT_FIELD_NAME from django.conf import settings from django.views.generic.base import View from django.http import HttpResponseBadRequest from authentic2.decorators import setting_enabled -from authentic2.utils import redirect, login, good_next_url +from authentic2.utils import redirect, login, good_next_url, authenticate from . import app_settings, models from .utils import get_provider, get_provider_by_issuer @@ -198,7 +198,7 @@ class LoginCallback(View): return self.continue_to_next_url() logger.info(u'got token response %s', result) access_token = result.get('access_token') - user = authenticate(access_token=access_token, nonce=nonce, id_token=result['id_token']) + user = authenticate(request, access_token=access_token, nonce=nonce, id_token=result['id_token']) if user: # remember last tokens for logout tokens = request.session.setdefault('auth_oidc', {}).setdefault('tokens', []) diff --git a/src/authentic2_auth_saml/backends.py b/src/authentic2_auth_saml/backends.py index 8893dcc0..574fbfbb 100644 --- a/src/authentic2_auth_saml/backends.py +++ b/src/authentic2_auth_saml/backends.py @@ -22,7 +22,7 @@ from . import app_settings class SAMLBackend(SAMLBackend): - def authenticate(self, saml_attributes, request=None): + def authenticate(self, request, saml_attributes): if not app_settings.enable: return None return super(SAMLBackend, self).authenticate(saml_attributes=saml_attributes, request=request) diff --git a/tests/test_all.py b/tests/test_all.py index 1d1f37a9..b5f11678 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -253,7 +253,8 @@ class UserProfileTests(TestCase): user_editable=True, user_visible=True, kind='string') - self.assertTrue(self.client.login(username='testbot', + self.assertTrue(self.client.login(request=None, + username='testbot', password='secret')) # get the edit page in order to check form's prefix @@ -298,7 +299,8 @@ class UserProfileTests(TestCase): user_visible=False, kind='string') - self.assertTrue(self.client.login(username='testbot', + self.assertTrue(self.client.login(request=None, + username='testbot', password='secret')) response = self.client.get(reverse('profile_edit')) form = get_response_form(response) diff --git a/tests/test_api.py b/tests/test_api.py index cf3be3cb..270d7cb3 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -75,7 +75,7 @@ def test_api_user(client): assert response.content == b'{}' # login - client.login(username='john.doe', password='password') + client.login(request=None, username='john.doe', password='password') response = client.get('/api/user/', HTTP_ORIGIN='http://testserver') data = json.loads(response.content) assert isinstance(data, dict) diff --git a/tests/test_backends.py b/tests/test_backends.py index 419b6c76..587aba53 100644 --- a/tests/test_backends.py +++ b/tests/test_backends.py @@ -14,7 +14,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from django.contrib.auth import authenticate +from authentic2.utils import authenticate from authentic2.backends import is_user_authenticable diff --git a/tests/test_ldap.py b/tests/test_ldap.py index c7a54a36..5d5b88ae 100644 --- a/tests/test_ldap.py +++ b/tests/test_ldap.py @@ -24,7 +24,7 @@ import ldap from ldap.dn import escape_dn_chars from ldaptools.slapd import Slapd, has_slapd -from django.contrib.auth import get_user_model, authenticate +from django.contrib.auth import get_user_model from django.core.exceptions import ImproperlyConfigured from django.core import management from django.core import mail @@ -35,6 +35,7 @@ from django.utils.six.moves.urllib import parse as urlparse from authentic2.a2_rbac.utils import get_default_ou from django_rbac.utils import get_ou_model from authentic2.backends import ldap_backend +from authentic2.utils import authenticate from authentic2 import crypto, models import utils diff --git a/tests/test_utils.py b/tests/test_utils.py index e0a3e470..693ff1a0 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -15,13 +15,12 @@ # along with this program. If not, see . # authentic2 -from django.contrib.auth import authenticate from django.contrib.auth.middleware import AuthenticationMiddleware from django.contrib.sessions.middleware import SessionMiddleware from authentic2.utils import (good_next_url, same_origin, select_next_url, user_can_change_password, login, - get_authentication_events) + get_authentication_events, authenticate) def test_good_next_url(db, rf, settings): -- 2.20.1