From ba1aea4129983c26734c4f24a6bc1573929bf03a Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Fri, 23 Jul 2021 11:50:19 +0200 Subject: [PATCH 4/4] to be fixed up: use a decorator to indicate views where view_restriction can be applied --- src/authentic2/idp/saml/saml2_endpoints.py | 18 +++++------------- src/authentic2/middleware.py | 7 +++---- src/authentic2/utils/view_decorators.py | 20 ++++++++++++++++++++ src/authentic2/views.py | 8 +++----- src/authentic2_auth_fc/views.py | 1 - src/authentic2_idp_cas/views.py | 6 +++--- src/authentic2_idp_oidc/views.py | 5 ++--- tests/test_user_manager.py | 2 +- 8 files changed, 37 insertions(+), 30 deletions(-) create mode 100644 src/authentic2/utils/view_decorators.py diff --git a/src/authentic2/idp/saml/saml2_endpoints.py b/src/authentic2/idp/saml/saml2_endpoints.py index 9059bca3..616370e0 100644 --- a/src/authentic2/idp/saml/saml2_endpoints.py +++ b/src/authentic2/idp/saml/saml2_endpoints.py @@ -112,6 +112,7 @@ from authentic2.utils import misc as utils_misc from authentic2.utils.misc import datetime_to_xs_datetime, find_authentication_event from authentic2.utils.misc import get_backends as get_idp_backends from authentic2.utils.misc import login_require, make_url +from authentic2.utils.view_decorators import enable_view_restriction from . import app_settings @@ -494,6 +495,7 @@ def build_assertion(request, login, provider, nid_format='transient'): return kwargs['name_id_content'] +@enable_view_restriction @never_cache @csrf_exempt @log_assert @@ -624,7 +626,7 @@ def need_login(request, login, nid_format, service): """ nonce = login.request.id or get_nonce() save_key_values(nonce, force_text(login.dump()), False, nid_format) - next_url = make_url(continue_sso, params={NONCE_FIELD_NAME: nonce}) + next_url = make_url('a2-idp-saml-continue', params={NONCE_FIELD_NAME: nonce}) logger.debug('redirect to login page with next url %s', next_url) return login_require( request, @@ -662,6 +664,7 @@ def need_consent_for_federation(request, login, nid_format): return HttpResponseRedirect(url) +@enable_view_restriction @never_cache def continue_sso(request): consent_answer = None @@ -1025,6 +1028,7 @@ def check_delegated_authentication_permission(request): return request.user.is_superuser() +@enable_view_restriction @never_cache @csrf_exempt @login_required @@ -1126,9 +1130,6 @@ def finish_slo(request): return return_saml2_response(request, logout, title=_('You are being redirected to "%s"') % provider.name) -finish_slo.no_view_restriction = True - - def return_logout_error(request, logout, error): logout.buildResponseMsg() set_saml2_response_responder_status_code(logout.response, error) @@ -1448,9 +1449,6 @@ def slo(request): return a2_views.logout(request, next_url=next_url, do_local=False, check_referer=False) -slo.no_view_restriction = True - - def icon_url(name): return '%s/authentic2/images/%s.png' % (settings.STATIC_URL, name) @@ -1535,9 +1533,6 @@ def idp_slo(request, provider_id=None): return HttpResponseRedirect(logout.msgUrl) -idp_slo.no_view_restriction = True - - def process_logout_response(request, logout, soap_response, next): logger.debug('logout response is %r', soap_response) try: @@ -1579,9 +1574,6 @@ def slo_return(request): return process_logout_response(request, logout, get_saml2_query_request(request), next) -slo_return.no_view_restriction = True - - # Helpers # Mapping to generate the metadata file, must be kept in sync with the url diff --git a/src/authentic2/middleware.py b/src/authentic2/middleware.py index 3dae2d95..1681a3bb 100644 --- a/src/authentic2/middleware.py +++ b/src/authentic2/middleware.py @@ -166,6 +166,9 @@ class ViewRestrictionMiddleware(MiddlewareMixin): def process_view(self, request, view_func, view_args, view_kwargs): '''If current view is not the one where we should be, redirect''' + if not getattr(view_func, 'enable_view_restriction', False): + return + view = self.check_view_restrictions(request) if not view: return @@ -174,10 +177,6 @@ class ViewRestrictionMiddleware(MiddlewareMixin): # do not block on the restricted view if url_name == view: return - - # prevent blocking some views, like logout views - if getattr(request.resolver_match.func, 'no_view_restriction', False): - return return utils_misc.redirect_and_come_back(request, view) diff --git a/src/authentic2/utils/view_decorators.py b/src/authentic2/utils/view_decorators.py new file mode 100644 index 00000000..884d3d92 --- /dev/null +++ b/src/authentic2/utils/view_decorators.py @@ -0,0 +1,20 @@ +# authentic2 - versatile identity manager +# Copyright (C) 2010-2021 Entr'ouvert +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + +def enable_view_restriction(view): + view.enable_view_restriction = True + return view diff --git a/src/authentic2/views.py b/src/authentic2/views.py index a04240e7..10d9485a 100644 --- a/src/authentic2/views.py +++ b/src/authentic2/views.py @@ -63,6 +63,7 @@ from .utils import misc as utils_misc from .utils import switch_user as utils_switch_user from .utils.evaluate import HTTPHeaders from .utils.service import get_service_from_request, get_service_from_token, set_service_ref +from .utils.view_decorators import enable_view_restriction User = get_user_model() @@ -427,7 +428,7 @@ class Homepage(cbv.TemplateNamesMixin, TemplateView): return ctx -homepage = Homepage.as_view() +homepage = enable_view_restriction(Homepage.as_view()) class ProfileView(cbv.TemplateNamesMixin, TemplateView): @@ -559,7 +560,7 @@ class ProfileView(cbv.TemplateNamesMixin, TemplateView): return context -profile = login_required(ProfileView.as_view()) +profile = enable_view_restriction(login_required(ProfileView.as_view())) def logout_list(request): @@ -630,9 +631,6 @@ def logout(request, next_url=None, do_local=True, check_referer=True): return response -logout.no_view_restriction = True - - def login_password_profile(request, *args, **kwargs): context = kwargs.pop('context', {}) can_change_password = utils_misc.user_can_change_password(request=request) diff --git a/src/authentic2_auth_fc/views.py b/src/authentic2_auth_fc/views.py index a47202e5..f7c65c55 100644 --- a/src/authentic2_auth_fc/views.py +++ b/src/authentic2_auth_fc/views.py @@ -572,4 +572,3 @@ class LogoutReturnView(View): logout = LogoutReturnView.as_view() -logout.no_view_restriction = True diff --git a/src/authentic2_idp_cas/views.py b/src/authentic2_idp_cas/views.py index 04f4f361..9d37a23f 100644 --- a/src/authentic2_idp_cas/views.py +++ b/src/authentic2_idp_cas/views.py @@ -36,6 +36,7 @@ from authentic2.utils.misc import ( normalize_attribute_values, redirect, ) +from authentic2.utils.view_decorators import enable_view_restriction from authentic2.views import logout as logout_view from authentic2_idp_cas.constants import ( ATTRIBUTES_ELT, @@ -467,10 +468,9 @@ class LogoutView(View): return redirect(request, next_url) -login = LoginView.as_view() +login = enable_view_restriction(LoginView.as_view()) logout = LogoutView.as_view() -logout.no_view_restriction = True -_continue = ContinueView.as_view() +_continue = enable_view_restriction(ContinueView.as_view()) validate = ValidateView.as_view() service_validate = ServiceValidateView.as_view() proxy = ProxyView.as_view() diff --git a/src/authentic2_idp_oidc/views.py b/src/authentic2_idp_oidc/views.py index 4714550c..cedc828a 100644 --- a/src/authentic2_idp_oidc/views.py +++ b/src/authentic2_idp_oidc/views.py @@ -48,6 +48,7 @@ from authentic2 import hooks from authentic2.decorators import setting_enabled from authentic2.exponential_retry_timeout import ExponentialRetryTimeout from authentic2.utils.misc import last_authentication_event, login_require, make_url, redirect +from authentic2.utils.view_decorators import enable_view_restriction from authentic2.views import logout as a2_logout from django_rbac.utils import get_ou_model @@ -228,6 +229,7 @@ def certs(request, *args, **kwargs): return HttpResponse(utils.get_jwkset().export(private_keys=False), content_type='application/json') +@enable_view_restriction @setting_enabled('ENABLE', settings=app_settings) def authorize(request, *args, **kwargs): validated_redirect_uri = None @@ -811,6 +813,3 @@ def logout(request): # FIXME: do something with id_token_hint id_token_hint = request.GET.get('id_token_hint') return a2_logout(request, next_url=post_logout_redirect_uri, do_local=False, check_referer=False) - - -logout.no_view_restriction = True diff --git a/tests/test_user_manager.py b/tests/test_user_manager.py index 52441ffd..368f8260 100644 --- a/tests/test_user_manager.py +++ b/tests/test_user_manager.py @@ -336,7 +336,7 @@ def test_export_csv(settings, app, superuser, django_assert_num_queries): user_count = User.objects.count() # queries should be batched to keep prefetching working without # overspending memory for the queryset cache, 4 queries by batches - num_queries = int(4 + 4 * (user_count / DEFAULT_BATCH_SIZE + bool(user_count % DEFAULT_BATCH_SIZE))) + num_queries = int(4 * (user_count / DEFAULT_BATCH_SIZE + bool(user_count % DEFAULT_BATCH_SIZE))) # export task also perform one query to set trigram an another to get users count num_queries += 3 with django_assert_num_queries(num_queries): -- 2.32.0.rc0