Browse Source

Merge 9bea87062b into ecfd65a8b3

pull/3846/merge
Edward 1 week ago
committed by GitHub
parent
commit
51ea47f52b
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      requirements/requirements.txt
  2. 45
      sapl/base/receivers.py
  3. 12
      sapl/base/templatetags/common_tags.py
  4. 228
      sapl/base/tests/test_sanitize.py
  5. 28
      sapl/compilacao/forms.py
  6. 4
      sapl/compilacao/views.py
  7. 12
      sapl/crispy_layout_mixin.py
  8. 182
      sapl/sanitize.py
  9. 2
      sapl/templates/comissoes/materias_em_tramitacao.html
  10. 4
      sapl/templates/compilacao/dispositivo_form_search_fragment_child.html
  11. 2
      sapl/templates/compilacao/layout/dispositivo_checkbox.html
  12. 2
      sapl/templates/compilacao/layout/dispositivo_radio.html
  13. 2
      sapl/templates/compilacao/text_edit_bloco.html
  14. 2
      sapl/templates/compilacao/text_edit_blocoalteracao.html
  15. 2
      sapl/templates/compilacao/text_list_bloco.html
  16. 6
      sapl/templates/compilacao/text_list_blocoalteracao.html
  17. 2
      sapl/templates/compilacao/text_notificacoes.html
  18. 2
      sapl/templates/compilacao/textoarticulado_detail.html
  19. 2
      sapl/templates/materia/acompanhamento_materia.html
  20. 2
      sapl/templates/materia/materialegislativa_filter.html
  21. 2
      sapl/templates/materia/resumo_detail_materia.html
  22. 2
      sapl/templates/norma/normajuridica_filter.html
  23. 2
      sapl/templates/parlamentares/parlamentar_perfil_publico.html
  24. 2
      sapl/templates/protocoloadm/acompanhamento_documento.html
  25. 2
      sapl/templates/protocoloadm/documentoadministrativo_filter.html
  26. 2
      sapl/templates/relatorios/RelatorioMateriasPorAutor_filter.html
  27. 2
      sapl/templates/relatorios/RelatorioNormasPorAutor_filter.html
  28. 3
      sapl/templates/relatorios/blocos_sessao_plenaria/consideracoes_finais.html
  29. 3
      sapl/templates/relatorios/blocos_sessao_plenaria/expedientes.html
  30. 3
      sapl/templates/relatorios/blocos_sessao_plenaria/ocorrencias_da_sessao.html
  31. 2
      sapl/templates/relatorios/relatorio_doc_administrativos.html
  32. 2
      sapl/templates/relatorios/relatorio_normas_por_autor.html
  33. 10
      sapl/templates/relatorios/relatorio_pauta_sessao.html
  34. 6
      sapl/templates/search/search.html
  35. 2
      sapl/templates/sessao/adicionar_varias_materias_expediente.html
  36. 3
      sapl/templates/sessao/blocos_ata/consideracoes_finais.html
  37. 3
      sapl/templates/sessao/blocos_ata/expedientes.html
  38. 2
      sapl/templates/sessao/blocos_ata/materias_expediente.html
  39. 2
      sapl/templates/sessao/blocos_ata/materias_ordem_dia.html
  40. 3
      sapl/templates/sessao/blocos_ata/ocorrencias_da_sessao.html
  41. 3
      sapl/templates/sessao/blocos_resumo/consideracoes_finais.html
  42. 3
      sapl/templates/sessao/blocos_resumo/expedientes.html
  43. 3
      sapl/templates/sessao/blocos_resumo/ocorrencias_da_sessao.html
  44. 2
      sapl/templates/sessao/consideracoes_finais.html
  45. 2
      sapl/templates/sessao/leitura/leitura_bloco.html
  46. 2
      sapl/templates/sessao/ocorrencias_da_sessao.html
  47. 6
      sapl/templates/sessao/pauta_sessao_detail.html
  48. 2
      sapl/templates/sessao/votacao/nominal.html
  49. 6
      sapl/templates/sessao/votacao/nominal_detail.html
  50. 6
      sapl/templates/sessao/votacao/nominal_edit.html
  51. 2
      sapl/templates/sessao/votacao/nominal_transparencia.html
  52. 2
      sapl/templates/sessao/votacao/simbolica_transparencia.html
  53. 4
      sapl/templates/sessao/votacao/votacao.html
  54. 2
      sapl/templates/sessao/votacao/votacao_bloco.html
  55. 6
      sapl/templates/sessao/votacao/votacao_edit.html
  56. 4
      sapl/templates/sessao/votacao/votacao_nominal_bloco.html
  57. 8
      sapl/templates/sessao/votacao/votacao_simbolica_bloco.html

1
requirements/requirements.txt

@ -38,6 +38,7 @@ django-prometheus==2.2.0
asn1crypto==1.5.1
XlsxWriter==3.2.0
nh3==0.2.22
setuptools==80.9.0

45
sapl/base/receivers.py

@ -8,6 +8,7 @@ from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.core import serializers
from django.core.files.uploadedfile import InMemoryUploadedFile, UploadedFile
from django.db.models import TextField
from django.db.models.fields.files import FileField
from django.db.models.signals import post_delete, post_save, \
post_migrate, pre_save, pre_migrate
@ -22,11 +23,15 @@ from sapl.decorators import receiver_multi_senders
from sapl.materia.models import Tramitacao
from sapl.parlamentares.models import Parlamentar
from sapl.protocoloadm.models import TramitacaoAdministrativo
from sapl.sanitize import sanitize_field
from sapl.utils import get_base_url, models_with_gr_for_model
models_with_gr_for_autor = models_with_gr_for_model(Autor)
SAPL_APP_LABELS = frozenset(
app.rsplit('.', 1)[-1] for app in settings.SAPL_APPS)
@receiver_multi_senders(post_save, senders=models_with_gr_for_autor)
def handle_update_autor_related(sender, **kwargs):
@ -477,6 +482,46 @@ def signed_files_extraction_pre_save_signal(sender, instance, **kwargs):
signed_files_extraction_function(sender, instance, **kwargs)
# Cache dos TextField por modelo: a introspecção de _meta.fields a cada save
# pesa mais que a própria sanitização, e compilacao salva Dispositivo em laço.
_text_fields_cache = {}
def get_text_fields(model):
try:
return _text_fields_cache[model]
except KeyError:
fields = [f.name for f in model._meta.fields
if isinstance(f, TextField)]
_text_fields_cache[model] = fields
return fields
@receiver(pre_save, dispatch_uid='sanitize_textfields_pre_save_signal')
def sanitize_textfields_pre_save_signal(sender, instance, **kwargs):
"""Remove HTML/JavaScript perigoso de todo TextField dos modelos do SAPL.
Cobre forms, a API do drfautoapi, o admin e o shell num único ponto.
Ver sapl.sanitize para as políticas por campo.
"""
# loaddata (inclusive dentro de migrations) grava o objeto literalmente
if kwargs.get('raw'):
return
if sender._meta.app_label not in SAPL_APP_LABELS:
return
for fieldname in get_text_fields(sender):
value = getattr(instance, fieldname, None)
if not value:
continue
sanitized = sanitize_field(sender, fieldname, value)
if sanitized != value:
setattr(instance, fieldname, sanitized)
@receiver(pre_migrate, dispatch_uid='disconnect_signals_pre_migrate')
def disconnect_signals_pre_migrate(*args, **kwargs):
# sanitize_textfields_pre_save_signal não é desconectado aqui de propósito:
# é barato e idempotente, e desconectá-lo deixaria sem proteção qualquer
# migrate rodado no mesmo processo (é o caso da suíte de testes).
pre_save.disconnect(dispatch_uid='signed_files_extraction_pre_save_signal')

12
sapl/base/templatetags/common_tags.py

@ -10,6 +10,7 @@ from sapl.base.models import AppConfig
from sapl.materia.models import DocumentoAcessorio, MateriaLegislativa, Proposicao
from sapl.norma.models import NormaJuridica
from sapl.parlamentares.models import Filiacao
from sapl.sanitize import sanitize_html
from sapl.sessao.models import SessaoPlenaria
from sapl.utils import filiacao_data, SEPARADOR_HASH_PROPOSICAO, is_report_allowed
@ -396,6 +397,17 @@ def render_chunk_vendors(extension=None):
return ''
@register.filter(is_safe=True)
@stringfilter
def sanitize(value):
"""Renderiza HTML de campo rico (TinyMCE) sem script nem URL perigosa.
Substitui o |safe nos campos que legitimamente guardam HTML. Protege
também as linhas gravadas antes da sanitização no pre_save.
"""
return mark_safe(sanitize_html(value, rich=True))
@register.filter(is_safe=True)
@stringfilter
def dont_break_out(value):

228
sapl/base/tests/test_sanitize.py

@ -0,0 +1,228 @@
import pytest
from django.db.models.signals import pre_save
from django.template import Context, Template
from model_bakery import baker
from sapl.compilacao.forms import TipoDispositivoForm
from sapl.compilacao.models import TipoDispositivo, TipoTextoArticulado
from sapl.crispy_layout_mixin import get_field_display
from sapl.lexml.models import LexmlProvedor
from sapl.parlamentares.models import Parlamentar
from sapl.protocoloadm.models import TramitacaoAdministrativo
from sapl.sanitize import (html_fragment_is_balanced, sanitize_field,
sanitize_html, sanitize_scope)
from sapl.sessao.models import ExpedienteSessao
def test_plain_remove_marcacao_e_preserva_texto():
assert sanitize_html('<script>alert(1)</script>Ciente') == 'Ciente'
assert sanitize_html('Encaminhado <b>ao</b> setor') == \
'Encaminhado ao setor'
assert sanitize_html('<img src=x onerror=alert(1)>') == ''
assert sanitize_html('<div>a</div><div>b</div>') == 'ab'
def test_plain_guarda_texto_puro_sem_escape():
"""O escape é da renderização; gravado escapado, apareceria &amp; na tela."""
assert sanitize_html('Valor < 10 & prazo > 5') == 'Valor < 10 & prazo > 5'
assert sanitize_html('ALFA &amp; BETA') == 'ALFA & BETA'
def test_plain_preserva_quebras_de_linha():
# get_field_display converte \n em <br/> depois de sanitizar
assert sanitize_html('linha1\nlinha2') == 'linha1\nlinha2'
def test_valores_vazios_atravessam():
assert sanitize_html('') == ''
assert sanitize_html(None) is None
assert sanitize_html('', rich=True) == ''
@pytest.mark.parametrize('valor', [
'<script>alert(1)</script>Ciente',
'Valor < 10 & prazo > 5',
'Encaminhado <b>ao</b> <a href="https://x">setor</a>',
'texto &amp; cia',
])
def test_plain_e_idempotente(valor):
"""Salvar de novo um registro já sanitizado não altera o texto."""
uma_vez = sanitize_html(valor)
assert sanitize_html(uma_vez) == uma_vez
@pytest.mark.parametrize('valor', [
'<a href="https://camara.gov.br" target="_blank">Portal</a>',
'<p style="text-align: center;">centro</p>',
'<table><tr><td colspan="2">c</td></tr></table>',
'<script>alert(1)</script><b>ok</b>',
])
def test_rich_e_idempotente(valor):
uma_vez = sanitize_html(valor, rich=True)
assert sanitize_html(uma_vez, rich=True) == uma_vez
def test_rich_preserva_links():
saida = sanitize_html(
'<a href="https://camara.gov.br" target="_blank">Portal</a>',
rich=True)
assert 'href="https://camara.gov.br"' in saida
assert 'target="_blank"' in saida
assert 'rel="noopener noreferrer"' in saida
assert '>Portal</a>' in saida
assert 'href="/materia/123"' in sanitize_html(
'<a href="/materia/123">Matéria</a>', rich=True)
assert 'href="mailto:a@b.c"' in sanitize_html(
'<a href="mailto:a@b.c">mail</a>', rich=True)
def test_rich_remove_href_perigosa_mas_mantem_o_texto():
saida = sanitize_html(
'<a href="javascript:alert(1)">clique</a>', rich=True)
assert 'javascript' not in saida
assert 'clique' in saida
def test_rich_remove_script_e_manipuladores_de_evento():
saida = sanitize_html('<script>alert(1)</script><b>ok</b>', rich=True)
assert saida == '<b>ok</b>'
assert 'onclick' not in sanitize_html(
'<a href="#" onclick="steal()">x</a>', rich=True)
assert 'onerror' not in sanitize_html(
'<img src="x" onerror="alert(1)">', rich=True)
def test_rich_preserva_formatacao_do_tinymce():
"""Protege contra regressão visual no conteúdo já cadastrado."""
assert 'style="text-align: center;"' in sanitize_html(
'<p style="text-align: center;">centro</p>', rich=True)
saida = sanitize_html(
'<table><tr><td colspan="2">c</td></tr></table>', rich=True)
assert '<table>' in saida and 'colspan="2"' in saida
assert sanitize_html('<ul><li>a</li><li>b</li></ul>', rich=True) == \
'<ul><li>a</li><li>b</li></ul>'
def test_sanitize_scope():
assert sanitize_scope(TramitacaoAdministrativo, 'texto') == 'plain'
assert sanitize_scope(ExpedienteSessao, 'conteudo') == 'rich'
assert sanitize_scope(LexmlProvedor, 'xml') == 'exempt'
assert sanitize_scope(TipoTextoArticulado, 'rodape_global') == 'exempt'
assert sanitize_scope(Parlamentar, 'biografia') == 'rich'
def test_sanitize_field_respeita_isencao():
xml = '<xml><a href="javascript:x">y</a></xml>'
assert sanitize_field(LexmlProvedor, 'xml', xml) == xml
@pytest.mark.django_db
def test_pre_save_sanitiza_campo_simples():
t = baker.make(TramitacaoAdministrativo,
texto='<script>alert(1)</script>Ciente <b>ok</b>')
t.refresh_from_db()
assert t.texto == 'Ciente ok'
@pytest.mark.django_db
def test_pre_save_sanitiza_campo_rico_preservando_html():
e = baker.make(ExpedienteSessao,
conteudo='<b>x</b><script>alert(1)</script>'
'<a href="https://a.b" target="_blank">l</a>')
e.refresh_from_db()
assert '<script>' not in e.conteudo
assert '<b>x</b>' in e.conteudo
assert 'href="https://a.b"' in e.conteudo
@pytest.mark.django_db
def test_pre_save_nao_toca_modelo_isento():
xml = '<xml>a &amp; b <script>x</script></xml>'
p = baker.make(LexmlProvedor, xml=xml)
p.refresh_from_db()
assert p.xml == xml
@pytest.mark.django_db
def test_get_field_display_nao_devolve_script():
t = TramitacaoAdministrativo(texto='<script>alert(1)</script>Ciente')
__, display = get_field_display(t, 'texto')
assert '<script>' not in display
assert 'Ciente' in display
@pytest.mark.django_db
def test_get_field_display_protege_linha_legada():
"""Linhas gravadas antes do pre_save não passam pela camada de entrada."""
t = baker.make(TramitacaoAdministrativo, texto='ok')
TramitacaoAdministrativo.objects.filter(pk=t.pk).update(
texto='<script>alert(1)</script>legado')
t.refresh_from_db()
assert t.texto == '<script>alert(1)</script>legado'
__, display = get_field_display(t, 'texto')
assert '<script>' not in display
def test_pre_save_ignora_raw():
"""loaddata (inclusive em migrations) grava o objeto literalmente."""
t = TramitacaoAdministrativo(texto='<b>fixture</b>')
pre_save.send(sender=TramitacaoAdministrativo, instance=t, raw=True)
assert t.texto == '<b>fixture</b>'
def test_get_field_display_escapa_texto_puro_uma_unica_vez():
t = TramitacaoAdministrativo(texto='ALFA & BETA < 30')
__, display = get_field_display(t, 'texto')
assert 'ALFA &amp; BETA &lt; 30' in display
assert '&amp;amp;' not in display
def test_get_field_display_escapa_campo_isento():
p = LexmlProvedor(xml='<xml><script>x</script></xml>')
__, display = get_field_display(p, 'xml')
assert '<script>' not in display
assert '&lt;xml&gt;' in display
def test_striptags_apos_sanitize_nao_escapa_duas_vezes():
"""Blocos da ata: texto puro, sem entidades escapadas de novo."""
t = Template('{% load common_tags %}{{ v|sanitize|striptags }}')
saida = t.render(Context({
'v': '<p>Ofício&nbsp;12 lido &amp; arquivado</p>'
'<script>alert(1)</script><img src=x onerror=alert(1)>'}))
assert saida == 'Ofício&nbsp;12 lido &amp; arquivado'
@pytest.mark.parametrize('valor, esperado', [
('<br>', True),
('<br/>', True),
('<div class="titulo">Justificativa</div>', True),
('Art. ', True),
('<span class="x">', False),
('</span>', False),
('<b><i>x</b></i>', False),
])
def test_html_fragment_is_balanced(valor, esperado):
assert html_fragment_is_balanced(valor) is esperado
@pytest.mark.django_db
def test_tipo_dispositivo_form_rejeita_fragmento_desbalanceado():
td = baker.make(TipoDispositivo)
dados = {f: getattr(td, f) or '' for f in TipoDispositivoForm.Meta.fields}
dados['rotulo_prefixo_html'] = '<span class="rotulo">'
dados['rotulo_sufixo_html'] = '</span>'
form = TipoDispositivoForm(data=dados, instance=td)
assert not form.is_valid()
assert 'rotulo_prefixo_html' in form.errors
assert 'rotulo_sufixo_html' in form.errors
dados['rotulo_prefixo_html'] = '<br/>'
dados['rotulo_sufixo_html'] = ''
form = TipoDispositivoForm(data=dados, instance=td)
assert 'rotulo_prefixo_html' not in form.errors

28
sapl/compilacao/forms.py

@ -26,6 +26,7 @@ from sapl.compilacao.models import (NOTAS_PUBLICIDADE_CHOICES,
from sapl.compilacao.utils import DISPOSITIVO_SELECT_RELATED
from sapl.crispy_layout_mixin import SaplFormHelper
from sapl.crispy_layout_mixin import SaplFormLayout, to_column, to_row
from sapl.sanitize import RICH_TEXT_FIELDS, html_fragment_is_balanced
from sapl.utils import YES_NO_CHOICES, FileFieldCheckMixin
@ -51,6 +52,33 @@ ta_error_messages = {
}
class TipoDispositivoForm(ModelForm):
class Meta:
model = TipoDispositivo
fields = ['rotulo_ordinal', 'formato_variacao0',
'rotulo_separador_variacao01', 'formato_variacao1',
'rotulo_separador_variacao12', 'formato_variacao2',
'rotulo_separador_variacao23', 'formato_variacao3',
'rotulo_separador_variacao34', 'formato_variacao4',
'rotulo_separador_variacao45', 'formato_variacao5',
'rotulo_prefixo_html', 'rotulo_sufixo_html',
'texto_prefixo_html', 'texto_sufixo_html',
'nota_automatica_prefixo_html',
'nota_automatica_sufixo_html']
def clean(self):
cleaned_data = super().clean()
for fieldname in RICH_TEXT_FIELDS['compilacao.TipoDispositivo']:
value = cleaned_data.get(fieldname)
if value and not html_fragment_is_balanced(value):
self.add_error(fieldname, _(
'Toda tag aberta neste campo deve ser fechada nele '
'mesmo; tags divididas entre prefixo e sufixo são '
'descartadas ao salvar.'))
return cleaned_data
class TipoTaForm(ModelForm):
sigla = forms.CharField(
label=TipoTextoArticulado._meta.get_field(

4
sapl/compilacao/views.py

@ -39,7 +39,8 @@ from sapl.compilacao.forms import (DispositivoDefinidorVigenciaForm,
DispositivoRegistroRevogacaoForm,
DispositivoSearchModalForm, NotaForm,
PublicacaoForm, TaForm,
TextNotificacoesForm, TipoTaForm, VideForm)
TextNotificacoesForm, TipoDispositivoForm,
TipoTaForm, VideForm)
from sapl.compilacao.models import (STATUS_TA_EDITION, STATUS_TA_PRIVATE,
STATUS_TA_PUBLIC, Dispositivo, Nota,
PerfilEstruturalTextoArticulado,
@ -85,6 +86,7 @@ class TipoDispositivoCrud(CrudAux):
class UpdateView(CrudAux.UpdateView):
layout_key = 'TipoDispositivoUpdate'
form_class = TipoDispositivoForm
class ListView(CrudAux.ListView):
paginate_by = 100

12
sapl/crispy_layout_mixin.py

@ -8,9 +8,12 @@ from django.contrib.contenttypes.models import ContentType
from django.urls import reverse, reverse_lazy
from django.utils import formats
from django.utils.encoding import force_text
from django.utils.html import escape
from django.utils.translation import ugettext as _
import yaml
from sapl.sanitize import sanitize_html, sanitize_scope
def heads_and_tails(list_of_lists):
for alist in list_of_lists:
@ -167,7 +170,14 @@ def get_field_display(obj, fieldname):
args=(value.id,)),
value)
elif 'TextField' in str_type_from_field:
display = value.replace('\n', '<br/>')
scope = sanitize_scope(obj._meta.model, fieldname)
if scope == 'rich':
display = sanitize_html(value, rich=True)
elif scope == 'plain':
display = escape(sanitize_html(value))
else:
display = escape(value)
display = display.replace('\n', '<br/>')
display = '<div class="dont-break-out">{}</div>'.format(display)
else:
display = str(value)

182
sapl/sanitize.py

@ -0,0 +1,182 @@
"""Sanitização de HTML/JavaScript nos campos de texto livre do SAPL.
Módulo sem dependências internas do SAPL de propósito: é importado por
``sapl.crispy_layout_mixin``, ``sapl.base.receivers`` e pelos templatetags,
e ``sapl.utils`` já importa ``sapl.crispy_layout_mixin``.
Duas políticas:
* ``plain`` — remove toda a marcação e guarda texto puro, com as entidades
já decodificadas: o escape fica por conta da renderização. É o padrão para
qualquer ``TextField``.
* ``rich`` — allowlist para os campos editados via TinyMCE, que contêm HTML
legítimo (negrito, listas, tabelas e links).
A ``rich`` é idempotente, o que permite aplicá-la tanto no ``pre_save`` quanto
na renderização. A ``plain`` só não é idempotente para texto que seja HTML
codificado em entidades (``&lt;b&gt;``): a primeira passada decodifica, a
segunda remove a tag. Na renderização, campos ``plain`` são escapados em vez
de re-sanitizados.
"""
import html
from html.parser import HTMLParser
import nh3
SANITIZE_RICH_TAGS = {
'p', 'br', 'hr', 'div', 'span',
'b', 'strong', 'i', 'em', 'u', 's', 'strike', 'sub', 'sup',
'ul', 'ol', 'li', 'dl', 'dt', 'dd', 'blockquote', 'pre', 'code',
'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
'table', 'thead', 'tbody', 'tfoot', 'tr', 'th', 'td', 'caption',
'col', 'colgroup',
'a', 'img',
}
# 'style' mantém o alinhamento produzido pelos botões do TinyMCE;
# 'target' mantém o "abrir em nova aba" dos links já cadastrados.
# 'rel' não pode entrar aqui: o nh3 aborta se a tag 'a' declarar 'rel'
# ao mesmo tempo em que link_rel está definido — ele mesmo escreve o atributo.
SANITIZE_RICH_ATTRS = {
'*': {'style', 'class', 'align', 'title', 'dir', 'lang'},
'a': {'href', 'target', 'name'},
'img': {'src', 'alt', 'width', 'height'},
'td': {'colspan', 'rowspan', 'headers'},
'th': {'colspan', 'rowspan', 'scope', 'headers'},
'col': {'span'},
'colgroup': {'span'},
'table': {'border', 'cellpadding', 'cellspacing', 'summary'},
}
SANITIZE_URL_SCHEMES = {'http', 'https', 'mailto', 'tel'}
# O conteúdo destas tags é descartado junto com a tag. Sem isso o texto de
# dentro de um <script> sobreviveria como texto solto.
SANITIZE_CLEAN_CONTENT_TAGS = {'script', 'style'}
# Campos que guardam HTML legítimo, indexados por '<app_label>.<Model>'.
# Os de sessao e compilacao.Dispositivo são editados no TinyMCE; os de
# compilacao.TipoDispositivo são fragmentos de template configurados por
# administradores.
RICH_TEXT_FIELDS = {
'base.CasaLegislativa': {'informacao_geral'},
'norma.NormaRelacionada': {'resumo'},
'parlamentares.Legislatura': {'observacao'},
'parlamentares.Parlamentar': {'biografia'},
'sessao.ExpedienteSessao': {'conteudo'},
'sessao.OcorrenciaSessao': {'conteudo'},
'sessao.ConsideracoesFinais': {'conteudo'},
'compilacao.Dispositivo': {'texto', 'texto_atualizador'},
'compilacao.TipoDispositivo': {
'rotulo_prefixo_html', 'rotulo_sufixo_html',
'texto_prefixo_html', 'texto_sufixo_html',
'nota_automatica_prefixo_html', 'nota_automatica_sufixo_html',
},
}
# Campos que não devem ser tocados em hipótese alguma.
SANITIZE_EXEMPT_FIELDS = {
# xml é XML fornecido pela equipe do LexML; já é escapado em pretty_xml
'lexml.LexmlProvedor': {'xml'},
# rodape_global é interpolado dentro de um content: de CSS
'compilacao.TipoTextoArticulado': {'rodape_global'},
}
def model_key(model):
return '{}.{}'.format(model._meta.app_label, model.__name__)
def sanitize_scope(model, fieldname):
"""Retorna 'exempt', 'rich' ou 'plain' para um campo de um modelo."""
key = model_key(model)
if fieldname in SANITIZE_EXEMPT_FIELDS.get(key, ()):
return 'exempt'
if fieldname in RICH_TEXT_FIELDS.get(key, ()):
return 'rich'
return 'plain'
def sanitize_html(value, rich=False):
"""Remove HTML/JavaScript perigoso de ``value``.
Com ``rich=False`` toda a marcação é removida e apenas o texto sobra,
sem escape: ``&`` continua ``&``.
Com ``rich=True`` aplica-se a allowlist: links são preservados, mas
esquemas de URL fora de SANITIZE_URL_SCHEMES (javascript:, data:) e
manipuladores de evento (onclick, onerror) são descartados.
"""
if not value:
return value
if not isinstance(value, str):
value = str(value)
if rich:
return nh3.clean(
value,
tags=SANITIZE_RICH_TAGS,
attributes=SANITIZE_RICH_ATTRS,
url_schemes=SANITIZE_URL_SCHEMES,
clean_content_tags=SANITIZE_CLEAN_CONTENT_TAGS,
link_rel='noopener noreferrer',
strip_comments=True)
return html.unescape(nh3.clean(
value,
tags=set(),
attributes={},
clean_content_tags=SANITIZE_CLEAN_CONTENT_TAGS,
link_rel=None,
strip_comments=True))
def sanitize_field(model, fieldname, value):
"""Sanitiza ``value`` conforme a política do campo.
Campos isentos atravessam sem modificação.
"""
scope = sanitize_scope(model, fieldname)
if scope == 'exempt':
return value
return sanitize_html(value, rich=(scope == 'rich'))
_VOID_TAGS = {
'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link',
'meta', 'param', 'source', 'track', 'wbr',
}
class _BalanceChecker(HTMLParser):
def __init__(self):
super().__init__(convert_charrefs=True)
self.stack = []
self.balanced = True
def handle_starttag(self, tag, attrs):
if tag not in _VOID_TAGS:
self.stack.append(tag)
def handle_startendtag(self, tag, attrs):
pass
def handle_endtag(self, tag):
if tag in _VOID_TAGS:
return
if not self.stack or self.stack.pop() != tag:
self.balanced = False
def html_fragment_is_balanced(value):
"""Indica se toda tag aberta em ``value`` é fechada nele mesmo.
Fragmentos desbalanceados (``<span>`` num campo, ``</span>`` em outro)
são reescritos pela sanitização ``rich``, que fecha ou descarta as tags.
"""
checker = _BalanceChecker()
checker.feed(value)
checker.close()
return checker.balanced and not checker.stack

2
sapl/templates/comissoes/materias_em_tramitacao.html

@ -38,7 +38,7 @@
{{ materia_em_tramitacao.materia.ementa|dont_break_out }}
</td>
<td>
{{ materia_em_tramitacao.tramitacao.status.descricao|linebreaksbr|safe }}
{{ materia_em_tramitacao.tramitacao.status.descricao|linebreaksbr }}
</td>
<td>
{% if materia_em_tramitacao.tramitacao.data_fim_prazo %}

4
sapl/templates/compilacao/dispositivo_form_search_fragment_child.html

@ -1,4 +1,4 @@
{% load i18n compilacao_filters %}
{% load i18n compilacao_filters common_tags %}
<li>
<div class="iteminput">
@ -30,7 +30,7 @@
{{ df.tipo_dispositivo.rotulo_prefixo_html|safe }}
<a name="{{df.pk}}" title="{{df.pk}}">{% if df.rotulo or df.nivel == 1%}{{ df.rotulo }}{%else%}[{{ df|nomenclatura}} {% trans "de" %} {{ df.dispositivo_pai.rotulo }}] - {% endif %}</a>
{{ df.tipo_dispositivo.rotulo_sufixo_html|safe }}
<span class="dtxt" id="d{% if not df.dispositivo_subsequente_id and df.dispositivo_substituido_id %}a{% endif %}{{df.pk}}" pks="{{df.dispositivo_substituido_id|default:''}}" pk="{{df.pk}}">{{ df.tipo_dispositivo.texto_prefixo_html|safe }}{%if df.texto %}{{ df.texto|safe }}{%else%}{%if not df.tipo_dispositivo.dispositivo_de_articulacao %}&nbsp;{% endif %}{% endif %}</span>
<span class="dtxt" id="d{% if not df.dispositivo_subsequente_id and df.dispositivo_substituido_id %}a{% endif %}{{df.pk}}" pks="{{df.dispositivo_substituido_id|default:''}}" pk="{{df.pk}}">{{ df.tipo_dispositivo.texto_prefixo_html|safe }}{%if df.texto %}{{ df.texto|sanitize }}{%else%}{%if not df.tipo_dispositivo.dispositivo_de_articulacao %}&nbsp;{% endif %}{% endif %}</span>
{% if df.ta_publicado_id %}
<a class="nota-alteracao" href="{% url 'sapl.compilacao:ta_text' df.ta_publicado.pk %}#{{df.dispositivo_atualizador_id}}">
{{ df.tipo_dispositivo.nota_automatica_prefixo_html|safe }}

2
sapl/templates/compilacao/layout/dispositivo_checkbox.html

@ -37,7 +37,7 @@
{{ choice.1.tipo_dispositivo.rotulo_prefixo_html|safe }}
<a name="{{choice.1.pk}}" title="{{choice.1.pk}}">{% if choice.1.rotulo %}{{ choice.1.rotulo }}{%else%}[{{ choice.1|nomenclatura}}{% if choice.1.dispositivo_pai %} {% trans "de" %} {{ choice.1.dispositivo_pai.rotulo }}{% endif %}] - {% endif %}</a>
{{ choice.1.tipo_dispositivo.rotulo_sufixo_html|safe }}
<span class="dtxt" id="d{% if not choice.1.dispositivo_subsequente_id and choice.1.dispositivo_substituido_id %}a{% endif %}{{choice.1.pk}}" pks="{{choice.1.dispositivo_substituido_id|default:''}}" pk="{{choice.1.pk}}">{{ choice.1.tipo_dispositivo.texto_prefixo_html|safe }}{%if choice.1.texto %}{{ choice.1.texto|safe }}{%else%}{%if not choice.1.tipo_dispositivo.dispositivo_de_articulacao %}&nbsp;{% endif %}{% endif %}</span>
<span class="dtxt" id="d{% if not choice.1.dispositivo_subsequente_id and choice.1.dispositivo_substituido_id %}a{% endif %}{{choice.1.pk}}" pks="{{choice.1.dispositivo_substituido_id|default:''}}" pk="{{choice.1.pk}}">{{ choice.1.tipo_dispositivo.texto_prefixo_html|safe }}{%if choice.1.texto %}{{ choice.1.texto|sanitize }}{%else%}{%if not choice.1.tipo_dispositivo.dispositivo_de_articulacao %}&nbsp;{% endif %}{% endif %}</span>
{% if choice.1.ta_publicado_id and not choice.1.tipo_dispositivo.dispositivo_de_articulacao %}
<a class="nota-alteracao" target="_blank" href="{% url 'sapl.compilacao:ta_text' choice.1.ta_publicado.pk %}#{{choice.1.dispositivo_atualizador_id}}">
{{ choice.1.tipo_dispositivo.nota_automatica_prefixo_html|safe }}

2
sapl/templates/compilacao/layout/dispositivo_radio.html

@ -35,7 +35,7 @@
<span class="dtxt"
id="d{% if not dpt.dispositivo_subsequente_id and dpt.dispositivo_substituido_id %}a{% endif %}{{dpt.pk}}"
pks="{{dpt.dispositivo_substituido_id|default:''}}"
pk="{{dpt.pk}}">{{ dpt.tipo_dispositivo.texto_prefixo_html|safe }}{%if dpt.texto %}{{ dpt.texto|safe }}{%else%}{%if not dpt.tipo_dispositivo.dispositivo_de_articulacao %}&nbsp;{% endif %}{% endif %}</span>
pk="{{dpt.pk}}">{{ dpt.tipo_dispositivo.texto_prefixo_html|safe }}{%if dpt.texto %}{{ dpt.texto|sanitize }}{%else%}{%if not dpt.tipo_dispositivo.dispositivo_de_articulacao %}&nbsp;{% endif %}{% endif %}</span>
{% if dpt.ta_publicado_id and not dpt.tipo_dispositivo.dispositivo_de_articulacao %}
<a class="nota-alteracao" href="{%url 'sapl.compilacao:ta_text' dpt.ta_publicado.pk %}#{{dpt.dispositivo_atualizador_id}}">
{{ dpt.tipo_dispositivo.nota_automatica_prefixo_html|safe }}

2
sapl/templates/compilacao/text_edit_bloco.html

@ -43,7 +43,7 @@
{% if not node.dpt.texto and not node.td.dispositivo_de_articulacao %}
<span class="semtexto">({{node.td}} sem texto)</span>
{%else%}
{% if node.da and node.dpt.texto_atualizador and node in node.da.alts%}{{ node.dpt.texto_atualizador|safe }}{% else %}{{ node.dpt.texto|safe }}{% endif %}
{% if node.da and node.dpt.texto_atualizador and node in node.da.alts%}{{ node.dpt.texto_atualizador|sanitize }}{% else %}{{ node.dpt.texto|sanitize }}{% endif %}
{%endif%}
{% if node.na %}
<a class="dpt-link nota-alteracao" href="{% url 'sapl.compilacao:ta_text_edit' node.da.dpt.ta_id %}#{{node.da.dpt.pk}}" title="{% trans 'Abrir Edição do Dispositivo Alterador'%}">

2
sapl/templates/compilacao/text_edit_blocoalteracao.html

@ -5,7 +5,7 @@
<div class="dpt" id="dpt{{ch.id}}" pk="{{ch.id}}" >
<div class="{{ ch.tipo_dispositivo.class_css }}" id="id{{ch.id}}" nivel="{{ch.nivel}}" style="margin: 0px;">
<div class="de-compilacao" id="id{{ch.id}}" pk="{{ch.pk}}" ordem="{{ch.ordem}}" name="{{ch.pk}}" title="{{ch.pk}} - {{ch.ordem}}">
{{ ch.tipo_dispositivo.rotulo_prefixo_html|safe }}<a name="{{ch.pk}}" href="{% url 'sapl.compilacao:ta_text_edit' ch.ta.pk %}#{{ch.pk}}">{{ ch.rotulo }}</a>{{ ch.tipo_dispositivo.rotulo_sufixo_html|safe }}{{ ch.tipo_dispositivo.texto_prefixo_html|safe }}{{ ch.texto|safe }}
{{ ch.tipo_dispositivo.rotulo_prefixo_html|safe }}<a name="{{ch.pk}}" href="{% url 'sapl.compilacao:ta_text_edit' ch.ta.pk %}#{{ch.pk}}">{{ ch.rotulo }}</a>{{ ch.tipo_dispositivo.rotulo_sufixo_html|safe }}{{ ch.tipo_dispositivo.texto_prefixo_html|safe }}{{ ch.texto|sanitize }}
</div>
</div>
</div>

2
sapl/templates/compilacao/text_list_bloco.html

@ -32,7 +32,7 @@
{% endif %}
<div class="dtxt" id="d{% if not dpt.dispositivo_subsequente_id and dpt.dispositivo_substituido_id %}a{% endif %}{{dpt.pk}}" pks="{{dpt.dispositivo_substituido_id|default:''}}" pk="{{dpt.pk}}">{{ dpt.tipo_dispositivo.texto_prefixo_html|safe }}{%if dpt.texto %}{{ dpt.texto|safe }}{%else%}{%if not dpt.tipo_dispositivo.dispositivo_de_articulacao %}&nbsp;{% endif %}{% endif %}</div>
<div class="dtxt" id="d{% if not dpt.dispositivo_subsequente_id and dpt.dispositivo_substituido_id %}a{% endif %}{{dpt.pk}}" pks="{{dpt.dispositivo_substituido_id|default:''}}" pk="{{dpt.pk}}">{{ dpt.tipo_dispositivo.texto_prefixo_html|safe }}{%if dpt.texto %}{{ dpt.texto|sanitize }}{%else%}{%if not dpt.tipo_dispositivo.dispositivo_de_articulacao %}&nbsp;{% endif %}{% endif %}</div>
{% if dpt.ta_publicado_id %}
<a class="nota-alteracao" href="{%url 'sapl.compilacao:ta_text' dpt.ta_publicado.pk %}#{{dpt.dispositivo_atualizador_id}}">
{{ dpt.tipo_dispositivo.nota_automatica_prefixo_html|safe }}

6
sapl/templates/compilacao/text_list_blocoalteracao.html

@ -19,15 +19,15 @@
{% if ch.rotulo %}
<a name="{{ch.pk}}" href="{% url 'sapl.compilacao:ta_text' ch.ta.pk %}#{{ch.pk}}">{{ ch.rotulo }}</a>
{{ ch.tipo_dispositivo.rotulo_sufixo_html|safe }}
{{ ch.tipo_dispositivo.texto_prefixo_html|safe }}{% if ch.texto_atualizador %}{{ ch.texto_atualizador|safe }}{%else%}{{ ch.texto|safe }}{% endif %}
{{ ch.tipo_dispositivo.texto_prefixo_html|safe }}{% if ch.texto_atualizador %}{{ ch.texto_atualizador|sanitize }}{%else%}{{ ch.texto|sanitize }}{% endif %}
{% elif not ch.rotulo and not ch.auto_inserido %}
<a name="{{ch.pk}}" href="{% url 'sapl.compilacao:ta_text' ch.ta.pk %}#{{ch.pk}}">
{{ ch.tipo_dispositivo.rotulo_sufixo_html|safe }}
{{ ch.tipo_dispositivo.texto_prefixo_html|safe }}{% if ch.texto_atualizador %}{{ ch.texto_atualizador|safe }}{%else%}{{ ch.texto|safe }}{% endif %}
{{ ch.tipo_dispositivo.texto_prefixo_html|safe }}{% if ch.texto_atualizador %}{{ ch.texto_atualizador|sanitize }}{%else%}{{ ch.texto|sanitize }}{% endif %}
</a>
{% else %}
{{ ch.tipo_dispositivo.rotulo_sufixo_html|safe }}
{{ ch.tipo_dispositivo.texto_prefixo_html|safe }}{% if ch.texto_atualizador %}{{ ch.texto_atualizador|safe }}{%else%}{{ ch.texto|safe }}{% endif %}
{{ ch.tipo_dispositivo.texto_prefixo_html|safe }}{% if ch.texto_atualizador %}{{ ch.texto_atualizador|sanitize }}{%else%}{{ ch.texto|sanitize }}{% endif %}
{% endif %}
{% if ch.imagem %}

2
sapl/templates/compilacao/text_notificacoes.html

@ -23,7 +23,7 @@
<span class="dtxt"
id="d{% if not dpt.dispositivo_subsequente_id and dpt.dispositivo_substituido_id %}a{% endif %}{{dpt.pk}}"
pks="{{dpt.dispositivo_substituido_id|default:''}}"
pk="{{dpt.pk}}">{{ dpt.tipo_dispositivo.texto_prefixo_html|safe }}{%if dpt.texto %}{{ dpt.texto|safe }}{%else%}{%if not dpt.tipo_dispositivo.dispositivo_de_articulacao %}&nbsp;{% endif %}{% endif %}</span>
pk="{{dpt.pk}}">{{ dpt.tipo_dispositivo.texto_prefixo_html|safe }}{%if dpt.texto %}{{ dpt.texto|sanitize }}{%else%}{%if not dpt.tipo_dispositivo.dispositivo_de_articulacao %}&nbsp;{% endif %}{% endif %}</span>
{%if dpt.ta_publicado_id %}
<a class="nota-alteracao clearfix" href="{%url 'sapl.compilacao:ta_text' dpt.ta_publicado.pk %}#{{dpt.dispositivo_atualizador_id}}">

2
sapl/templates/compilacao/textoarticulado_detail.html

@ -75,7 +75,7 @@
<div class="col-md-12">
<div id="div_id_ementa" class="holder">
<label>{% field_verbose_name object 'ementa' %}</label>
<p>{{ object.ementa|safe}}</p>
<p>{{ object.ementa}}</p>
</div>
</div>
</div>

2
sapl/templates/materia/acompanhamento_materia.html

@ -19,7 +19,7 @@
</div>
<div class="row">
<div class="col-md-12"><b>Ementa:</b> {{materia.ementa|safe}}</div>
<div class="col-md-12"><b>Ementa:</b> {{materia.ementa}}</div>
</div>
{% crispy form %}

2
sapl/templates/materia/materialegislativa_filter.html

@ -153,7 +153,7 @@
{% if m.tramitacao_set.first.data_tramitacao %}
<strong>Data da última Tramitação:</strong> &nbsp;{{m.tramitacao_set.first.data_tramitacao}}</br>
{% if m.tramitacao_set.first.texto %}
<strong>Última Ação:</strong> &nbsp; {{m.tramitacao_set.first.texto|safe}}</br>
<strong>Última Ação:</strong> &nbsp; {{m.tramitacao_set.first.texto}}</br>
{% endif %}
{% endif %}
{% if m.anexo_de.exists %}

2
sapl/templates/materia/resumo_detail_materia.html

@ -3,5 +3,5 @@
<div class="col-md-4"><b>Número: </b>{{object.numero}}</div>
<div class="col-md-4"><b>Ano: </b>{{object.ano}}</div>
</div>
<b>Ementa:</b> {{object.ementa|safe}}
<b>Ementa:</b> {{object.ementa}}
<br /><br />

2
sapl/templates/norma/normajuridica_filter.html

@ -62,7 +62,7 @@
{% endif %}
<strong>Ementa:</strong><br>
{{n.ementa|safe}}<br>
{{n.ementa}}<br>
{% if n.texto_articulado.exists and not n.texto_articulado.first.privacidade %}
<a href="{% url 'sapl.norma:normajuridica_detail' n.id %}">

2
sapl/templates/parlamentares/parlamentar_perfil_publico.html

@ -61,7 +61,7 @@
<div class="col-sm-8">
<div id="div_biografia" class="form-group">
<p><b>Biografia: </b> &nbsp {{object.biografia|safe}}</p>
<p><b>Biografia: </b> &nbsp {{object.biografia|sanitize}}</p>
</div>
</div>
</div>

2
sapl/templates/protocoloadm/acompanhamento_documento.html

@ -19,7 +19,7 @@
</div>
<div class="row">
<div class="col-md-12"><b>Assunto:</b> {{documento.assunto|safe}}</div>
<div class="col-md-12"><b>Assunto:</b> {{documento.assunto}}</div>
</div>
{% crispy form %}

2
sapl/templates/protocoloadm/documentoadministrativo_filter.html

@ -48,7 +48,7 @@
<a href="{% url 'sapl.protocoloadm:documentoadministrativo_detail' d.id %}"><strong>{{d}}</strong></a></br>
<strong>Interessado:</strong>&nbsp;{{ d.interessado|default_if_none:"Não informado"}}
</br>
<strong>Assunto:</strong>&nbsp;{{ d.assunto|safe }}
<strong>Assunto:</strong>&nbsp;{{ d.assunto }}
</br>
{% if d.protocolo %}
<strong>Protocolo:</strong>&nbsp;<a href="{% url 'sapl.protocoloadm:protocolo_mostrar' d.protocolo.id %}">{{ d.protocolo}}</a></br>

2
sapl/templates/relatorios/RelatorioMateriasPorAutor_filter.html

@ -54,7 +54,7 @@
{{materia.tipo.sigla}} {{materia.numero}}/{{materia.ano}}
</a>
</td>
<td>{% autoescape off %}{{materia.ementa}}<br>{{materia.observacao}}{% endautoescape %}</td>
<td>{{materia.ementa}}<br>{{materia.observacao}}</td>
<td>
{% for autoria in materia.autoria_set.all %}
{% if autoria.autor != autor %}

2
sapl/templates/relatorios/RelatorioNormasPorAutor_filter.html

@ -52,7 +52,7 @@
<td><a href="{% url 'sapl.norma:normajuridica_detail' norma.pk %}">
{{norma.tipo.sigla}} {{norma.numero}}/{{norma.ano}}
</a></td>
<td>{% autoescape off %}{{norma.ementa}}<br>{{norma.observacao}}{% endautoescape %}</td>
<td>{{norma.ementa}}<br>{{norma.observacao}}</td>
<td>
{% for autoria in norma.autorianorma_set.all %}
{% if autoria.autor != autor %}

3
sapl/templates/relatorios/blocos_sessao_plenaria/consideracoes_finais.html

@ -1,4 +1,5 @@
{% load common_tags %}
<h2 class="gray-title">Considerações Finais</h2>
{% for c in lst_consideracoes%}
<p>{{c|striptags|safe}}</p>
<p>{{c|sanitize|striptags}}</p>
{% endfor %}

3
sapl/templates/relatorios/blocos_sessao_plenaria/expedientes.html

@ -1,5 +1,6 @@
{% load common_tags %}
<h2 class="gray-title">Expedientes</h2>
{% for expediente in lst_expedientes%}
<h3>{{expediente.nom_expediente}}</h3>
<div style="margin-bottom: 1cm">{{expediente.txt_expediente|safe}}</div>
<div style="margin-bottom: 1cm">{{expediente.txt_expediente|sanitize}}</div>
{% endfor%}

3
sapl/templates/relatorios/blocos_sessao_plenaria/ocorrencias_da_sessao.html

@ -1,4 +1,5 @@
{% load common_tags %}
<h2 class="gray-title">Ocorrências da Sessão</h2>
{% for o in lst_ocorrencias%}
<p>{{o.conteudo|striptags|safe}}</p>
<p>{{o.conteudo|sanitize|striptags}}</p>
{% endfor %}

2
sapl/templates/relatorios/relatorio_doc_administrativos.html

@ -13,7 +13,7 @@
<strong><a href="{% url 'sapl.protocoloadm:documentoadministrativo_detail' d.id %}">{{d.tipo.sigla}} {{d.numero}}/{{d.ano}} - {{d.tipo}}</strong></a></br>
<strong>Interessado:</strong>&nbsp;{{ d.interessado|default_if_none:"Não informado"}}
</br>
<strong>Assunto:</strong>&nbsp;<a>{{ d.assunto|safe}}</a>
<strong>Assunto:</strong>&nbsp;<a>{{ d.assunto}}</a>
</br>
{% if d.protocolo %}
<strong>Protocolo:</strong>&nbsp;<a href="{% url 'sapl.protocoloadm:protocolo_mostrar' d.protocolo.id %}">{{ d.protocolo}}</a></br>

2
sapl/templates/relatorios/relatorio_normas_por_autor.html

@ -47,7 +47,7 @@
<td>
{{norma.tipo.sigla}} {{norma.numero}}/{{norma.ano}}
</td>
<td>{% autoescape off %}{{norma.ementa}}<br>{{norma.observacao}}{% endautoescape %}</td>
<td>{{norma.ementa}}<br>{{norma.observacao}}</td>
<td>
{% if norma.autorianorma_set.first != norma.autorianorma_set.last %}
{% for autor in norma.autorianorma_set.all %}

10
sapl/templates/relatorios/relatorio_pauta_sessao.html

@ -39,7 +39,7 @@
</table>
<h2 class="gray-title">Expedientes</h2>
{% for e in expedientes %}
<b>{{ e.tipo }}: </b><br/><p>{{ e.conteudo|safe }}</p>
<b>{{ e.tipo }}: </b><br/><p>{{ e.conteudo|sanitize }}</p>
{% endfor %}
<h2 class="gray-title">Matérias do Expediente</h2>
{% if materia_expediente %}
@ -53,10 +53,10 @@
<b>Autor{{ m.autor|length|pluralize:"es" }}</b>: {{ m.autor|join:', ' }}
</td>
<td style="width:60%;">
{{ m.ementa|safe }}
{{ m.ementa }}
{% if m.observacao %}<br><br>Obs.: {{m.observacao}} {% endif %}
</td>
<td style="width:20%;">{{m.situacao|linebreaksbr|safe}}</td>
<td style="width:20%;">{{m.situacao|linebreaksbr}}</td>
</tr>
{% endfor %}
</table>
@ -75,10 +75,10 @@
<b>Autor{{ m.autor|length|pluralize:"es" }}</b>: {{ m.autor|join:', ' }}
</td>
<td style="width:60%;">
{{m.ementa|safe}}
{{m.ementa}}
{% if m.observacao %}<br><br>Obs.: {{m.observacao}} {% endif %}
</td>
<td style="width:20%;">{{m.situacao|linebreaksbr|safe}}</td>
<td style="width:20%;">{{m.situacao|linebreaksbr}}</td>
</tr>
{% endfor %}
</table>

6
sapl/templates/search/search.html

@ -51,7 +51,7 @@
{% if result.object|search_get_model == 'm' %}
<p>
<strong>Matéria Legislativa: </strong> <a href="{% url 'sapl.materia:materialegislativa_detail' result.object.pk %}">{{ result.object }}</a></br>
{{result.object.ementa|safe}}<br>
{{result.object.ementa}}<br>
{% if result.object.texto_original %}
<strong>Texto Original:</strong> <a href="{{result.object.texto_original.url}}"> Clique aqui </a></br>
@ -64,7 +64,7 @@
{% elif result.object|search_get_model == 'd' %}
<p>
<strong> Documento Acessório: </strong><a href="{% url 'sapl.materia:documentoacessorio_detail' result.object.pk %}">{{ result.object }}</a></br>
{{result.object.ementa|safe}}<br>
{{result.object.ementa}}<br>
{% if result.object.arquivo %}
<strong>Texto Original:</strong> <a href="{{result.object.arquivo.url}}"> Clique aqui </a></br>
{% endif %}
@ -73,7 +73,7 @@
{% elif result.object|search_get_model == 'n' %}
<p>
<strong> Norma Jurídica: </strong><a href="{% url 'sapl.norma:normajuridica_detail' result.object.pk %}">{{ result.object }}</a></br>
{{ result.object.ementa|safe }}<br>
{{ result.object.ementa }}<br>
{% if result.object.texto_integral %}
<strong>Texto Original:</strong> <a href="{{result.object.texto_integral.url}}"> Clique aqui </a></br>
{% endif %}

2
sapl/templates/sessao/adicionar_varias_materias_expediente.html

@ -65,7 +65,7 @@
<strong>Localização Atual:</strong> &nbsp;{{ m.tramitacao_set.first.unidade_tramitacao_destino|default_if_none:"Não informado" }}</br>
<strong>Status:</strong> &nbsp;{{ m.tramitacao_set.first.status|default_if_none:"Não informado" }}</br>
<strong>Data da última Tramitação:</strong> &nbsp;{{ m.tramitacao_set.first.data_tramitacao|default_if_none:"Não informado" }}</br>
<strong>Ementa:</strong>&nbsp;{{ m.ementa|safe }}</br>
<strong>Ementa:</strong>&nbsp;{{ m.ementa }}</br>
<p></p>
<td width="20%%">
<input type="radio" name="tipo_votacao_{{ m.id }}" id="tipo_votacao_{{ m.id }}_1" value="1"> <label for="tipo_votacao_{{ m.id }}_1">Simbólica</label>

3
sapl/templates/sessao/blocos_ata/consideracoes_finais.html

@ -1,8 +1,9 @@
{% load common_tags %}
{% if object.consideracoesfinais.conteudo %}
<fieldset>
<p style="text-align: justify;margin-top: 0">
<strong>Considerações Finais: </strong>
{{object.consideracoesfinais.conteudo|striptags|safe}}
{{object.consideracoesfinais.conteudo|sanitize|striptags}}
</p>
</fieldset>
{% endif %}

3
sapl/templates/sessao/blocos_ata/expedientes.html

@ -1,3 +1,4 @@
{% load common_tags %}
{% if expedientes %}
<fieldset>
<p style="text-align: justify;margin-top: 0">
@ -5,7 +6,7 @@
{% for e in expedientes %}
{% if e.conteudo %}
<b>{{ e.tipo }}</b>:
{{ e.conteudo|striptags|safe }}
{{ e.conteudo|sanitize|striptags }}
{% endif %}
{% endfor %}
</p>

2
sapl/templates/sessao/blocos_ata/materias_expediente.html

@ -4,7 +4,7 @@
<strong>Matérias do Expediente: </strong>
{% for m in materia_expediente %}
<b>{{ m.numero }} - {{ m.titulo }}</b>,
{{ m.ementa|safe }}
{{ m.ementa }}
{% if m.observacao %} - Obs.: {{ m.observacao }} {% endif %}
Autor{{ m.autor|length|pluralize:"es" }}: {{ m.autor|join:', ' }},
{% if m.numero_protocolo %}

2
sapl/templates/sessao/blocos_ata/materias_ordem_dia.html

@ -5,7 +5,7 @@
<strong>Matérias da Ordem do Dia: </strong>
{% for m in materias_ordem %}
<b>{{ m.numero }} - {{ m.titulo }}</b>,
{{ m.ementa|safe }}
{{ m.ementa }}
{% if m.observacao %} - Obs.: {{ m.observacao }} {% endif %}
Autor{{ m.autor|length|pluralize:"es" }}: {{ m.autor|join:', ' }},
{% if m.numero_protocolo %}

3
sapl/templates/sessao/blocos_ata/ocorrencias_da_sessao.html

@ -1,8 +1,9 @@
{% load common_tags %}
{% if object.ocorrenciasessao.conteudo %}
<fieldset>
<p style="text-align: justify;margin-top: 0">
<strong>Ocorrências da Sessão: </strong>
{{ object.ocorrenciasessao.conteudo|striptags|safe }}
{{ object.ocorrenciasessao.conteudo|sanitize|striptags }}
</p>
</fieldset>
{% endif %}

3
sapl/templates/sessao/blocos_resumo/consideracoes_finais.html

@ -1,8 +1,9 @@
{% load common_tags %}
{% if object.consideracoesfinais.conteudo %}
<fieldset>
<legend>Considerações Finais</legend>
<div style="border:0.5px solid #BAB4B1; border-radius: 10px; background-color: rgba(225, 225, 225, .8);">
<p>{{object.consideracoesfinais.conteudo|safe}}</p>
<p>{{object.consideracoesfinais.conteudo|sanitize}}</p>
</div>
</fieldset>
<br /><br /><br />

3
sapl/templates/sessao/blocos_resumo/expedientes.html

@ -1,3 +1,4 @@
{% load common_tags %}
{% if expedientes %}
<fieldset>
<legend>Expedientes</legend>
@ -9,7 +10,7 @@
<td>
<b>{{e.tipo}}: </b> <br /><br />
<div style="border:0.5px solid #BAB4B1; border-radius: 10px; background-color: rgba(225, 225, 225, .8);">
<p>{{e.conteudo|safe}}</p>
<p>{{e.conteudo|sanitize}}</p>
</div>
</td>
</tr>

3
sapl/templates/sessao/blocos_resumo/ocorrencias_da_sessao.html

@ -1,8 +1,9 @@
{% load common_tags %}
{% if object.ocorrenciasessao.conteudo %}
<fieldset>
<legend>Ocorrências da Sessão</legend>
<div style="border:0.5px solid #BAB4B1; border-radius: 10px; background-color: rgba(225, 225, 225, .8);">
<p>{{object.ocorrenciasessao.conteudo|safe}}</p>
<p>{{object.ocorrenciasessao.conteudo|sanitize}}</p>
</div>
</fieldset>
<br /><br /><br />

2
sapl/templates/sessao/consideracoes_finais.html

@ -15,7 +15,7 @@
<input type="submit" name="delete" value="Apagar" class="btn btn-danger" />
</form>
{% else %}
{{object.consideracoesfinais.conteudo|safe}}
{{object.consideracoesfinais.conteudo|sanitize}}
{% endif %}
{% endblock detail_content %}

2
sapl/templates/sessao/leitura/leitura_bloco.html

@ -72,7 +72,7 @@
{% endfor %}<br>
{% endif %}
{% endif %}
<strong>Ementa:</strong>&nbsp;{{ o.materia.ementa|safe }}<br>
<strong>Ementa:</strong>&nbsp;{{ o.materia.ementa }}<br>
<p></p>
</td>
</tr>

2
sapl/templates/sessao/ocorrencias_da_sessao.html

@ -15,7 +15,7 @@
<input type="submit" name="delete" value="Apagar" class="btn btn-danger" />
</form>
{% else %}
{{object.ocorrenciasessao.conteudo|safe}}
{{object.ocorrenciasessao.conteudo|sanitize}}
{% endif %}
{% endblock detail_content %}

6
sapl/templates/sessao/pauta_sessao_detail.html

@ -54,7 +54,7 @@
<b>{{e.tipo}}: </b> <br><br>
<div style="border:0.5px solid #BAB4B1; border-radius: 10px;
background-color: rgba(225, 225, 225, .8);">
<p>{{e.conteudo|safe}}</p>
<p>{{e.conteudo|sanitize}}</p>
</div>
</td>
</tr>
@ -82,7 +82,7 @@
{{m.ementa|dont_break_out}}
{% if m.observacao %}<br><br>Obs.: {{m.observacao}} {% endif %}
</td>
<td style="width:20%;">{{m.situacao|linebreaksbr|safe}}</td>
<td style="width:20%;">{{m.situacao|linebreaksbr}}</td>
</tr>
{% endfor %}
</table>
@ -108,7 +108,7 @@
{{m.ementa|dont_break_out}}
{% if m.observacao %}<br><br>Obs.: {{m.observacao}} {% endif %}
</td>
<td style="width:20%;">{{m.situacao|linebreaksbr|safe}}</td>
<td style="width:20%;">{{m.situacao|linebreaksbr}}</td>
</tr>
{% endfor %}
</table>

2
sapl/templates/sessao/votacao/nominal.html

@ -10,7 +10,7 @@
<div>
Matéria: {{materia.materia}}
<br />
Ementa: {{materia.ementa|safe}}
Ementa: {{materia.ementa}}
</div>
<br />
{% if total == 0 %}

6
sapl/templates/sessao/votacao/nominal_detail.html

@ -8,9 +8,9 @@
<fieldset>
<legend>Votação Nominal</legend>
<div>
Matéria: {{materia.materia|safe}}
Matéria: {{materia.materia}}
<br />
Ementa: {{materia.ementa|safe}}
Ementa: {{materia.ementa}}
</div>
<br />
@ -45,7 +45,7 @@
<div class="row">
<div class="col-md-12">
Observações
<textarea id="observacao" name="observacao" style="width:100%;" rows="7" class="form-control">{{votacao.observacao|safe}}</textarea>
<textarea id="observacao" name="observacao" style="width:100%;" rows="7" class="form-control">{{votacao.observacao}}</textarea>
</div>
</div>
</fieldset>

6
sapl/templates/sessao/votacao/nominal_edit.html

@ -8,9 +8,9 @@
<fieldset>
<legend>Votação Nominal</legend>
<div>
Matéria: {{materia.materia|safe}}
Matéria: {{materia.materia}}
<br />
Ementa: {{materia.ementa|safe}}
Ementa: {{materia.ementa}}
</div>
<br />
@ -73,7 +73,7 @@
<div class="row">
<div class="col-md-12">
Observações
<textarea id="observacao" name="observacao" style="width:100%;" rows="7" class="form-control">{{votacao.observacao|safe}}</textarea>
<textarea id="observacao" name="observacao" style="width:100%;" rows="7" class="form-control">{{votacao.observacao}}</textarea>
</div>
</div>

2
sapl/templates/sessao/votacao/nominal_transparencia.html

@ -49,7 +49,7 @@
<div class="row">
<div class="col-md-12">
Observações
<textarea id="observacao" name="observacao" style="width:100%;" rows="7" class="form-control">{{votacao.observacao|safe}}</textarea>
<textarea id="observacao" name="observacao" style="width:100%;" rows="7" class="form-control">{{votacao.observacao}}</textarea>
</div>
</div>

2
sapl/templates/sessao/votacao/simbolica_transparencia.html

@ -40,7 +40,7 @@
<div class="row">
<div class="col-md-12">
Observações
<textarea id="observacao" name="observacao" style="width:100%;" rows="7" class="form-control">{{votacao.observacao|safe}}</textarea>
<textarea id="observacao" name="observacao" style="width:100%;" rows="7" class="form-control">{{votacao.observacao}}</textarea>
</div>
</div>

4
sapl/templates/sessao/votacao/votacao.html

@ -26,9 +26,9 @@
{% endif %}
<div>
<b>Matéria:</b> {{materia.materia|safe}}
<b>Matéria:</b> {{materia.materia}}
<br />
<b>Ementa:</b> {{materia.ementa|safe}}
<b>Ementa:</b> {{materia.ementa}}
<br />
<br />
<b>Total presentes:</b> {{total_presentes}} (com presidente)

2
sapl/templates/sessao/votacao/votacao_bloco.html

@ -73,7 +73,7 @@
{% endfor %}</br>
{% endif %}
{% endif %}
<strong>Ementa:</strong>&nbsp;{{ o.materia.ementa|safe }}</br>
<strong>Ementa:</strong>&nbsp;{{ o.materia.ementa }}</br>
<p></p>
</td>
</tr>

6
sapl/templates/sessao/votacao/votacao_edit.html

@ -8,9 +8,9 @@
<fieldset class="form-group">
<legend>{{votacao_titulo}}</legend>
<div>
Matéria: {{materia.materia|safe}}
Matéria: {{materia.materia}}
<br />
Ementa: {{materia.ementa|safe}}
Ementa: {{materia.ementa}}
</div>
<br />
@ -36,7 +36,7 @@
<div class="row">
<div class="col-md-12">
Observações
<textarea id="observacao" name="observacao" cols="10" rows="10" class="form-control">{{votacao.observacao|safe}}</textarea>
<textarea id="observacao" name="observacao" cols="10" rows="10" class="form-control">{{votacao.observacao}}</textarea>
</div>
</div>

4
sapl/templates/sessao/votacao/votacao_nominal_bloco.html

@ -24,7 +24,7 @@
<div>
Matéria: {{o.materia}}
<br />
Ementa: {{o.materia.ementa|safe}}
Ementa: {{o.materia.ementa}}
</div>
<br />
{% endfor %}
@ -34,7 +34,7 @@
<div>
Matéria: {{e.materia}}
<br />
Ementa: {{e.materia.ementa|safe}}
Ementa: {{e.materia.ementa}}
</div>
<br />
{% endfor %}

8
sapl/templates/sessao/votacao/votacao_simbolica_bloco.html

@ -21,17 +21,17 @@
{% if ordens %}
{% for o in ordens %}
<input type="hidden" id="ordens" name="ordens" value="{{o.id}}">
<b>Matéria:</b> {{o.materia|safe}}
<b>Matéria:</b> {{o.materia}}
<br />
<b>Ementa:</b> {{o.materia.ementa|safe}}
<b>Ementa:</b> {{o.materia.ementa}}
<br /> <br />
{% endfor %}
{% else %}
{% for e in expedientes %}
<input type="hidden" id="expedientes" name="expedientes" value="{{e.id}}">
<b>Matéria:</b> {{e.materia|safe}}
<b>Matéria:</b> {{e.materia}}
<br />
<b>Ementa:</b> {{e.materia.ementa|safe}}
<b>Ementa:</b> {{e.materia.ementa}}
<br /> <br />
{% endfor %}
{% endif %}

Loading…
Cancel
Save