# # This file is part of pretix (Community Edition). # # Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2020-today pretix GmbH and contributors # # 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 in version 3 of the License. # # ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are # applicable granting you additional permissions and placing additional restrictions on your usage of this software. # Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive # this file, see . # # 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 json from contextlib import contextmanager from django.conf import settings from django.contrib import messages from django.contrib.auth import ( BACKEND_SESSION_KEY, get_user_model, load_backend, login, ) from django.contrib.auth.mixins import LoginRequiredMixin from django.db import transaction from django.shortcuts import get_object_or_404, redirect from django.urls import reverse from django.utils.crypto import get_random_string from django.utils.functional import cached_property from django.utils.translation import gettext_lazy as _ from django.views import View from django.views.generic import ListView, TemplateView from django_otp.plugins.otp_static.models import StaticDevice from hijack import signals from pretix.base.auth import get_auth_backends from pretix.base.models import User from pretix.control.forms.filter import UserFilterForm from pretix.control.forms.users import UserEditForm from pretix.control.permissions import AdministratorPermissionRequiredMixin from pretix.control.views import CreateView, UpdateView from pretix.control.views.user import RecentAuthenticationRequiredMixin def get_used_backend(request): # vendored from hijack/views.py backend_str = request.session[BACKEND_SESSION_KEY] backend = load_backend(backend_str) return backend @contextmanager def keep_session_age(session): # vendored from hijack/views.py try: session_expiry = session["_session_expiry"] except KeyError: yield else: yield session["_session_expiry"] = session_expiry class UserListView(AdministratorPermissionRequiredMixin, ListView): template_name = 'pretixcontrol/users/index.html' context_object_name = 'users' paginate_by = 30 def get_queryset(self): qs = User.objects.all() if self.filter_form.is_valid(): qs = self.filter_form.filter_qs(qs) return qs def get_context_data(self, **kwargs): ctx = super().get_context_data(**kwargs) ctx['filter_form'] = self.filter_form return ctx @cached_property def filter_form(self): return UserFilterForm(data=self.request.GET) class UserEditView(AdministratorPermissionRequiredMixin, RecentAuthenticationRequiredMixin, UpdateView): template_name = 'pretixcontrol/users/form.html' context_object_name = 'user' form_class = UserEditForm def get_object(self, queryset=None): return get_object_or_404(User, pk=self.kwargs.get("id")) def get_context_data(self, **kwargs): ctx = super().get_context_data(**kwargs) ctx['teams'] = self.object.teams.select_related('organizer') b = get_auth_backends() ctx['backend'] = ( b[self.object.auth_backend].verbose_name if self.object.auth_backend in b else self.object.auth_backend ) return ctx def get_success_url(self): return reverse('control:users.edit', kwargs=self.kwargs) @transaction.atomic def form_valid(self, form): messages.success(self.request, _('Your changes have been saved.')) data = {} for k in form.changed_data: if k != 'new_pw_repeat': if 'new_pw' == k: data['new_pw'] = True else: data[k] = form.cleaned_data[k] sup = super().form_valid(form) if 'require_2fa' in form.changed_data and form.cleaned_data['require_2fa']: self.object.log_action('pretix.user.settings.2fa.enabled', user=self.request.user) elif 'require_2fa' in form.changed_data and not form.cleaned_data['require_2fa']: self.object.log_action('pretix.user.settings.2fa.disabled', user=self.request.user) self.object.log_action('pretix.user.settings.changed', user=self.request.user, data=data) return sup class UserResetView(AdministratorPermissionRequiredMixin, RecentAuthenticationRequiredMixin, View): def get(self, request, *args, **kwargs): return redirect(reverse('control:users.edit', kwargs=self.kwargs)) def post(self, request, *args, **kwargs): self.object = get_object_or_404(User, pk=self.kwargs.get("id")) self.object.send_password_reset() self.object.log_action('pretix.control.auth.user.forgot_password.mail_sent', user=request.user) messages.success(request, _('We sent out an email containing further instructions.')) return redirect(self.get_success_url()) def get_success_url(self): return reverse('control:users.edit', kwargs=self.kwargs) class UserEmergencyTokenView(AdministratorPermissionRequiredMixin, RecentAuthenticationRequiredMixin, View): def get(self, request, *args, **kwargs): return redirect(reverse('control:users.edit', kwargs=self.kwargs)) def post(self, request, *args, **kwargs): self.object = get_object_or_404(User, pk=self.kwargs.get("id")) d, __ = StaticDevice.objects.get_or_create(user=self.object, name='emergency') token = d.token_set.create(token=get_random_string(length=12, allowed_chars='1234567890')) self.object.log_action('pretix.user.settings.2fa.emergency', user=self.request.user) self.object.send_security_notice([ _('A two-factor emergency code has been generated by a system administrator. This will usually happen ' 'if you lost access to your two-factor credentials and requested a reset of the credentials.') ]) messages.success(request, _( 'The emergency token for this user is "{token}". It can only be used once. Please make sure to transmit ' 'this code only over an authenticated channel (other than email, if possible). Any previous emergency ' 'tokens for this user remain active.' ).format( token=token.token )) return redirect(self.get_success_url()) def get_success_url(self): return reverse('control:users.edit', kwargs=self.kwargs) class UserAnonymizeView(AdministratorPermissionRequiredMixin, RecentAuthenticationRequiredMixin, TemplateView): template_name = "pretixcontrol/users/anonymize.html" def get_context_data(self, **kwargs): ctx = super().get_context_data(**kwargs) ctx['user'] = get_object_or_404(User, pk=self.kwargs.get("id")) return ctx def post(self, request, *args, **kwargs): self.object = get_object_or_404(User, pk=self.kwargs.get("id")) self.object.log_action('pretix.user.anonymized', user=request.user) self.object.email = "{}.{}@disabled.pretix.eu".format(self.object.pk, self.object.auth_backend) self.object.fullname = "" self.object.is_active = False self.object.notifications_send = False self.object.auth_backend = 'anonymized' self.object.auth_backend_identifier = None self.object.save() for le in self.object.all_logentries.filter(action_type="pretix.user.settings.changed"): d = le.parsed_data if 'email' in d: d['email'] = '█' if 'fullname' in d: d['fullname'] = '█' le.data = json.dumps(d) le.shredded = True le.save(update_fields=['data', 'shredded']) return redirect(reverse('control:users.edit', kwargs=self.kwargs)) class UserImpersonateView(AdministratorPermissionRequiredMixin, RecentAuthenticationRequiredMixin, View): def get(self, request, *args, **kwargs): return redirect(reverse('control:users.edit', kwargs=self.kwargs)) def post(self, request, *args, **kwargs): self.object = get_object_or_404(User, pk=self.kwargs.get("id")) self.request.user.log_action('pretix.control.auth.user.impersonated', user=request.user, data={ 'other': self.kwargs.get("id"), 'other_email': self.object.email }) oldkey = request.session.session_key hijacker = request.user hijacked = self.object hijack_history = request.session.get("hijack_history", []) hijack_history.append(request.user._meta.pk.value_to_string(hijacker)) backend = get_used_backend(request) backend = f"{backend.__module__}.{backend.__class__.__name__}" with signals.no_update_last_login(), keep_session_age(request.session): login(request, hijacked, backend=backend) request.session["hijack_history"] = hijack_history signals.hijack_started.send( sender=None, request=request, hijacker=hijacker, hijacked=hijacked, ) request.session['hijacker_session'] = oldkey return redirect(reverse('control:index')) class UserImpersonateStopView(LoginRequiredMixin, View): def post(self, request, *args, **kwargs): impersonated = request.user hijs = request.session['hijacker_session'] hijack_history = request.session.get("hijack_history", []) hijacked = request.user user_pk = hijack_history.pop() hijacker = get_object_or_404(get_user_model(), pk=user_pk) backend = get_used_backend(request) backend = f"{backend.__module__}.{backend.__class__.__name__}" with signals.no_update_last_login(), keep_session_age(request.session): login(request, hijacker, backend=backend) request.session["hijack_history"] = hijack_history signals.hijack_ended.send( sender=None, request=request, hijacker=hijacker, hijacked=hijacked, ) ss = request.user.get_active_staff_session(hijs) if ss: request.session.save() ss.session_key = request.session.session_key ss.save() request.user.log_action('pretix.control.auth.user.impersonate_stopped', user=request.user, data={ 'other': impersonated.pk, 'other_email': impersonated.email }) return redirect(reverse('control:index')) class UserCreateView(AdministratorPermissionRequiredMixin, RecentAuthenticationRequiredMixin, CreateView): template_name = 'pretixcontrol/users/create.html' context_object_name = 'user' form_class = UserEditForm def get_form(self, form_class=None): f = super().get_form(form_class) f.fields['new_pw'].required = True f.fields['new_pw_repeat'].required = True return f def get_initial(self): i = super().get_initial() i['timezone'] = settings.TIME_ZONE return i def get_success_url(self): return reverse('control:users') def form_valid(self, form): messages.success(self.request, _('The new user has been created.')) return super().form_valid(form)