Harden StaffSession handling

This commit is contained in:
Mira Weller
2026-07-03 10:41:39 +02:00
committed by Raphael Michel
parent 7fd6bf41f9
commit 7a5f96369a
3 changed files with 42 additions and 26 deletions
+6 -9
View File
@@ -647,25 +647,22 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
id__in=self.teams.filter(TeamQuerySet.organizer_permission_q(permission)).values_list('organizer', flat=True) id__in=self.teams.filter(TeamQuerySet.organizer_permission_q(permission)).values_list('organizer', flat=True)
) )
def has_active_staff_session(self, session_key=None): def has_active_staff_session(self, session_key):
""" """
Returns whether or not a user has an active staff session (formerly known as superuser session) Returns whether or not a user has an active staff session (formerly known as superuser session)
with the given session key. with the given session key.
""" """
return self.get_active_staff_session(session_key) is not None return self.get_active_staff_session(session_key) is not None
def get_active_staff_session(self, session_key=None): def get_active_staff_session(self, session_key):
if not self.is_staff: if not self.is_staff or not session_key:
return None return None
if not hasattr(self, '_staff_session_cache'): if not hasattr(self, '_staff_session_cache'):
self._staff_session_cache = {} self._staff_session_cache = {}
if session_key not in self._staff_session_cache: if session_key not in self._staff_session_cache:
qs = StaffSession.objects.filter( sess = StaffSession.objects.filter(
user=self, date_end__isnull=True user=self, date_end__isnull=True, session_key=session_key
) ).first()
if session_key:
qs = qs.filter(session_key=session_key)
sess = qs.first()
if sess: if sess:
if sess.date_start < now() - timedelta(seconds=settings.PRETIX_SESSION_TIMEOUT_ABSOLUTE): if sess.date_start < now() - timedelta(seconds=settings.PRETIX_SESSION_TIMEOUT_ABSOLUTE):
sess.date_end = now() sess.date_end = now()
+10 -6
View File
@@ -36,6 +36,7 @@ from urllib.parse import quote, urljoin, urlparse
from django.conf import settings from django.conf import settings
from django.contrib.auth import REDIRECT_FIELD_NAME, logout from django.contrib.auth import REDIRECT_FIELD_NAME, logout
from django.contrib.auth.views import redirect_to_login
from django.http import Http404 from django.http import Http404
from django.shortcuts import get_object_or_404, resolve_url from django.shortcuts import get_object_or_404, resolve_url
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
@@ -214,12 +215,15 @@ class AuditLogMiddleware:
hijack_history = request.session.get('hijack_history', False) hijack_history = request.session.get('hijack_history', False)
hijacker = get_object_or_404(User, pk=hijack_history[0]["user"]) hijacker = get_object_or_404(User, pk=hijack_history[0]["user"])
ss = hijacker.get_active_staff_session(request.session.get('hijacker_session')) ss = hijacker.get_active_staff_session(request.session.get('hijacker_session'))
if ss: if not ss:
ss.logs.create( # Staff session expired or not found
url=request.path, logout(request)
method=request.method, return redirect_to_login(request.get_full_path())
impersonating=request.user ss.logs.create(
) url=request.path,
method=request.method,
impersonating=request.user
)
else: else:
ss = request.user.get_active_staff_session(request.session.session_key) ss = request.user.get_active_staff_session(request.session.session_key)
if ss: if ss:
+26 -11
View File
@@ -31,6 +31,7 @@ from django.contrib.auth import (
) )
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.views import redirect_to_login from django.contrib.auth.views import redirect_to_login
from django.core.exceptions import PermissionDenied
from django.db import transaction from django.db import transaction
from django.shortcuts import get_object_or_404, redirect from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse from django.urls import reverse
@@ -221,11 +222,13 @@ class UserImpersonateView(AdministratorPermissionRequiredMixin, RecentAuthentica
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
self.object = get_object_or_404(User, pk=self.kwargs.get("id")) self.object = get_object_or_404(User, pk=self.kwargs.get("id"))
staff_session = request.user.get_active_staff_session(request.session.session_key)
self.request.user.log_action('pretix.control.auth.user.impersonated', self.request.user.log_action('pretix.control.auth.user.impersonated',
user=request.user, user=request.user,
data={ data={
'other': self.kwargs.get("id"), 'other': self.kwargs.get("id"),
'other_email': self.object.email 'other_email': self.object.email,
'staff_session': staff_session.pk,
}) })
oldkey = request.session.session_key oldkey = request.session.session_key
@@ -249,6 +252,12 @@ class UserImpersonateView(AdministratorPermissionRequiredMixin, RecentAuthentica
with signals.no_update_last_login(), keep_session_age(request.session): with signals.no_update_last_login(), keep_session_age(request.session):
login(request, hijacked, backend=backend) login(request, hijacked, backend=backend)
request.session.save()
staff_session.logs.create(
method='(NOTE)',
url=f'Begin impersonating user #{hijacked.pk} (request session {oldkey[:8]} -> {request.session.session_key[:8]})',
)
request.session["hijack_history"] = hijack_history request.session["hijack_history"] = hijack_history
signals.hijack_started.send( signals.hijack_started.send(
@@ -265,13 +274,15 @@ class UserImpersonateView(AdministratorPermissionRequiredMixin, RecentAuthentica
class UserImpersonateStopView(LoginRequiredMixin, View): class UserImpersonateStopView(LoginRequiredMixin, View):
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
impersonated = request.user staff_session_key = request.session['hijacker_session']
prev_session_key = request.session.session_key
hijs = request.session['hijacker_session']
hijack_history = request.session.get("hijack_history", []) hijack_history = request.session.get("hijack_history", [])
hijacked = request.user hijacked = request.user
prev_session = hijack_history.pop() prev_session = hijack_history.pop()
hijacker = get_object_or_404(get_user_model(), pk=prev_session["user"]) hijacker = get_object_or_404(get_user_model(), pk=prev_session["user"])
staff_session = hijacker.get_active_staff_session(staff_session_key)
if not staff_session:
raise PermissionDenied
expected_hash = salted_hmac( expected_hash = salted_hmac(
key_salt=b"hijack-history-hash", key_salt=b"hijack-history-hash",
@@ -299,17 +310,21 @@ class UserImpersonateStopView(LoginRequiredMixin, View):
hijacked=hijacked, hijacked=hijacked,
) )
ss = request.user.get_active_staff_session(hijs) request.session.save()
if ss: staff_session.session_key = request.session.session_key
request.session.save() staff_session.save()
ss.session_key = request.session.session_key
ss.save() staff_session.logs.create(
method='(NOTE)',
url=f'Stop impersonating user #{hijacked.pk} (request session {prev_session_key[:8]}, staff session {staff_session_key[:8]} -> {request.session.session_key[:8]})',
)
request.user.log_action('pretix.control.auth.user.impersonate_stopped', request.user.log_action('pretix.control.auth.user.impersonate_stopped',
user=request.user, user=request.user,
data={ data={
'other': impersonated.pk, 'other': hijacked.pk,
'other_email': impersonated.email 'other_email': hijacked.email,
'staff_session': staff_session.pk,
}) })
return redirect(reverse('control:index')) return redirect(reverse('control:index'))