From acceefe17c5244561701c498a21a61794cb45e7b 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 | 12 ------- src/authentic2/idp/saml/urls.py | 9 ++--- src/authentic2/middleware.py | 6 ++-- src/authentic2/urls.py | 5 +-- src/authentic2/utils/view_decorators.py | 40 ++++++++++++++++++++++ src/authentic2/views.py | 3 -- src/authentic2_idp_cas/urls.py | 6 ++-- src/authentic2_idp_oidc/urls.py | 4 ++- 8 files changed, 57 insertions(+), 28 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..2c4fd393 100644 --- a/src/authentic2/idp/saml/saml2_endpoints.py +++ b/src/authentic2/idp/saml/saml2_endpoints.py @@ -1126,9 +1126,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 +1445,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 +1529,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 +1570,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/idp/saml/urls.py b/src/authentic2/idp/saml/urls.py index df87f69b..ba05d73d 100644 --- a/src/authentic2/idp/saml/urls.py +++ b/src/authentic2/idp/saml/urls.py @@ -28,13 +28,14 @@ from authentic2.idp.saml.saml2_endpoints import ( slo_soap, sso, ) +from authentic2.utils.view_decorators import view_restriction from . import views urlpatterns = [ url(r'^metadata$', metadata, name='a2-idp-saml-metadata'), - url(r'^sso$', sso, name='a2-idp-saml-sso'), - url(r'^continue$', continue_sso, name='a2-idp-saml-continue'), + url(r'^sso$', view_restriction(sso), name='a2-idp-saml-sso'), + url(r'^continue$', view_restriction(continue_sso), name='a2-idp-saml-continue'), url(r'^slo$', slo, name='a2-idp-saml-slo'), url(r'^slo/soap$', slo_soap, name='a2-idp-saml-slo-soap'), url(r'^idp_slo/(.*)$', idp_slo, name='a2-idp-saml-slo-idp'), @@ -42,8 +43,8 @@ urlpatterns = [ url(r'^finish_slo$', finish_slo, name='a2-idp-saml-finish-slo'), url(r'^artifact$', artifact, name='a2-idp-saml-artifact'), # legacy endpoint, now it's prefered to pass the entity_id in a parameter - url(r'^idp_sso/(.+)$', idp_sso, name='a2-idp-saml-idp-sso-named'), - url(r'^idp_sso/$', idp_sso, name='a2-idp-saml2-idp-sso'), + url(r'^idp_sso/(.+)$', view_restriction(idp_sso), name='a2-idp-saml-idp-sso-named'), + url(r'^idp_sso/$', view_restriction(idp_sso), name='a2-idp-saml2-idp-sso'), url(r'^federations/create/(?P\d+)/$', views.create_federation, name='a2-idp-saml2-federation-create'), url(r'^federations/(?P\d+)/delete/$', views.delete_federation, name='a2-idp-saml2-federation-delete'), ] diff --git a/src/authentic2/middleware.py b/src/authentic2/middleware.py index 3dae2d95..1b60409f 100644 --- a/src/authentic2/middleware.py +++ b/src/authentic2/middleware.py @@ -166,6 +166,8 @@ 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, 'view_restriction', False): + return view = self.check_view_restrictions(request) if not view: return @@ -174,10 +176,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/urls.py b/src/authentic2/urls.py index 9781a735..00ab833f 100644 --- a/src/authentic2/urls.py +++ b/src/authentic2/urls.py @@ -34,6 +34,7 @@ import authentic2_idp_oidc.urls from authentic2.decorators import lasso_required, required, setting_enabled from . import plugins, views +from .utils.view_decorators import view_restriction admin.autodiscover() @@ -67,7 +68,7 @@ accounts_urlpatterns = [ login_required(views.authorized_oauth_services), name='authorized-oauth-services', ), - url(r'^$', views.profile, name='account_management'), + url(r'^$', view_restriction(views.profile), name='account_management'), # Password change url(r'^password/change/$', views.password_change, name='password_change'), url( @@ -99,7 +100,7 @@ accounts_urlpatterns = [ ] urlpatterns = [ - url(r'^$', views.homepage, name='auth_homepage'), + url(r'^$', view_restriction(views.homepage), name='auth_homepage'), url(r'^login/$', views.login, name='auth_login'), url(r'^logout/$', views.logout, name='auth_logout'), url(r'^su/(?P[A-Za-z0-9_-]+)/$', views.su, name='su'), diff --git a/src/authentic2/utils/view_decorators.py b/src/authentic2/utils/view_decorators.py new file mode 100644 index 00000000..3e179232 --- /dev/null +++ b/src/authentic2/utils/view_decorators.py @@ -0,0 +1,40 @@ +# 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 . + +import copy +import functools +import types + + +def copy_callable(f): + """Based on http://stackoverflow.com/a/6528148/190597 (Glenn Maynard)""" + assert hasattr(f, '__call__') + + if isinstance(f, types.FunctionType): + g = types.FunctionType( + f.__code__, f.__globals__, name=f.__name__, argdefs=f.__defaults__, closure=f.__closure__ + ) + g = functools.update_wrapper(g, f) + g.__kwdefaults__ = f.__kwdefaults__ + return g + else: + return copy.copy(f) + + +def view_restriction(view): + decorated_view = copy_callable(view) + decorated_view.view_restriction = True + return decorated_view diff --git a/src/authentic2/views.py b/src/authentic2/views.py index a04240e7..66b80645 100644 --- a/src/authentic2/views.py +++ b/src/authentic2/views.py @@ -630,9 +630,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_idp_cas/urls.py b/src/authentic2_idp_cas/urls.py index ca7c01af..3456d3e1 100644 --- a/src/authentic2_idp_cas/urls.py +++ b/src/authentic2_idp_cas/urls.py @@ -16,11 +16,13 @@ from django.conf.urls import url +from authentic2.utils.view_decorators import view_restriction + from . import views urlpatterns = [ - url('^login/?$', views.login, name='a2-idp-cas-login'), - url('^continue/$', views._continue, name='a2-idp-cas-continue'), + url('^login/?$', view_restriction(views.login), name='a2-idp-cas-login'), + url('^continue/$', view_restriction(views._continue), name='a2-idp-cas-continue'), url('^validate/?$', views.validate, name='a2-idp-cas-validate'), url('^serviceValidate/?$', views.service_validate, name='a2-idp-cas-service-validate'), url('^logout/?$', views.logout, name='a2-idp-cas-logout'), diff --git a/src/authentic2_idp_oidc/urls.py b/src/authentic2_idp_oidc/urls.py index 19427aa2..451570dd 100644 --- a/src/authentic2_idp_oidc/urls.py +++ b/src/authentic2_idp_oidc/urls.py @@ -16,12 +16,14 @@ from django.conf.urls import url +from authentic2.utils.view_decorators import view_restriction + from . import views urlpatterns = [ url(r'^.well-known/openid-configuration$', views.openid_configuration, name='oidc-openid-configuration'), url(r'^idp/oidc/certs/?$', views.certs, name='oidc-certs'), - url(r'^idp/oidc/authorize/?$', views.authorize, name='oidc-authorize'), + url(r'^idp/oidc/authorize/?$', view_restriction(views.authorize), name='oidc-authorize'), url(r'^idp/oidc/token/?$', views.token, name='oidc-token'), url(r'^idp/oidc/user_info/?$', views.user_info, name='oidc-user-info'), url(r'^idp/oidc/logout/?$', views.logout, name='oidc-logout'), -- 2.32.0.rc0