diff --git a/docker/config/nginx/sapl.conf b/docker/config/nginx/sapl.conf index 44b339173..7e0802ef1 100644 --- a/docker/config/nginx/sapl.conf +++ b/docker/config/nginx/sapl.conf @@ -49,8 +49,36 @@ server { alias /var/interlegis/sapl/collected_static/; } - # /media/ não é servido direto do disco: cai em location / e o Django - # (sapl.base.media) autoriza e devolve X-Accel-Redirect para cá. + # Arquivos que dependem de autorização vão ao Django (sapl.base.media), + # que devolve X-Accel-Redirect para /media-interna/. Manter a lista em + # sincronia com PROTEGIDO_RE e MODELOS_DE_SESSAO (teste em + # sapl/base/tests/test_media.py). Os dois regex ficam no nível do server + # e antes de location /media/: um regex aninhado em /media/ teria + # precedência sobre este. + location ~* ^/media/sapl/(private/|[^/]+/(documentoadministrativo|documentoacessorioadministrativo|proposicao|sessaoplenaria|orador|oradorexpediente|oradorordemdia|justificativaausencia)/) { + proxy_set_header X-Request-ID $req_id; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + proxy_redirect off; + proxy_pass http://sapl_server; + } + + # Demais arquivos são públicos e servidos direto do disco; conteúdo que o + # navegador executaria como página é forçado a download. + location ~* ^/media/.+\.(html?|xhtml|xht|shtml|svgz?|xml|xsl|xslt|rdf|js|mjs|mht|mhtml|rss|atom|mml)$ { + root /var/interlegis/sapl; + types { } + default_type application/octet-stream; + add_header Content-Disposition attachment always; + add_header X-Content-Type-Options nosniff always; + } + + location /media/ { + root /var/interlegis/sapl; + add_header X-Content-Type-Options nosniff always; + } + location /media-interna/ { internal; alias /var/interlegis/sapl/media/; diff --git a/sapl/base/media.py b/sapl/base/media.py index 87d636ee0..9b91027c2 100644 --- a/sapl/base/media.py +++ b/sapl/base/media.py @@ -17,6 +17,10 @@ from sapl.utils import EXTENSOES_ATIVAS, tipo_ativo X_ACCEL_PREFIX = '/media-interna/' +# O nginx do container só envia para cá os caminhos de PROTEGIDO_RE e de +# MODELOS_DE_SESSAO; o resto de /media/ sai direto do disco. Ao incluir um +# modelo aqui, incluí-lo também em docker/config/nginx/sapl.conf. + # Arquivos que dependem de autorização, em private/ ou em qualquer outra # pasta (public/ guarda os enviados antes da criação de private/). PROTEGIDO_RE = re.compile( diff --git a/sapl/base/tests/test_media.py b/sapl/base/tests/test_media.py index 24ceb7156..ee7a27446 100644 --- a/sapl/base/tests/test_media.py +++ b/sapl/base/tests/test_media.py @@ -1,3 +1,7 @@ +import os +import re + +from django.conf import settings from django.contrib.auth import get_user_model from django.contrib.auth.models import AnonymousUser from django.contrib.auth.models import Permission @@ -8,7 +12,7 @@ from django.utils import timezone from model_bakery import baker import pytest -from sapl.base.media import media +from sapl.base.media import MODELOS_DE_SESSAO, RESOLVEDORES, media from sapl.base.models import AppConfig, Autor from sapl.materia.models import Proposicao from sapl.protocoloadm.models import (DocumentoAcessorioAdministrativo, @@ -345,3 +349,17 @@ def test_proposicao_texto(client, settings, tmp_path, estado, perfil, else: assert response.status_code == 302 assert response['Location'] == '/proposicao/%s' % pk + + +def test_nginx_envia_ao_django_todos_os_caminhos_protegidos(): + caminho = os.path.join(settings.PROJECT_DIR, 'docker', 'config', + 'nginx', 'sapl.conf') + with open(caminho) as f: + conf = f.read() + regex = re.search(r'location ~\*? \^/media/sapl/\(private/\|' + r'\[\^/\]\+/\(([a-z|]+)\)/\)', conf) + + assert regex, 'location de arquivos protegidos não encontrada' + no_nginx = set(regex.group(1).split('|')) + assert set(RESOLVEDORES) | set(MODELOS_DE_SESSAO) <= no_nginx + assert conf.index(regex.group(0)) < conf.index('location /media/ {')