From 3c029fe4ecd98a9b04146f53a4384f9319eff43a Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Tue, 11 Feb 2020 14:45:24 +0100 Subject: [PATCH 3/3] misc: use one-time tokens instead of cache (#39745) --- src/authentic2/manager/user_views.py | 4 +-- src/authentic2/urls.py | 2 +- src/authentic2/utils/__init__.py | 28 -------------------- src/authentic2/utils/switch_user.py | 39 ++++++++++++++++++++++++++++ src/authentic2/views.py | 5 ++-- 5 files changed, 45 insertions(+), 33 deletions(-) create mode 100644 src/authentic2/utils/switch_user.py diff --git a/src/authentic2/manager/user_views.py b/src/authentic2/manager/user_views.py index a9815dc7..20ff23f7 100644 --- a/src/authentic2/manager/user_views.py +++ b/src/authentic2/manager/user_views.py @@ -281,7 +281,7 @@ class UserDetailView(OtherActionsMixin, BaseDetailView): def action_su(self, request, *args, **kwargs): return redirect(request, 'auth_logout', - params={REDIRECT_FIELD_NAME: build_su_url(self.object)}) + params={REDIRECT_FIELD_NAME: switch_user.build_url(self.object)}) # Copied from PasswordResetForm implementation def send_mail(self, subject_template_name, email_template_name, @@ -821,7 +821,7 @@ class UserSuView(MediaMixin, TitleMixin, PermissionMixin, DetailView): ctx['su_url'] = make_url( 'auth_logout', - params={REDIRECT_FIELD_NAME: build_su_url(self.object, self.duration)}, + params={REDIRECT_FIELD_NAME: switch_user.build_url(self.object, self.duration)}, request=self.request, absolute=True) ctx['duration'] = self.duration diff --git a/src/authentic2/urls.py b/src/authentic2/urls.py index 0ca463ee..a1cbbac6 100644 --- a/src/authentic2/urls.py +++ b/src/authentic2/urls.py @@ -111,7 +111,7 @@ urlpatterns = [ url(r'^$', 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-f0-9]+)/$', views.su, name='su'), + url(r'^su/(?P[A-Za-z0-9_-]+)/$', views.su, name='su'), url(r'^accounts/', include(accounts_urlpatterns)), url(r'^admin/', include(admin.site.urls)), url(r'^idp/', include('authentic2.idp.urls')), diff --git a/src/authentic2/utils/__init__.py b/src/authentic2/utils/__init__.py index 39e0a913..c6251b7d 100644 --- a/src/authentic2/utils/__init__.py +++ b/src/authentic2/utils/__init__.py @@ -867,34 +867,6 @@ def to_dict_of_set(d): return dict((k, set(v)) for k, v in d.items()) -def build_su_url(user, duration=30): - token = get_hex_uuid() - data = {'user_pk': user.pk} - cache.set('switch-%s' % token, data, duration) - return make_url('su', kwargs={'token': token}) - -HEX_RE = re.compile('^[a-f0-9]+$') - - -def get_su_user(token): - User = get_user_model() - if not token: - return None - if not HEX_RE.match(token): - return None - key = 'switch-%s' % token - data = cache.get(key) - if not isinstance(data, dict): - return None - if not data.get('user_pk'): - return None - cache.delete(key) - try: - return User.objects.get(pk=data['user_pk']) - except User.DoesNotExist: - return None - - def datetime_to_utc(dt): if timezone.is_naive(dt): dt = timezone.make_aware(dt, timezone.get_current_timezone()) diff --git a/src/authentic2/utils/switch_user.py b/src/authentic2/utils/switch_user.py new file mode 100644 index 00000000..e7f477be --- /dev/null +++ b/src/authentic2/utils/switch_user.py @@ -0,0 +1,39 @@ +# authentic2 - versatile identity manager +# Copyright (C) 2010-2020 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 . + +from authentic2.models import Token +from authentic2.custom_user.models import User + +from authentic2.utils import make_url + + +def build_url(user, duration=30): + token = Token.create('su', {'user_pk': user.pk}, expires=duration) + return make_url('su', kwargs={'uuid': token.uuid_b64url}) + + +def resolve_token(uuid): + try: + token = Token.use('su', uuid) + except (ValueError, TypeError, Token.DoesNotExist): + return None + + try: + return User.objects.get(pk=token.content['user_pk']) + except User.DoesNotExist: + return None + + diff --git a/src/authentic2/views.py b/src/authentic2/views.py index 0de56980..bd04cacf 100644 --- a/src/authentic2/views.py +++ b/src/authentic2/views.py @@ -54,6 +54,7 @@ from django.template import loader from . import (utils, app_settings, compat, decorators, constants, models, cbv, hooks, validators) +from .utils import switch_user from .a2_rbac.utils import get_default_ou from .a2_rbac.models import OrganizationalUnit as OU from .forms import ( @@ -1210,8 +1211,8 @@ def notimplemented_view(request): class SuView(View): - def get(self, request, token): - user = utils.get_su_user(token) + def get(self, request, uuid): + user = switch_user.resolve_token(uuid) if not user: raise Http404 return utils.simulate_authentication(request, user, 'su') -- 2.24.0