Browse Source

Ajusta regras de acesso a arquivos de documentos e proposições

Documentos administrativos e acessórios ficam ocultos ao público quando a
configuração é restritiva ou o documento é restrito. O texto original da
proposição nunca é público. Arquivos de sessões plenárias são públicos.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pull/3865/head
Edward Ribeiro 3 weeks ago
parent
commit
a7b8802dc2
  1. 30
      sapl/base/media.py
  2. 27
      sapl/base/tests/test_media.py
  3. 12
      sapl/materia/models.py
  4. 10
      sapl/materia/views.py
  5. 7
      sapl/protocoloadm/models.py
  6. 4
      sapl/protocoloadm/tests/test_docadm_acesso.py
  7. 17
      sapl/sessao/tests/test_sessao_oculta_api.py

30
sapl/base/media.py

@ -6,11 +6,10 @@ from urllib.parse import quote
from django.conf import settings
from django.http import FileResponse, Http404, HttpResponse
from sapl.materia.models import Proposicao
from sapl.materia.models import Proposicao, pode_ver_proposicao
from sapl.protocoloadm.models import (DocumentoAcessorioAdministrativo,
DocumentoAdministrativo,
pode_ver_documento_adm)
from sapl.sessao.models import SessaoPlenaria, restringe_sessoes_visiveis
from sapl.utils import EXTENSOES_ATIVAS, tipo_ativo
X_ACCEL_PREFIX = '/media-interna/'
@ -21,19 +20,6 @@ PROTEGIDO_RE = re.compile(
r'^sapl/[^/]+/(?:documentoadministrativo|'
r'documentoacessorioadministrativo|proposicao)/')
def _pode_ver_proposicao(user, proposicao):
if proposicao.data_recebimento:
return True
if not user.is_authenticated:
return False
if proposicao.autor and proposicao.autor.operadores.filter(
id=user.id).exists():
return True
return bool(proposicao.data_envio) and user.has_module_perms(
'protocoloadm')
RESOLVEDORES = {
'documentoadministrativo': (
DocumentoAdministrativo, 'texto_integral', pode_ver_documento_adm),
@ -41,7 +27,7 @@ RESOLVEDORES = {
DocumentoAcessorioAdministrativo, 'arquivo',
lambda user, obj: pode_ver_documento_adm(user, obj.documento)),
'proposicao': (
Proposicao, 'texto_original', _pode_ver_proposicao),
Proposicao, 'texto_original', pode_ver_proposicao),
}
@ -62,14 +48,6 @@ def _nome_protegido(request, path):
return arquivo.name[2:] if arquivo.name.startswith('./') else arquivo.name
def _sessao_oculta(request, partes):
if request.user.is_authenticated or not partes[3].isdigit():
return False
qs = SessaoPlenaria.objects.filter(pk=partes[3])
return qs.exists() and not restringe_sessoes_visiveis(
qs, request.user).exists()
def media(request, path):
partes = path.split('/')
if any(p in ('', '.', '..') for p in partes) or '\x00' in path:
@ -78,10 +56,6 @@ def media(request, path):
protegido = path.startswith('sapl/private/') or bool(
PROTEGIDO_RE.match(path))
nome = _nome_protegido(request, path) if protegido else path
if path.startswith('sapl/public/sessaoplenaria/') and len(partes) > 4 \
and _sessao_oculta(request, partes):
raise Http404
nome_arquivo = os.path.basename(nome)
content_type = (mimetypes.guess_type(nome_arquivo)[0] or

27
sapl/base/tests/test_media.py

@ -71,8 +71,9 @@ def _assert_servido(response, path):
('O', True, None, False),
('O', True, 'logado', True),
('R', False, None, False),
('R', False, 'logado', False),
('R', True, 'com_permissao', True),
('R', False, 'logado', True),
('R', True, None, False),
('R', True, 'logado', True),
])
def test_documento_administrativo(client, comportamento, restrito, perfil,
pode_ver):
@ -81,9 +82,6 @@ def test_documento_administrativo(client, comportamento, restrito, perfil,
texto_integral=DOC_PATH)
if perfil == 'logado':
_login(client, 'logado')
elif perfil == 'com_permissao':
_login(client, 'perm',
'protocoloadm.detail_documentoadministrativo')
response = _get(client, DOC_PATH)
@ -180,7 +178,10 @@ def _proposicao(enviada=False, recebida=False):
('enviada', 'protocolo', True),
('enviada', 'outro', False),
('enviada', None, False),
('recebida', None, True),
('recebida', None, False),
('recebida', 'outro', False),
('recebida', 'protocolo', True),
('recebida', 'operador', True),
])
def test_proposicao(client, estado, perfil, pode_ver):
operador = _proposicao(enviada=estado == 'enviada',
@ -276,3 +277,17 @@ def test_senha_fraca_nao_redireciona_media(client):
assert response.status_code == 200
assert response.has_header('X-Accel-Redirect')
@pytest.mark.django_db(transaction=False)
@pytest.mark.parametrize('perfil', [None, 'outro'])
def test_proposicao_texto_recebida_nao_e_publica(client, perfil):
_proposicao(recebida=True)
if perfil:
_login(client, perfil)
pk = Proposicao.objects.get(texto_original=PROPOSICAO_PATH).pk
response = client.get('/proposicao/texto/%s' % pk)
assert response.status_code == 302
assert response['Location'] == '/proposicao/%s' % pk

12
sapl/materia/models.py

@ -1154,6 +1154,18 @@ class Proposicao(models.Model):
update_fields=update_fields)
def pode_ver_proposicao(user, proposicao):
"""Texto original da proposição: nunca público. Operadores do autor veem
sempre; o protocolo vê depois do envio."""
if not user.is_authenticated:
return False
if proposicao.autor and proposicao.autor.operadores.filter(
id=user.id).exists():
return True
return bool(proposicao.data_envio) and user.has_module_perms(
'protocoloadm')
class HistoricoProposicao(models.Model):
STATUS_PROPOSICAO = Choices(('E', 'ENVIADA', _('Enviada')),
('R', 'RECEBIDA', _('Recebida')),

10
sapl/materia/views.py

@ -74,7 +74,7 @@ from .forms import (AcessorioEmLoteFilterSet, AcompanhamentoMateriaForm,
ReceberProposicaoForm, RelatoriaForm,
TramitacaoEmLoteFilterSet, TramitacaoEmLoteForm,
UnidadeTramitacaoForm, StatusTramitacaoFilterSet)
from .models import (AcompanhamentoMateria, Anexada, AssuntoMateria, Autoria, DespachoInicial,
from .models import (pode_ver_proposicao, AcompanhamentoMateria, Anexada, AssuntoMateria, Autoria, DespachoInicial,
DocumentoAcessorio, MateriaAssunto, MateriaLegislativa, Numeracao, Orgao,
Origem, Proposicao, RegimeTramitacao, Relatoria, StatusTramitacao,
TipoDocumento, TipoFimRelatoria, TipoMateriaLegislativa, TipoProposicao,
@ -106,14 +106,10 @@ def proposicao_texto(request, pk):
username = request.user.username
logger.debug('user=' + username +
'. Tentando obter objeto Proposicao com pk = {}.'.format(pk))
proposicao = Proposicao.objects.get(pk=pk)
proposicao = get_object_or_404(Proposicao, pk=pk)
if proposicao.texto_original:
if (not proposicao.data_recebimento and
not proposicao.autor.operadores.filter(
id=request.user.id
).exists()
):
if not pode_ver_proposicao(request.user, proposicao):
logger.error("user=" + username + ". Usuário ({}) não tem permissão para acessar o texto original."
.format(request.user.id))
messages.error(request, _(

7
sapl/protocoloadm/models.py

@ -343,9 +343,10 @@ class DocumentoAdministrativo(models.Model):
def pode_ver_documento_adm(user, documento):
if SaplAppConfig.attr('documentos_administrativos') == DOC_ADM_OSTENSIVO:
return not (documento.restrito and user.is_anonymous)
return user.has_perm('protocoloadm.detail_documentoadministrativo')
if user.is_authenticated:
return True
return not documento.restrito and SaplAppConfig.attr(
'documentos_administrativos') == DOC_ADM_OSTENSIVO
class DocumentoAcessorioAdministrativo(models.Model):

4
sapl/protocoloadm/tests/test_docadm_acesso.py

@ -62,10 +62,10 @@ ACESSO = [
('O', True, 'sem_permissao', True),
('O', True, 'com_permissao', True),
('R', False, 'anonimo', False),
('R', False, 'sem_permissao', False),
('R', False, 'sem_permissao', True),
('R', False, 'com_permissao', True),
('R', True, 'anonimo', False),
('R', True, 'sem_permissao', False),
('R', True, 'sem_permissao', True),
('R', True, 'com_permissao', True),
]

17
sapl/sessao/tests/test_sessao_oculta_api.py

@ -70,16 +70,9 @@ def test_api_mostra_sessao_previa_ao_usuario_autenticado(client, sessoes):
@pytest.mark.django_db(transaction=False)
def test_media_de_sessao_previa_oculta_do_anonimo(client, settings, sessoes):
def test_media_de_sessao_e_publica(client, settings, sessoes):
settings.DEBUG = False
previa, iniciada = sessoes
def get(sessao):
return client.get('/media/sapl/public/sessaoplenaria/%s/pauta/p.pdf'
% sessao.pk)
assert get(previa).status_code == 404
assert get(iniciada).status_code == 200
client.force_login(baker.make(get_user_model()))
assert get(previa).status_code == 200
for sessao in sessoes:
response = client.get(
'/media/sapl/public/sessaoplenaria/%s/pauta/p.pdf' % sessao.pk)
assert response.status_code == 200

Loading…
Cancel
Save