"""Regression for an isolated instance mounted below a fixed tenant path.""" from html.parser import HTMLParser from urllib.parse import parse_qs, urlsplit import pytest from django.test import Client from django.urls import get_script_prefix, set_script_prefix from vergabe_teilnahme.apps.ausschreibungen.models import Ausschreibung pytestmark = pytest.mark.django_db PREFIX = '/demo-company' @pytest.fixture def tenant_path(settings): previous = get_script_prefix() settings.FORCE_SCRIPT_NAME = PREFIX settings.APP_BASE_PATH = PREFIX settings.STATIC_URL = PREFIX + '/static/' settings.MEDIA_URL = PREFIX + '/media/' settings.SESSION_COOKIE_PATH = settings.CSRF_COOKIE_PATH = PREFIX + '/' settings.SESSION_COOKIE_NAME = 'vergabe_demo_company_sessionid' settings.CSRF_COOKIE_NAME = 'vergabe_demo_company_csrftoken' set_script_prefix(PREFIX) yield settings set_script_prefix(previous) class LocalLinks(HTMLParser): def __init__(self): super().__init__() self.links = [] def handle_starttag(self, tag, attrs): for name, value in attrs: if name in {'href', 'src', 'action', 'hx-get', 'hx-post', 'hx-delete'} and value: if value.startswith('/'): self.links.append(value) def assert_prefixed(content): parser = LocalLinks() parser.feed(content.decode()) assert parser.links assert all(link.startswith(PREFIX + '/') for link in parser.links), parser.links def test_anonymous_and_expired_fragments_stay_in_tenant_path(client, tenant_path): response = client.get('/ausschreibungen/', HTTP_X_FORWARDED_PREFIX='/other-company') assert response.status_code == 302 assert urlsplit(response.url).path == PREFIX + '/accounts/login/' assert parse_qs(urlsplit(response.url).query)['next'] == [PREFIX + '/ausschreibungen/'] response = client.get('/suche/?q=private', HTTP_HX_REQUEST='true') assert response.status_code == 401 assert urlsplit(response['HX-Redirect']).path == PREFIX + '/accounts/login/' assert parse_qs(urlsplit(response['HX-Redirect']).query)['next'] == [PREFIX + '/'] assert client.get('/health/').json() == {'status': 'ok'} def test_login_cookie_csrf_logout_and_external_redirect(client, mitarbeiter, tenant_path): client = Client(enforce_csrf_checks=True) login = client.get('/accounts/login/') assert_prefixed(login.content) csrf = tenant_path.CSRF_COOKIE_NAME assert client.cookies[csrf]['path'] == PREFIX + '/' credentials = {'username': mitarbeiter.username, 'password': 'testpass'} assert client.post('/accounts/login/', credentials).status_code == 403 credentials['csrfmiddlewaretoken'] = client.cookies[csrf].value credentials['next'] = 'https://untrusted.example/private' response = client.post('/accounts/login/', credentials) assert response.status_code == 302 assert response.url == PREFIX + '/' assert client.cookies[tenant_path.SESSION_COOKIE_NAME]['path'] == PREFIX + '/' assert client.get('/accounts/logout/').status_code == 405 assert client.post('/accounts/logout/').status_code == 403 response = client.post('/accounts/logout/', {'csrfmiddlewaretoken': client.cookies[csrf].value}) assert response.url == PREFIX + '/accounts/login/' assert client.cookies[tenant_path.SESSION_COOKIE_NAME]['path'] == PREFIX + '/' assert client.get('/ausschreibungen/').status_code == 302 def test_member_navigation_fragments_and_password_change(client, mitarbeiter, tenant_path): tender = Ausschreibung.objects.create(titel='Path test', ausschreiber='Demo') client.force_login(mitarbeiter) for path in [ '/ausschreibungen/', f'/ausschreibungen/{tender.pk}/', '/aufgaben/', '/partner/subunternehmer/', '/bibliothek/nachweise/', '/accounts/password-change/', ]: response = client.get(path) assert response.status_code == 200, path assert_prefixed(response.content) fragment = client.get('/suche/?q=Path', HTTP_HX_REQUEST='true') assert fragment.status_code == 200 assert_prefixed(fragment.content) response = client.post('/accounts/password-change/', { 'old_password': 'testpass', 'new_password1': 'Path-pilot-password-984!', 'new_password2': 'Path-pilot-password-984!', }) assert response.status_code == 302 assert response.url.startswith(PREFIX + '/') assert client.get('/ausschreibungen/').status_code == 200 def test_private_media_remains_authenticated_under_prefix( client, mitarbeiter, tenant_path, tmp_path, ): tenant_path.MEDIA_ROOT = tmp_path (tmp_path / 'document.pdf').write_bytes(b'%PDF-private-demo') anonymous = client.get('/media/document.pdf') assert anonymous.status_code == 302 assert urlsplit(anonymous.url).path == PREFIX + '/accounts/login/' client.force_login(mitarbeiter) response = client.get('/media/document.pdf') assert response.status_code == 200 assert b''.join(response.streaming_content) == b'%PDF-private-demo' assert response['Content-Disposition'] == 'attachment; filename="document.pdf"' assert 'no-store' in response['Cache-Control']