All checks were successful
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
120 lines
5.1 KiB
Python
120 lines
5.1 KiB
Python
"""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']
|