From 6600c430abfe462e4d0234950780a1c8e899217e Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Sat, 25 Nov 2017 18:55:06 +0100 Subject: [PATCH] [SECURITY] Respect session timeout in API --- src/pretix/api/auth/permission.py | 16 +++++ src/tests/api/conftest.py | 11 ++++ src/tests/api/test_permissions.py | 99 +++++++++++++++++++++++++++++++ 3 files changed, 126 insertions(+) diff --git a/src/pretix/api/auth/permission.py b/src/pretix/api/auth/permission.py index be028ad44..eee47d82f 100644 --- a/src/pretix/api/auth/permission.py +++ b/src/pretix/api/auth/permission.py @@ -1,3 +1,7 @@ +import time + +from django.conf import settings +from django.contrib.auth import logout from rest_framework.permissions import SAFE_METHODS, BasePermission from pretix.base.models import Event @@ -13,6 +17,18 @@ class EventPermission(BasePermission): return True return False + if request.user.is_authenticated: + # If this logic is updated, make sure to also update the logic in pretix/control/middleware.py + if not settings.PRETIX_LONG_SESSIONS or not request.session.get('pretix_auth_long_session', False): + last_used = request.session.get('pretix_auth_last_used', time.time()) + if time.time() - request.session.get('pretix_auth_login_time', time.time()) > settings.PRETIX_SESSION_TIMEOUT_ABSOLUTE: + logout(request) + request.session['pretix_auth_login_time'] = 0 + return False + if time.time() - last_used > settings.PRETIX_SESSION_TIMEOUT_RELATIVE: + return False + request.session['pretix_auth_last_used'] = int(time.time()) + perm_holder = (request.auth if isinstance(request.auth, TeamAPIToken) else request.user) if 'event' in request.resolver_match.kwargs and 'organizer' in request.resolver_match.kwargs: diff --git a/src/tests/api/conftest.py b/src/tests/api/conftest.py index 6fa2548d9..d4bcf78a4 100644 --- a/src/tests/api/conftest.py +++ b/src/tests/api/conftest.py @@ -43,6 +43,17 @@ def user(): return User.objects.create_user('dummy@dummy.dummy', 'dummy') +@pytest.fixture +def user_client(client, team, user): + team.can_view_orders = True + team.can_view_vouchers = True + team.all_events = True + team.save() + team.members.add(user) + client.force_authenticate(user=user) + return client + + @pytest.fixture def token_client(client, team): team.can_view_orders = True diff --git a/src/tests/api/test_permissions.py b/src/tests/api/test_permissions.py index cab631455..4c1ce4177 100644 --- a/src/tests/api/test_permissions.py +++ b/src/tests/api/test_permissions.py @@ -1,4 +1,7 @@ +import time + import pytest +from django.test import override_settings from pretix.base.models import Organizer @@ -107,3 +110,99 @@ def test_token_event_permission_not_allowed(token_client, team, organizer, event resp = getattr(token_client, urlset[0])('/api/v1/organizers/{}/events/{}/{}'.format( organizer.slug, event.slug, urlset[2])) assert resp.status_code in (404, 403) + + +@pytest.mark.django_db +def test_log_out_after_absolute_timeout(user_client, team, organizer, event): + session = user_client.session + session['pretix_auth_long_session'] = False + session['pretix_auth_login_time'] = int(time.time()) - 3600 * 12 - 60 + session.save() + + response = user_client.get('/api/v1/organizers/{}/events/'.format(organizer.slug)) + assert response.status_code == 403 + + +@pytest.mark.django_db +def test_dont_logout_before_absolute_timeout(user_client, team, organizer, event): + session = user_client.session + session['pretix_auth_long_session'] = True + session['pretix_auth_login_time'] = int(time.time()) - 3600 * 12 + 60 + session.save() + + response = user_client.get('/api/v1/organizers/{}/events/'.format(organizer.slug)) + assert response.status_code == 200 + + +@pytest.mark.django_db +@override_settings(PRETIX_LONG_SESSIONS=False) +def test_ignore_long_session_if_disabled_in_config(user_client, team, organizer, event): + session = user_client.session + session['pretix_auth_long_session'] = True + session['pretix_auth_login_time'] = int(time.time()) - 3600 * 12 - 60 + session.save() + + response = user_client.get('/api/v1/organizers/{}/events/'.format(organizer.slug)) + assert response.status_code == 403 + + +@pytest.mark.django_db +def test_dont_logout_in_long_session(user_client, team, organizer, event): + session = user_client.session + session['pretix_auth_long_session'] = True + session['pretix_auth_login_time'] = int(time.time()) - 3600 * 12 - 60 + session.save() + + response = user_client.get('/api/v1/organizers/{}/events/'.format(organizer.slug)) + assert response.status_code == 200 + + +@pytest.mark.django_db +def test_log_out_after_relative_timeout(user_client, team, organizer, event): + session = user_client.session + session['pretix_auth_long_session'] = False + session['pretix_auth_login_time'] = int(time.time()) - 3600 * 6 + session['pretix_auth_last_used'] = int(time.time()) - 3600 * 3 - 60 + session.save() + + response = user_client.get('/api/v1/organizers/{}/events/'.format(organizer.slug)) + assert response.status_code == 403 + + +@pytest.mark.django_db +def test_dont_logout_before_relative_timeout(user_client, team, organizer, event): + session = user_client.session + session['pretix_auth_long_session'] = True + session['pretix_auth_login_time'] = int(time.time()) - 3600 * 6 + session['pretix_auth_last_used'] = int(time.time()) - 3600 * 3 + 60 + session.save() + + response = user_client.get('/api/v1/organizers/{}/events/'.format(organizer.slug)) + assert response.status_code == 200 + + +@pytest.mark.django_db +def test_dont_logout_by_relative_in_long_session(user_client, team, organizer, event): + session = user_client.session + session['pretix_auth_long_session'] = True + session['pretix_auth_login_time'] = int(time.time()) - 3600 * 5 + session['pretix_auth_last_used'] = int(time.time()) - 3600 * 3 - 60 + session.save() + + response = user_client.get('/api/v1/organizers/{}/events/'.format(organizer.slug)) + assert response.status_code == 200 + + +@pytest.mark.django_db +def test_update_session_activity(user_client, team, organizer, event): + t1 = int(time.time()) - 5 + session = user_client.session + session['pretix_auth_long_session'] = False + session['pretix_auth_login_time'] = int(time.time()) - 3600 * 5 + session['pretix_auth_last_used'] = t1 + session.save() + + response = user_client.get('/api/v1/organizers/{}/events/'.format(organizer.slug)) + assert response.status_code == 200 + + assert user_client.session['pretix_auth_last_used'] > t1