From 55f9312ae1eb2056216761700353c55381c8a694 Mon Sep 17 00:00:00 2001 From: Edward Oliveira Date: Mon, 29 Sep 2025 14:16:38 -0300 Subject: [PATCH] Refatora rate limiter --- sapl/audiencia/views.py | 9 ++- sapl/base/views.py | 8 ++- sapl/comissoes/views.py | 11 ++- sapl/crud/base.py | 96 +++++++++++++++----------- sapl/materia/views.py | 40 ++++++----- sapl/middleware.py | 3 + sapl/norma/views.py | 20 ++---- sapl/parlamentares/views.py | 19 ++++- sapl/protocoloadm/views.py | 27 +++++++- sapl/relatorios/views.py | 67 +++++++++++++++++- sapl/sessao/views.py | 15 +++- sapl/settings.py | 2 + sapl/static/.well-known/traffic-advice | 5 ++ sapl/urls.py | 15 ++++ scripts/test_ratelimiter.sh | 9 ++- 15 files changed, 257 insertions(+), 89 deletions(-) create mode 100644 sapl/static/.well-known/traffic-advice diff --git a/sapl/audiencia/views.py b/sapl/audiencia/views.py index 443fc81e8..d5ea7f710 100755 --- a/sapl/audiencia/views.py +++ b/sapl/audiencia/views.py @@ -9,6 +9,12 @@ from sapl.crud.base import RP_DETAIL, RP_LIST, Crud, MasterDetailCrud from .forms import AudienciaForm, AnexoAudienciaPublicaForm from .models import AudienciaPublica, AnexoAudienciaPublica +from ratelimit.decorators import ratelimit +from django.utils.decorators import method_decorator + +from ..settings import RATE_LIMITER_RATE +from ..utils import ratelimit_ip + def index(request): return HttpResponse("Audiência Pública") @@ -105,6 +111,3 @@ class AnexoAudienciaPublicaCrud(MasterDetailCrud): qs = super(MasterDetailCrud.ListView, self).get_queryset() kwargs = {self.crud.parent_field: self.kwargs['pk']} return qs.filter(**kwargs).order_by('-data', '-id') - - class DetailView(AudienciaPublicaMixin, MasterDetailCrud.DetailView): - pass diff --git a/sapl/base/views.py b/sapl/base/views.py index 68493dc04..2b53aa57c 100644 --- a/sapl/base/views.py +++ b/sapl/base/views.py @@ -48,7 +48,7 @@ from sapl.parlamentares.models import ( from sapl.protocoloadm.models import (Anexado, Protocolo) from sapl.relatorios.views import (relatorio_estatisticas_acesso_normas) from sapl.sessao.models import (Bancada, SessaoPlenaria) -from sapl.settings import EMAIL_SEND_USER +from sapl.settings import EMAIL_SEND_USER, RATE_LIMITER_RATE from sapl.utils import (gerar_hash_arquivo, intervalos_tem_intersecao, mail_service_configured, SEPARADOR_HASH_PROPOSICAO, show_results_filter_set, google_recaptcha_configured, get_client_ip, sapn_is_enabled, is_weak_password, ratelimit_ip) @@ -68,7 +68,7 @@ class IndexView(TemplateView): @method_decorator(ratelimit(key=ratelimit_ip, - rate='10/m', + rate=RATE_LIMITER_RATE, method=ratelimit.UNSAFE, block=True), name='dispatch') @@ -1400,6 +1400,10 @@ class SaplSearchView(SearchView): return context +@method_decorator(ratelimit(key=ratelimit_ip, + rate=RATE_LIMITER_RATE, + block=True), + name='dispatch') class PesquisarAuditLogView(PermissionRequiredMixin, FilterView): model = AuditLog filterset_class = AuditLogFilterSet diff --git a/sapl/comissoes/views.py b/sapl/comissoes/views.py index 5d3e450c2..6017ae2b3 100644 --- a/sapl/comissoes/views.py +++ b/sapl/comissoes/views.py @@ -28,11 +28,16 @@ from sapl.crud.base import (Crud, CrudAux, MasterDetailCrud, RP_LIST) from sapl.materia.models import (MateriaEmTramitacao, MateriaLegislativa, PautaReuniao, Tramitacao) -from sapl.utils import show_results_filter_set +from sapl.utils import show_results_filter_set, ratelimit_ip from .models import (CargoComissao, Comissao, Composicao, DocumentoAcessorio, Participacao, Periodo, Reuniao, TipoComissao) +from ratelimit.decorators import ratelimit +from django.utils.decorators import method_decorator + +from ..settings import RATE_LIMITER_RATE + def pegar_url_composicao(pk): participacao = Participacao.objects.get(id=pk) @@ -333,6 +338,10 @@ class RemovePautaView(PermissionRequiredMixin, CreateView): return HttpResponseRedirect(success_url) +@method_decorator(ratelimit(key=ratelimit_ip, + rate=RATE_LIMITER_RATE, + block=True), + name='dispatch') class AdicionaPautaView(PermissionRequiredMixin, FilterView): filterset_class = PautaReuniaoFilterSet template_name = 'comissoes/pauta.html' diff --git a/sapl/crud/base.py b/sapl/crud/base.py index ceaef7580..fd091ec52 100644 --- a/sapl/crud/base.py +++ b/sapl/crud/base.py @@ -26,7 +26,11 @@ from sapl.crispy_layout_mixin import CrispyLayoutFormMixin, get_field_display from sapl.crispy_layout_mixin import SaplFormHelper from sapl.rules import (RP_ADD, RP_CHANGE, RP_DELETE, RP_DETAIL, RP_LIST) -from sapl.utils import normalize +from sapl.settings import RATE_LIMITER_RATE +from sapl.utils import normalize, ratelimit_ip + +from ratelimit.decorators import ratelimit +from django.utils.decorators import method_decorator logger = logging.getLogger(settings.BASE_DIR.name) @@ -101,7 +105,6 @@ variáveis do crud: class SearchMixin(models.Model): - search = models.TextField(blank=True, default='') logger = logging.getLogger(__name__) @@ -238,7 +241,7 @@ class PermissionRequiredContainerCrudMixin(PermissionRequiredMixin): @property def container_field_set(self): - if hasattr(self, 'crud') and\ + if hasattr(self, 'crud') and \ not hasattr(self.crud, 'container_field_set'): self.crud.container_field_set = '' if hasattr(self, 'crud'): @@ -267,12 +270,11 @@ class CrudBaseMixin(CrispyLayoutFormMixin): obj.public = [] if hasattr(self, 'permission_required') and self.permission_required: - self.permission_required = tuple( ( self.permission(pr) for pr in ( set(self.permission_required) - set(obj.public) - ) + ) ) ) @@ -337,9 +339,9 @@ class CrudBaseMixin(CrispyLayoutFormMixin): if not obj.DetailView.permission_required: return self.resolve_url(ACTION_DETAIL, args=(self.object.id,)) else: - return self.resolve_url(ACTION_DETAIL, args=(self.object.id,))\ + return self.resolve_url(ACTION_DETAIL, args=(self.object.id,)) \ if self.request.user.has_perm( - self.permission(RP_DETAIL)) else '' + self.permission(RP_DETAIL)) else '' @property def update_url(self): @@ -347,9 +349,9 @@ class CrudBaseMixin(CrispyLayoutFormMixin): if not obj.UpdateView.permission_required: return self.resolve_url(ACTION_UPDATE, args=(self.object.id,)) else: - return self.resolve_url(ACTION_UPDATE, args=(self.object.id,))\ + return self.resolve_url(ACTION_UPDATE, args=(self.object.id,)) \ if self.request.user.has_perm( - self.permission(RP_CHANGE)) else '' + self.permission(RP_CHANGE)) else '' @property def delete_url(self): @@ -357,9 +359,9 @@ class CrudBaseMixin(CrispyLayoutFormMixin): if not obj.DeleteView.permission_required: return self.resolve_url(ACTION_DELETE, args=(self.object.id,)) else: - return self.resolve_url(ACTION_DELETE, args=(self.object.id,))\ + return self.resolve_url(ACTION_DELETE, args=(self.object.id,)) \ if self.request.user.has_perm( - self.permission(RP_DELETE)) else '' + self.permission(RP_DELETE)) else '' @property def openapi_url(self): @@ -388,6 +390,10 @@ class CrudBaseMixin(CrispyLayoutFormMixin): return self.model._meta.verbose_name_plural +@method_decorator(ratelimit(key=ratelimit_ip, + rate=RATE_LIMITER_RATE, + block=True), + name='dispatch') class CrudListView(PermissionRequiredContainerCrudMixin, ListView): permission_required = (RP_LIST,) logger = logging.getLogger(__name__) @@ -496,7 +502,7 @@ class CrudListView(PermissionRequiredContainerCrudMixin, ListView): if m: ss = get_field_display(m, n[-1])[1] ss = ( - ('
' if '