Browse Source

Merge branch '3.1.x' into tipo_votacao_multiplas_materias

pull/3781/head
cristian-longhi 1 month ago
committed by GitHub
parent
commit
5664cf5de0
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 11
      CHANGES.md
  2. 10
      docker/docker-compose.yaml
  3. 7
      sapl/base/views.py
  4. 27
      sapl/materia/views.py
  5. 24
      sapl/norma/views.py
  6. 14
      sapl/sessao/views.py
  7. 2
      sapl/settings.py
  8. 2
      sapl/templates/base.html
  9. 12
      sapl/utils.py
  10. 9
      scripts/test_ratelimiter.sh

11
CHANGES.md

@ -1,4 +1,15 @@
3.1.164-RC5 / 2025-09-22
========================
* Adiciona smoke test for rate limiter
* Hot-fix: rate limiter get ip
3.1.164-RC4 / 2025-09-22
========================
* Fix recibo proposição e adiciona rate limiter em matéria e norma
3.1.164-RC3 / 2025-09-16 3.1.164-RC3 / 2025-09-16
======================== ========================

10
docker/docker-compose.yaml

@ -33,11 +33,11 @@ services:
networks: networks:
- sapl-net - sapl-net
sapl: sapl:
# image: interlegis/sapl:3.1.164-RC3 image: interlegis/sapl:3.1.164-RC5
build: # build:
context: ../ # context: ../
dockerfile: ./docker/Dockerfile # dockerfile: ./docker/Dockerfile
container_name: sapl # container_name: sapl
labels: labels:
NAME: "sapl" NAME: "sapl"
restart: always restart: always

7
sapl/base/views.py

@ -51,7 +51,7 @@ from sapl.sessao.models import (Bancada, SessaoPlenaria)
from sapl.settings import EMAIL_SEND_USER from sapl.settings import EMAIL_SEND_USER
from sapl.utils import (gerar_hash_arquivo, intervalos_tem_intersecao, mail_service_configured, from sapl.utils import (gerar_hash_arquivo, intervalos_tem_intersecao, mail_service_configured,
SEPARADOR_HASH_PROPOSICAO, show_results_filter_set, google_recaptcha_configured, SEPARADOR_HASH_PROPOSICAO, show_results_filter_set, google_recaptcha_configured,
get_client_ip, sapn_is_enabled, is_weak_password) get_client_ip, sapn_is_enabled, is_weak_password, ratelimit_ip)
from .forms import (AlterarSenhaForm, CasaLegislativaForm, ConfiguracoesAppForm, EstatisticasAcessoNormasForm) from .forms import (AlterarSenhaForm, CasaLegislativaForm, ConfiguracoesAppForm, EstatisticasAcessoNormasForm)
from .models import AppConfig, CasaLegislativa from .models import AppConfig, CasaLegislativa
@ -67,10 +67,11 @@ class IndexView(TemplateView):
return TemplateView.get(self, request, *args, **kwargs) return TemplateView.get(self, request, *args, **kwargs)
@method_decorator(ratelimit(key=lambda group, request: get_client_ip(request), @method_decorator(ratelimit(key=ratelimit_ip,
rate='10/m', rate='10/m',
method=ratelimit.UNSAFE, method=ratelimit.UNSAFE,
block=True), name='dispatch') block=True),
name='dispatch')
class LoginSapl(views.LoginView): class LoginSapl(views.LoginView):
template_name = 'base/login.html' template_name = 'base/login.html'
authentication_form = LoginForm authentication_form = LoginForm

27
sapl/materia/views.py

@ -56,7 +56,7 @@ from sapl.utils import (autor_label, autor_modal, gerar_hash_arquivo, get_base_u
get_client_ip, get_mime_type_from_file_extension, lista_anexados, get_client_ip, get_mime_type_from_file_extension, lista_anexados,
mail_service_configured, montar_row_autor, SEPARADOR_HASH_PROPOSICAO, mail_service_configured, montar_row_autor, SEPARADOR_HASH_PROPOSICAO,
show_results_filter_set, get_tempfile_dir, show_results_filter_set, get_tempfile_dir,
google_recaptcha_configured, MultiFormatOutputMixin) google_recaptcha_configured, MultiFormatOutputMixin, ratelimit_ip)
from .forms import (AcessorioEmLoteFilterSet, AcompanhamentoMateriaForm, from .forms import (AcessorioEmLoteFilterSet, AcompanhamentoMateriaForm,
AnexadaEmLoteFilterSet, AdicionarVariasAutoriasFilterSet, AnexadaEmLoteFilterSet, AdicionarVariasAutoriasFilterSet,
@ -1461,7 +1461,10 @@ class TramitacaoCrud(MasterDetailCrud):
return initial return initial
@method_decorator(ratelimit(key='ip', rate='10/m', block=True), name='dispatch') @method_decorator(ratelimit(key=ratelimit_ip,
rate='10/m',
block=True),
name='dispatch')
class ListView(MasterDetailCrud.ListView): class ListView(MasterDetailCrud.ListView):
def get_queryset(self): def get_queryset(self):
@ -1534,7 +1537,10 @@ class TramitacaoCrud(MasterDetailCrud):
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@method_decorator(ratelimit(key='ip', rate='10/m', block=True), name='dispatch') @method_decorator(ratelimit(key=ratelimit_ip,
rate='10/m',
block=True),
name='dispatch')
class DetailView(MasterDetailCrud.DetailView): class DetailView(MasterDetailCrud.DetailView):
template_name = "materia/tramitacao_detail.html" template_name = "materia/tramitacao_detail.html"
@ -1912,7 +1918,10 @@ class MateriaLegislativaCrud(Crud):
def get_success_url(self): def get_success_url(self):
return self.search_url return self.search_url
@method_decorator(ratelimit(key='ip', rate='10/m', block=True), name='dispatch') @method_decorator(ratelimit(key=ratelimit_ip,
rate='10/m',
block=True),
name='dispatch')
class DetailView(Crud.DetailView): class DetailView(Crud.DetailView):
layout_key = 'MateriaLegislativaDetail' layout_key = 'MateriaLegislativaDetail'
@ -1925,7 +1934,10 @@ class MateriaLegislativaCrud(Crud):
pk=self.kwargs['pk']) pk=self.kwargs['pk'])
return context return context
@method_decorator(ratelimit(key='ip', rate='10/m', block=True), name='dispatch') @method_decorator(ratelimit(key=ratelimit_ip,
rate='10/m',
block=True),
name='dispatch')
class ListView(Crud.ListView, RedirectView): class ListView(Crud.ListView, RedirectView):
def get_redirect_url(self, *args, **kwargs): def get_redirect_url(self, *args, **kwargs):
@ -2046,7 +2058,10 @@ class AcompanhamentoExcluirView(TemplateView):
return HttpResponseRedirect(self.get_success_url()) return HttpResponseRedirect(self.get_success_url())
@method_decorator(ratelimit(key='ip', rate='10/m', block=True), name='dispatch') @method_decorator(ratelimit(key=ratelimit_ip,
rate='10/m',
block=True),
name='dispatch')
class MateriaLegislativaPesquisaView(MultiFormatOutputMixin, FilterView): class MateriaLegislativaPesquisaView(MultiFormatOutputMixin, FilterView):
model = MateriaLegislativa model = MateriaLegislativa
filterset_class = MateriaLegislativaFilterSet filterset_class = MateriaLegislativaFilterSet

24
sapl/norma/views.py

@ -30,8 +30,8 @@ from sapl.compilacao.views import IntegracaoTaView
from sapl.crud.base import (RP_DETAIL, RP_LIST, Crud, CrudAux, from sapl.crud.base import (RP_DETAIL, RP_LIST, Crud, CrudAux,
MasterDetailCrud, make_pagination) MasterDetailCrud, make_pagination)
from sapl.materia.models import Orgao from sapl.materia.models import Orgao
from sapl.utils import show_results_filter_set, get_client_ip,\ from sapl.utils import show_results_filter_set, get_client_ip, \
sapn_is_enabled, MultiFormatOutputMixin sapn_is_enabled, MultiFormatOutputMixin, ratelimit_ip
from .forms import (AnexoNormaJuridicaForm, NormaFilterSet, NormaJuridicaForm, from .forms import (AnexoNormaJuridicaForm, NormaFilterSet, NormaJuridicaForm,
NormaPesquisaSimplesForm, NormaRelacionadaForm, NormaPesquisaSimplesForm, NormaRelacionadaForm,
@ -150,7 +150,10 @@ class NormaRelacionadaCrud(MasterDetailCrud):
layout_key = 'NormaRelacionadaDetail' layout_key = 'NormaRelacionadaDetail'
@method_decorator(ratelimit(key='ip', rate='10/m', block=True), name='dispatch') @method_decorator(ratelimit(key=ratelimit_ip,
rate='10/m',
block=True),
name='dispatch')
class NormaPesquisaView(MultiFormatOutputMixin, FilterView): class NormaPesquisaView(MultiFormatOutputMixin, FilterView):
model = NormaJuridica model = NormaJuridica
filterset_class = NormaFilterSet filterset_class = NormaFilterSet
@ -236,7 +239,10 @@ class AnexoNormaJuridicaCrud(MasterDetailCrud):
initial['ano'] = self.object.ano initial['ano'] = self.object.ano
return initial return initial
@method_decorator(ratelimit(key='ip', rate='10/m', block=True), name='dispatch') @method_decorator(ratelimit(key=ratelimit_ip,
rate='10/m',
block=True),
name='dispatch')
class DetailView(MasterDetailCrud.DetailView): class DetailView(MasterDetailCrud.DetailView):
form_class = AnexoNormaJuridicaForm form_class = AnexoNormaJuridicaForm
layout_key = 'AnexoNormaJuridica' layout_key = 'AnexoNormaJuridica'
@ -285,7 +291,10 @@ class NormaCrud(Crud):
namespace = self.model._meta.app_config.name namespace = self.model._meta.app_config.name
return reverse('%s:%s' % (namespace, 'norma_pesquisa')) return reverse('%s:%s' % (namespace, 'norma_pesquisa'))
@method_decorator(ratelimit(key='ip', rate='10/m', block=True), name='dispatch') @method_decorator(ratelimit(key=ratelimit_ip,
rate='10/m',
block=True),
name='dispatch')
class DetailView(Crud.DetailView): class DetailView(Crud.DetailView):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
estatisticas_acesso_normas = AppConfig.objects.first().estatisticas_acesso_normas estatisticas_acesso_normas = AppConfig.objects.first().estatisticas_acesso_normas
@ -343,7 +352,10 @@ class NormaCrud(Crud):
layout_key = 'NormaJuridicaCreate' layout_key = 'NormaJuridicaCreate'
@method_decorator(ratelimit(key='ip', rate='10/m', block=True), name='dispatch') @method_decorator(ratelimit(key=ratelimit_ip,
rate='10/m',
block=True),
name='dispatch')
class ListView(Crud.ListView): class ListView(Crud.ListView):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):

14
sapl/sessao/views.py

@ -48,8 +48,8 @@ from sapl.sessao.forms import ExpedienteMateriaForm, OrdemDiaForm, OrdemExpedien
CorrespondenciaForm, CorrespondenciaEmLoteFilterSet CorrespondenciaForm, CorrespondenciaEmLoteFilterSet
from sapl.sessao.models import Correspondencia from sapl.sessao.models import Correspondencia
from sapl.settings import TIME_ZONE from sapl.settings import TIME_ZONE
from sapl.utils import show_results_filter_set, remover_acentos, get_client_ip,\ from sapl.utils import show_results_filter_set, remover_acentos, get_client_ip, \
MultiFormatOutputMixin, PautaMultiFormatOutputMixin MultiFormatOutputMixin, PautaMultiFormatOutputMixin, ratelimit_ip
from .forms import (AdicionarVariasMateriasFilterSet, AdicionarVariasMateriasForm, BancadaForm, from .forms import (AdicionarVariasMateriasFilterSet, AdicionarVariasMateriasForm, BancadaForm,
ExpedienteForm, JustificativaAusenciaForm, OcorrenciaSessaoForm, ListMateriaForm, ExpedienteForm, JustificativaAusenciaForm, OcorrenciaSessaoForm, ListMateriaForm,
@ -3797,7 +3797,10 @@ class SessaoListView(ListView):
return context return context
@method_decorator(ratelimit(key='ip', rate='10/m', block=True), name='dispatch') @method_decorator(ratelimit(key=ratelimit_ip,
rate='10/m',
block=True),
name='dispatch')
class PautaSessaoView(TemplateView): class PautaSessaoView(TemplateView):
model = SessaoPlenaria model = SessaoPlenaria
template_name = "sessao/pauta_inexistente.html" template_name = "sessao/pauta_inexistente.html"
@ -3813,7 +3816,10 @@ class PautaSessaoView(TemplateView):
reverse('sapl.sessao:pauta_sessao_detail', kwargs={'pk': sessao.pk})) reverse('sapl.sessao:pauta_sessao_detail', kwargs={'pk': sessao.pk}))
@method_decorator(ratelimit(key='ip', rate='10/m', block=True), name='dispatch') @method_decorator(ratelimit(key=ratelimit_ip,
rate='10/m',
block=True),
name='dispatch')
class PautaSessaoDetailView(PautaMultiFormatOutputMixin, DetailView): class PautaSessaoDetailView(PautaMultiFormatOutputMixin, DetailView):
template_name = "sessao/pauta_sessao_detail.html" template_name = "sessao/pauta_sessao_detail.html"
model = SessaoPlenaria model = SessaoPlenaria

2
sapl/settings.py

@ -43,7 +43,7 @@ ALLOWED_HOSTS = ['*']
LOGIN_REDIRECT_URL = '/' LOGIN_REDIRECT_URL = '/'
LOGIN_URL = '/login/?next=' LOGIN_URL = '/login/?next='
SAPL_VERSION = '3.1.164-RC3' SAPL_VERSION = '3.1.164-RC5'
if DEBUG: if DEBUG:
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

2
sapl/templates/base.html

@ -200,7 +200,7 @@
<small> <small>
Desenvolvido pelo <a href="http://www.interlegis.leg.br/">Interlegis</a> em software livre e aberto. Desenvolvido pelo <a href="http://www.interlegis.leg.br/">Interlegis</a> em software livre e aberto.
</small> </small>
<span>Release: 3.1.164-RC3</span> <span>Release: 3.1.164-RC5</span>
</p> </p>
</div> </div>

12
sapl/utils.py

@ -402,12 +402,20 @@ def xstr(s):
def get_client_ip(request): def get_client_ip(request):
from ratelimit.core import ip_mask
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for: if x_forwarded_for:
ip = x_forwarded_for.split(',')[0] ip = x_forwarded_for.split(',')[0]
else: else:
ip = request.META.get('REMOTE_ADDR') ip = request.META.get('HTTP_X_REAL_IP') or request.META.get('REMOTE_ADDR') or '0.0.0.0'
return ip return ip_mask(ip)
def ratelimit_ip(group, request):
"""
Ignore group param in django-ratelimit==3.0.1
"""
return get_client_ip(request)
def get_base_url(request): def get_base_url(request):

9
scripts/test_ratelimiter.sh

@ -0,0 +1,9 @@
#!/bin/bash
#URL=http://localhost:8000/materia/4379
URL=http://localhost:8000/norma/pesquisar
#URL=http://localhost/norma/pesquisar
for i in $(seq 1 6); do
curl -sS -o /dev/null -w "req=$i http=%{http_code} time=%{time_total}\n" "$URL"
done
Loading…
Cancel
Save