From 3faba84bc8d51782fbbade84ad3f4557dfef57d1 Mon Sep 17 00:00:00 2001 From: Edward Oliveira Date: Wed, 17 Sep 2025 20:06:57 -0300 Subject: [PATCH 1/5] =?UTF-8?q?Fix=20recibo=20proposi=C3=A7=C3=A3o=20e=20a?= =?UTF-8?q?diciona=20rate=20limiter=20em=20mat=C3=A9ria=20e=20norma?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .dockerignore | 7 +++++++ docker/config/nginx/sapl.conf | 2 +- docker/startup_scripts/gunicorn.conf.py | 3 ++- docker/startup_scripts/start.sh | 5 +---- requirements/requirements.txt | 3 +-- sapl/base/views.py | 2 +- sapl/materia/views.py | 9 +++++++- sapl/norma/views.py | 7 +++++++ sapl/sessao/views.py | 5 +++++ sapl/utils.py | 28 ++++++++++++++----------- scripts/gunicorn_start.sh | 4 ++-- 11 files changed, 51 insertions(+), 24 deletions(-) diff --git a/.dockerignore b/.dockerignore index 318700ad6..4ff7fdc82 100644 --- a/.dockerignore +++ b/.dockerignore @@ -12,3 +12,10 @@ bower .travis.yml .env .idea +.DS_Store +.coveragerc +*.swp +.coveragerc +.drone.yml +.github +release.sh diff --git a/docker/config/nginx/sapl.conf b/docker/config/nginx/sapl.conf index 18641045e..a55731d02 100644 --- a/docker/config/nginx/sapl.conf +++ b/docker/config/nginx/sapl.conf @@ -7,7 +7,7 @@ upstream sapl_server { server { listen 80; - server_name sapl.test; + server_name sapl.prod; client_max_body_size 4G; diff --git a/docker/startup_scripts/gunicorn.conf.py b/docker/startup_scripts/gunicorn.conf.py index 95d2f0256..6bdcacb02 100644 --- a/docker/startup_scripts/gunicorn.conf.py +++ b/docker/startup_scripts/gunicorn.conf.py @@ -52,7 +52,8 @@ graceful_timeout = 30 keepalive = 10 backlog = 2048 max_requests = MAX_REQUESTS -max_requests_jitter = 100 +max_requests_jitter = 200 +worker_max_memory_per_child = 300 * 1024 * 1024 # 300 MB cap # Environment (same as exporting before running) raw_env = [ diff --git a/docker/startup_scripts/start.sh b/docker/startup_scripts/start.sh index 316d73f8a..bd98bdfc0 100755 --- a/docker/startup_scripts/start.sh +++ b/docker/startup_scripts/start.sh @@ -4,19 +4,16 @@ IFS=$'\n\t' APP_DIR="/var/interlegis/sapl" DATA_DIR="/var/interlegis/sapl/data" -MEDIA_DIR="/var/interlegis/sapl/media" RUN_DIR="/var/interlegis/sapl/run" ENV_FILE="$APP_DIR/.env" SECRET_FILE="$DATA_DIR/secret.key" chown -R root:nginx "$RUN_DIR" || true -chown -R root:nginx "$MEDIA_DIR" || true chmod -R g+rwX "$RUN_DIR" || true -chmod -R g+rwX "$MEDIA_DIR" || true # setgid bit on our writable trees (not data/) -find "$RUN_DIR" "$MEDIA_DIR" -type d -exec chmod g+s {} + 2>/dev/null || true +find "$RUN_DIR" -type d -exec chmod g+s {} + 2>/dev/null || true log() { printf '[%s] %s\n' "$(date -Is)" "$*"; } err() { printf '[%s] ERROR: %s\n' "$(date -Is)" "$*" >&2; } diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 56c2459b4..aea9f52db 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -22,8 +22,7 @@ pytz==2019.3 python-magic==0.4.15 unipath==1.1 Pillow==10.3.0 -rlPyCairo==0.3.0 -reportlab==4.2.0 +reportlab==3.6.13 WeasyPrint==66 trml2pdf==0.6 gunicorn==23.0.0 diff --git a/sapl/base/views.py b/sapl/base/views.py index ce3a0a717..7c935d421 100644 --- a/sapl/base/views.py +++ b/sapl/base/views.py @@ -68,7 +68,7 @@ class IndexView(TemplateView): @method_decorator(ratelimit(key=lambda group, request: get_client_ip(request), - rate='20/m', + rate='10/m', method=ratelimit.UNSAFE, block=True), name='dispatch') class LoginSapl(views.LoginView): diff --git a/sapl/materia/views.py b/sapl/materia/views.py index f64d8134b..ce158228b 100644 --- a/sapl/materia/views.py +++ b/sapl/materia/views.py @@ -24,7 +24,6 @@ from django.shortcuts import render from django.template import loader from django.urls import reverse from django.utils import formats, timezone -from django.utils.encoding import force_text from django.utils.translation import ugettext_lazy as _ from django.views.generic import CreateView, ListView, TemplateView, UpdateView from django.views.generic.base import RedirectView @@ -32,6 +31,9 @@ from django.views.generic.edit import FormView from django_filters.views import FilterView import weasyprint +from ratelimit.decorators import ratelimit +from django.utils.decorators import method_decorator + import sapl from sapl.base.email_utils import do_envia_email_confirmacao from sapl.base.models import Autor, CasaLegislativa, AppConfig as BaseAppConfig @@ -1459,6 +1461,7 @@ class TramitacaoCrud(MasterDetailCrud): return initial + @method_decorator(ratelimit(key='ip', rate='10/m', block=True), name='dispatch') class ListView(MasterDetailCrud.ListView): def get_queryset(self): @@ -1531,6 +1534,7 @@ class TramitacaoCrud(MasterDetailCrud): return HttpResponseRedirect(url) + @method_decorator(ratelimit(key='ip', rate='10/m', block=True), name='dispatch') class DetailView(MasterDetailCrud.DetailView): template_name = "materia/tramitacao_detail.html" @@ -1908,6 +1912,7 @@ class MateriaLegislativaCrud(Crud): def get_success_url(self): return self.search_url + @method_decorator(ratelimit(key='ip', rate='10/m', block=True), name='dispatch') class DetailView(Crud.DetailView): layout_key = 'MateriaLegislativaDetail' @@ -1920,6 +1925,7 @@ class MateriaLegislativaCrud(Crud): pk=self.kwargs['pk']) return context + @method_decorator(ratelimit(key='ip', rate='10/m', block=True), name='dispatch') class ListView(Crud.ListView, RedirectView): def get_redirect_url(self, *args, **kwargs): @@ -2040,6 +2046,7 @@ class AcompanhamentoExcluirView(TemplateView): return HttpResponseRedirect(self.get_success_url()) +@method_decorator(ratelimit(key='ip', rate='10/m', block=True), name='dispatch') class MateriaLegislativaPesquisaView(MultiFormatOutputMixin, FilterView): model = MateriaLegislativa filterset_class = MateriaLegislativaFilterSet diff --git a/sapl/norma/views.py b/sapl/norma/views.py index 27758a1ca..488e73c90 100644 --- a/sapl/norma/views.py +++ b/sapl/norma/views.py @@ -19,6 +19,9 @@ from django.views.generic.edit import FormView from django_filters.views import FilterView import weasyprint +from ratelimit.decorators import ratelimit +from django.utils.decorators import method_decorator + from sapl import settings import sapl from sapl.base.models import AppConfig @@ -147,6 +150,7 @@ class NormaRelacionadaCrud(MasterDetailCrud): layout_key = 'NormaRelacionadaDetail' +@method_decorator(ratelimit(key='ip', rate='10/m', block=True), name='dispatch') class NormaPesquisaView(MultiFormatOutputMixin, FilterView): model = NormaJuridica filterset_class = NormaFilterSet @@ -232,6 +236,7 @@ class AnexoNormaJuridicaCrud(MasterDetailCrud): initial['ano'] = self.object.ano return initial + @method_decorator(ratelimit(key='ip', rate='10/m', block=True), name='dispatch') class DetailView(MasterDetailCrud.DetailView): form_class = AnexoNormaJuridicaForm layout_key = 'AnexoNormaJuridica' @@ -280,6 +285,7 @@ class NormaCrud(Crud): namespace = self.model._meta.app_config.name return reverse('%s:%s' % (namespace, 'norma_pesquisa')) + @method_decorator(ratelimit(key='ip', rate='10/m', block=True), name='dispatch') class DetailView(Crud.DetailView): def get(self, request, *args, **kwargs): estatisticas_acesso_normas = AppConfig.objects.first().estatisticas_acesso_normas @@ -337,6 +343,7 @@ class NormaCrud(Crud): layout_key = 'NormaJuridicaCreate' + @method_decorator(ratelimit(key='ip', rate='10/m', block=True), name='dispatch') class ListView(Crud.ListView): def get(self, request, *args, **kwargs): diff --git a/sapl/sessao/views.py b/sapl/sessao/views.py index 4835a17b6..7df367379 100755 --- a/sapl/sessao/views.py +++ b/sapl/sessao/views.py @@ -28,6 +28,9 @@ from django.views.generic.edit import FormMixin from django_filters.views import FilterView import pytz +from ratelimit.decorators import ratelimit +from django.utils.decorators import method_decorator + from sapl.base.models import AppConfig as AppsAppConfig from sapl.crud.base import (RP_DETAIL, RP_LIST, Crud, CrudAux, MasterDetailCrud, @@ -3794,6 +3797,7 @@ class SessaoListView(ListView): return context +@method_decorator(ratelimit(key='ip', rate='10/m', block=True), name='dispatch') class PautaSessaoView(TemplateView): model = SessaoPlenaria template_name = "sessao/pauta_inexistente.html" @@ -3809,6 +3813,7 @@ class PautaSessaoView(TemplateView): reverse('sapl.sessao:pauta_sessao_detail', kwargs={'pk': sessao.pk})) +@method_decorator(ratelimit(key='ip', rate='10/m', block=True), name='dispatch') class PautaSessaoDetailView(PautaMultiFormatOutputMixin, DetailView): template_name = "sessao/pauta_sessao_detail.html" model = SessaoPlenaria diff --git a/sapl/utils.py b/sapl/utils.py index 9d6399c5e..6554c5aea 100644 --- a/sapl/utils.py +++ b/sapl/utils.py @@ -419,21 +419,25 @@ def get_base_url(request): return "{0}://{1}".format(protocol, current_domain) -def create_barcode(value, width=170, height=50): - ''' - creates a base64 encoded barcode PNG image - ''' +def create_barcode(value, width=170, height=50, dpi=72): + """ + creates a base64 encoded barcode PNG image + """ from base64 import b64encode from reportlab.graphics.barcode import createBarcodeDrawing + value_bytes = bytes(value, "ascii") - barcode = createBarcodeDrawing('Code128', - value=value_bytes, - barWidth=width, - height=height, - fontSize=2, - humanReadable=True) - data = b64encode(barcode.asString('png')) - return data.decode('utf-8') + barcode = createBarcodeDrawing( + 'Code128', + value=value_bytes, + barWidth=width, + height=height, + fontSize=2, + humanReadable=True + ) + # Lower DPI prevents Cairo surface from blowing up + png_bytes = barcode.asString("png", dpi=dpi) + return b64encode(png_bytes).decode("utf-8") YES_NO_CHOICES = [(True, _('Sim')), (False, _('Não'))] diff --git a/scripts/gunicorn_start.sh b/scripts/gunicorn_start.sh index d01503235..bd2932985 100755 --- a/scripts/gunicorn_start.sh +++ b/scripts/gunicorn_start.sh @@ -15,8 +15,8 @@ then fi NAME="SAPL" # Name of the application (*) -DJANGODIR=$SAPL_DIR/ # Django project directory (*) -SOCKFILE=$SAPL_DIR/run/gunicorn.sock # we will communicate using this unix socket (*) +DJANGODIR="$SAPL_DIR/" # Django project directory (*) +SOCKFILE="$SAPL_DIR/run/gunicorn.sock" # we will communicate using this unix socket (*) USER=`whoami` # the user to run as (*) GROUP=`whoami` # the group to run as (*) NUM_WORKERS=3 # how many worker processes should Gunicorn spawn (*) From b63a0cec38ddef03d5885c254dd2f605b63b66c2 Mon Sep 17 00:00:00 2001 From: Edward Oliveira Date: Mon, 22 Sep 2025 14:06:04 -0300 Subject: [PATCH 2/5] Release: 3.1.164-RC4 --- CHANGES.md | 6 ++++++ docker/docker-compose.yaml | 2 +- sapl/settings.py | 2 +- sapl/templates/base.html | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 6902a4f67..f977fe2b5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,10 @@ +3.1.164-RC4 / 2025-09-22 +======================== + + * Fix recibo proposição e adiciona rate limiter em matéria e norma + * Release: 3.1.164-RC3 + 3.1.164-RC3 / 2025-09-16 ======================== diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index 20c75374a..cd8b7a2a1 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -33,7 +33,7 @@ services: networks: - sapl-net sapl: - image: interlegis/sapl:3.1.164-RC3 + image: interlegis/sapl:3.1.164-RC4 # build: # context: ../ # dockerfile: ./docker/Dockerfile diff --git a/sapl/settings.py b/sapl/settings.py index 5a0fdef73..c31b6b3b9 100644 --- a/sapl/settings.py +++ b/sapl/settings.py @@ -43,7 +43,7 @@ ALLOWED_HOSTS = ['*'] LOGIN_REDIRECT_URL = '/' LOGIN_URL = '/login/?next=' -SAPL_VERSION = '3.1.164-RC3' +SAPL_VERSION = '3.1.164-RC4' if DEBUG: EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' diff --git a/sapl/templates/base.html b/sapl/templates/base.html index 5679f718a..a53524bd4 100644 --- a/sapl/templates/base.html +++ b/sapl/templates/base.html @@ -200,7 +200,7 @@ Desenvolvido pelo Interlegis em software livre e aberto. - Release: 3.1.164-RC3 + Release: 3.1.164-RC4

From 12cb253e58f987821d93348fad31c077c46ee74d Mon Sep 17 00:00:00 2001 From: Edward Oliveira Date: Mon, 22 Sep 2025 17:04:24 -0300 Subject: [PATCH 3/5] Hot-fix: rate limiter get_ip --- sapl/base/views.py | 7 ++++--- sapl/materia/views.py | 27 +++++++++++++++++++++------ sapl/norma/views.py | 24 ++++++++++++++++++------ sapl/sessao/views.py | 14 ++++++++++---- sapl/utils.py | 12 ++++++++++-- 5 files changed, 63 insertions(+), 21 deletions(-) diff --git a/sapl/base/views.py b/sapl/base/views.py index 7c935d421..68493dc04 100644 --- a/sapl/base/views.py +++ b/sapl/base/views.py @@ -51,7 +51,7 @@ from sapl.sessao.models import (Bancada, SessaoPlenaria) from sapl.settings import EMAIL_SEND_USER 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) + get_client_ip, sapn_is_enabled, is_weak_password, ratelimit_ip) from .forms import (AlterarSenhaForm, CasaLegislativaForm, ConfiguracoesAppForm, EstatisticasAcessoNormasForm) from .models import AppConfig, CasaLegislativa @@ -67,10 +67,11 @@ class IndexView(TemplateView): 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', method=ratelimit.UNSAFE, - block=True), name='dispatch') + block=True), + name='dispatch') class LoginSapl(views.LoginView): template_name = 'base/login.html' authentication_form = LoginForm diff --git a/sapl/materia/views.py b/sapl/materia/views.py index ce158228b..cfd9306c5 100644 --- a/sapl/materia/views.py +++ b/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, mail_service_configured, montar_row_autor, SEPARADOR_HASH_PROPOSICAO, show_results_filter_set, get_tempfile_dir, - google_recaptcha_configured, MultiFormatOutputMixin) + google_recaptcha_configured, MultiFormatOutputMixin, ratelimit_ip) from .forms import (AcessorioEmLoteFilterSet, AcompanhamentoMateriaForm, AnexadaEmLoteFilterSet, AdicionarVariasAutoriasFilterSet, @@ -1461,7 +1461,10 @@ class TramitacaoCrud(MasterDetailCrud): 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): def get_queryset(self): @@ -1534,7 +1537,10 @@ class TramitacaoCrud(MasterDetailCrud): 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): template_name = "materia/tramitacao_detail.html" @@ -1912,7 +1918,10 @@ class MateriaLegislativaCrud(Crud): def get_success_url(self): 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): layout_key = 'MateriaLegislativaDetail' @@ -1925,7 +1934,10 @@ class MateriaLegislativaCrud(Crud): pk=self.kwargs['pk']) 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): def get_redirect_url(self, *args, **kwargs): @@ -2046,7 +2058,10 @@ class AcompanhamentoExcluirView(TemplateView): 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): model = MateriaLegislativa filterset_class = MateriaLegislativaFilterSet diff --git a/sapl/norma/views.py b/sapl/norma/views.py index 488e73c90..b82d83d39 100644 --- a/sapl/norma/views.py +++ b/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, MasterDetailCrud, make_pagination) from sapl.materia.models import Orgao -from sapl.utils import show_results_filter_set, get_client_ip,\ - sapn_is_enabled, MultiFormatOutputMixin +from sapl.utils import show_results_filter_set, get_client_ip, \ + sapn_is_enabled, MultiFormatOutputMixin, ratelimit_ip from .forms import (AnexoNormaJuridicaForm, NormaFilterSet, NormaJuridicaForm, NormaPesquisaSimplesForm, NormaRelacionadaForm, @@ -150,7 +150,10 @@ class NormaRelacionadaCrud(MasterDetailCrud): 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): model = NormaJuridica filterset_class = NormaFilterSet @@ -236,7 +239,10 @@ class AnexoNormaJuridicaCrud(MasterDetailCrud): initial['ano'] = self.object.ano 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): form_class = AnexoNormaJuridicaForm layout_key = 'AnexoNormaJuridica' @@ -285,7 +291,10 @@ class NormaCrud(Crud): namespace = self.model._meta.app_config.name 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): def get(self, request, *args, **kwargs): estatisticas_acesso_normas = AppConfig.objects.first().estatisticas_acesso_normas @@ -343,7 +352,10 @@ class NormaCrud(Crud): 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): def get(self, request, *args, **kwargs): diff --git a/sapl/sessao/views.py b/sapl/sessao/views.py index 7df367379..63f6a39e7 100755 --- a/sapl/sessao/views.py +++ b/sapl/sessao/views.py @@ -48,8 +48,8 @@ from sapl.sessao.forms import ExpedienteMateriaForm, OrdemDiaForm, OrdemExpedien CorrespondenciaForm, CorrespondenciaEmLoteFilterSet from sapl.sessao.models import Correspondencia from sapl.settings import TIME_ZONE -from sapl.utils import show_results_filter_set, remover_acentos, get_client_ip,\ - MultiFormatOutputMixin, PautaMultiFormatOutputMixin +from sapl.utils import show_results_filter_set, remover_acentos, get_client_ip, \ + MultiFormatOutputMixin, PautaMultiFormatOutputMixin, ratelimit_ip from .forms import (AdicionarVariasMateriasFilterSet, BancadaForm, ExpedienteForm, JustificativaAusenciaForm, OcorrenciaSessaoForm, ListMateriaForm, @@ -3797,7 +3797,10 @@ class SessaoListView(ListView): 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): model = SessaoPlenaria template_name = "sessao/pauta_inexistente.html" @@ -3813,7 +3816,10 @@ class PautaSessaoView(TemplateView): 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): template_name = "sessao/pauta_sessao_detail.html" model = SessaoPlenaria diff --git a/sapl/utils.py b/sapl/utils.py index 6554c5aea..ee97094aa 100644 --- a/sapl/utils.py +++ b/sapl/utils.py @@ -402,12 +402,20 @@ def xstr(s): def get_client_ip(request): + from ratelimit.core import ip_mask x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') if x_forwarded_for: ip = x_forwarded_for.split(',')[0] else: - ip = request.META.get('REMOTE_ADDR') - return ip + ip = request.META.get('HTTP_X_REAL_IP') or request.META.get('REMOTE_ADDR') or '0.0.0.0' + 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): From e6970585039d3c23fefa21402f7ec850ea22fed0 Mon Sep 17 00:00:00 2001 From: Edward Oliveira Date: Mon, 22 Sep 2025 17:26:47 -0300 Subject: [PATCH 4/5] Adiciona smoke test for rate limiter --- scripts/test_ratelimiter.sh | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100755 scripts/test_ratelimiter.sh diff --git a/scripts/test_ratelimiter.sh b/scripts/test_ratelimiter.sh new file mode 100755 index 000000000..e67651c5c --- /dev/null +++ b/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 From 3f0d61bdb6b46d8c41ce54d2f9fca4183fd5c9cc Mon Sep 17 00:00:00 2001 From: Edward Oliveira Date: Mon, 22 Sep 2025 17:27:41 -0300 Subject: [PATCH 5/5] Release: 3.1.164-RC5 --- CHANGES.md | 7 ++++++- docker/docker-compose.yaml | 2 +- sapl/settings.py | 2 +- sapl/templates/base.html | 2 +- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index f977fe2b5..677aea561 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,9 +1,14 @@ +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 - * Release: 3.1.164-RC3 3.1.164-RC3 / 2025-09-16 ======================== diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index cd8b7a2a1..66c788241 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -33,7 +33,7 @@ services: networks: - sapl-net sapl: - image: interlegis/sapl:3.1.164-RC4 + image: interlegis/sapl:3.1.164-RC5 # build: # context: ../ # dockerfile: ./docker/Dockerfile diff --git a/sapl/settings.py b/sapl/settings.py index c31b6b3b9..511c7f2e8 100644 --- a/sapl/settings.py +++ b/sapl/settings.py @@ -43,7 +43,7 @@ ALLOWED_HOSTS = ['*'] LOGIN_REDIRECT_URL = '/' LOGIN_URL = '/login/?next=' -SAPL_VERSION = '3.1.164-RC4' +SAPL_VERSION = '3.1.164-RC5' if DEBUG: EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' diff --git a/sapl/templates/base.html b/sapl/templates/base.html index a53524bd4..1a077f4b1 100644 --- a/sapl/templates/base.html +++ b/sapl/templates/base.html @@ -200,7 +200,7 @@ Desenvolvido pelo Interlegis em software livre e aberto. - Release: 3.1.164-RC4 + Release: 3.1.164-RC5