- sapl/audiencia/views.py: relative import 'from ..utils import ratelimit_ip'
missed in previous migration; updated to
'from sapl.middleware.ratelimit import ratelimit_ip'.
- settings.py: POD_NAMESPACE now defaults to `host` (socket.gethostname()
result already computed at line 29) so bare-metal, VM and docker-compose
deployments get a meaningful, unique KEY_PREFIX without any extra config.
K8s deployments override it via POD_NAMESPACE env var (Downward API /
start.sh service-account detection).
- ratelimit.py: _NAMESPACE = settings.POD_NAMESPACE — single source of
truth; removes duplicate K8s SA-file reading that settings.py/start.sh
already handle. Drops now-unused `import os`.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1. user_id: str(request.user.pk) — pk is int, lower()/strip() were no-ops
2. Redis key constants: RL_IP_REQUESTS, RL_IP_BLOCKED, RL_USER_REQUESTS,
RL_USER_BLOCKED, RL_NS_WINDOW — no more inline f-string literals
3. Tenant namespace: _NAMESPACE resolved once at module load from
POD_NAMESPACE env var (K8s Downward API) → service-account namespace
file → 'global' fallback. No per-request getattr(request, 'tenant').
4. KEY_PREFIX in CACHES['default'] set to POD_NAMESPACE (e.g. patobranco-pr)
so each tenant's cache keys are isolated in shared Redis.
5. Logger extra: replaced getattr(request, 'tenant', 'unknown') with
_NAMESPACE (the actual resolved constant).
settings.py: add POD_NAMESPACE = config('POD_NAMESPACE', default='sapl');
use it as KEY_PREFIX.
start.sh: add resolve_pod_namespace() (Downward API → SA file → fallback);
call it before resolve_redis_url(); write POD_NAMESPACE into .env.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove get_client_ip and ratelimit_ip from all sapl.utils import lines
and replace with direct imports from sapl.middleware.ratelimit, which
is now the canonical location for both functions.
Affected files:
sapl/comissoes/views.py
sapl/protocoloadm/views.py
sapl/crud/base.py
sapl/norma/views.py
sapl/sessao/views.py
sapl/painel/views.py
sapl/parlamentares/views.py
sapl/utils.py (re-export shim removed)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Move get_client_ip() and ratelimit_ip() from utils.py to
sapl/middleware/ratelimit.py (canonical location).
utils.py re-exports both via a single import line so all existing
callers (comissoes, crud, norma, sessao, painel, parlamentares,
protocoloadm) keep working with zero changes.
- get_client_ip() is now used inside RateLimitMiddleware instead of
the weaker _get_ip(): gains ip_mask() for IPv6 /64 collapsing and
HTTP_X_REAL_IP fallback.
- Replace getattr(settings, 'X', default) with settings.X throughout
__init__: settings.py always defines these vars, defaults were
duplicated and would silently drift. django.conf.settings proxy also
honours @override_settings in tests, unlike direct module imports.
- Replace getattr(..., []) or [] with set(settings.RATE_LIMIT_WHITELIST_IPS):
the cast in settings.py always returns a list, the double guard was
redundant.
- Remove unused _get_ip() and 'from sapl.settings import RATE_LIMITER_RATE'.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
sapl/middleware/ratelimit.py:
- Decision chain: known UA → IP blocked → authenticated → anonymous
- Authenticated: 120 req/min per user (rl:{ns}:user:{id}:reqs, DB1)
- Anonymous: 35 req/min per IP (rl:ip:{ip}:reqs) + per-ns/IP/window
counter to catch UA rotators (rl:ns:{ns}:ip:{ip}:w:{bucket})
- Blocking keys expire after 300 s (BLOCK_TTL)
- Thresholds driven by RATE_LIMITER_RATE / RATE_LIMITER_RATE_AUTHENTICATED
- RATE_LIMIT_WHITELIST_IPS for legislative-house IP ranges (future)
- Atomic INCR+EXPIRE via Redis Lua script; falls back to non-atomic
cache get/set when Redis unavailable (dry-run / file-cache safe)
- RATELIMIT_DRY_RUN=True by default — logs only, no 429s returned
- OAI-SearchBot added to BOT_UA_FRAGMENTS
- Suspicious-header check: missing Accept-Language + Accept (2/2)
- Whitelist check short-circuits all other checks
settings.py:
- RateLimitMiddleware inserted after AuthenticationMiddleware so
request.user is available for authenticated-vs-anonymous branching
- RATELIMIT_DRY_RUN (default True)
- RATE_LIMITER_RATE_AUTHENTICATED (default '120/m')
- RATE_LIMITER_RATE_BOT (default '5/m')
- RATE_LIMIT_WHITELIST_IPS (default empty)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Corrige polling excessivo em /voto-individual/
- Adiciona @login_required em votante_view para que usuários
anônimos sejam redirecionados ao login em vez de receberem o
template com auto-reload.
- Corrige bug do relógio: setInterval recursivo dentro de
startTime() acumulava timers exponencialmente. Trocado por
setTimeout, mantendo apenas um timer pendente.
- Move o script de reload/relógio para dentro do bloco
{% if not error_message %} para que a variante de erro não
agende reloads de 30s.
Reduz drasticamente o volume de requisições que disparavam o
rate limiter (RATE_LIMITER_RATE=35/m) quando vários parlamentares
mantinham a tela aberta atrás do mesmo IP NAT.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Bloqueia /voto-individual/ para usuários sem Votante/can_vote
Antes, usuários autenticados sem registro de Votante ou sem a
permissão parlamentares.can_vote recebiam HTTP 200 com um template
de erro (e, antes do commit anterior, ainda traziam o JS de auto-
reload). Agora a view exige as duas condições no início e retorna
HTTP 403 caso falhe:
- @permission_required('parlamentares.can_vote', raise_exception=True)
garante a permissão (com raise_exception para 403 ao invés do
redirect padrão pra LOGIN_URL).
- Checagem inline de Votante.objects.filter(user=...).exists() pega
o caso de superusers (que passam pelo bypass automático de
permissões do Django mas não têm cadastro de Votante) e de
qualquer custom group que conceda can_vote sem o cadastro.
Mantém HTTP 200 com mensagem amigável para falhas de estado que
um votante legítimo pode encontrar (nenhuma matéria aberta,
parlamentar não presente na sessão).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: joao <joao@mezzoplanejamento.com.br>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refact: cria método get_proximo_numero
* feat: impl numeração automática em cadastros via API
* Update sapl/materia/models.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update sapl/api/serializers.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update sapl/api/views_materia.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update sapl/materia/models.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update sapl/materia/models.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Add transaction atomic no endpoint create
* add validação de tipo se tipo não é objeto do model TipoMateriaLegislativa
* refact: aplica solicitações de reviewer e cria testes
* fix: altera numero_preferido para numero_candidato
* fix: remove espaços entre classes
* fix: corrige uso de transaction e ausencia dele
* fix: corrige testes devido mudança de norme de variável
* fix: altera seleção para select_for_update
* fix: mudança de nome remanescente para numero_candidato
* fix: retorna decorator na view function recuperar_materia
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* feat: impl HEADER LastModifiedDecorator na api
* fix: ajuste no frontend para evitar cache automático devido a LastModified sem tratamento adequado
* rebuild frontend
* fix: corrige last_modified_func para retorna sob retrieve
* refact: impl LastModified diretamente nos models que possuem campos específicos
* Seleciona tipo de votação para múltiplas matérias ao incluir na Ordem do Dia ou Expediente
* Ajustes solicitados na seleção de múltiplas Matérias para Ordem/Expediente
* Update adicionar_varias_materias_expediente.html
* Fix read-only mount on k8s
* Fix recibo proposição e adiciona rate limiter em matéria e norma
* Update forms.py
Alteração do nome do campo todos, conforme solicitação.
* Update adicionar_varias_materias_expediente.html
Ajuste no template por alteração do nome do campo "todos".
* Fix recibo proposição e adiciona rate limiter em matéria e norma
---------
Co-authored-by: root <root@info38.camaranh>
Co-authored-by: Edward Oliveira <edwardr@senado.gov.br>
Co-authored-by: Edward <9326037+edwardoliveira@users.noreply.github.com>
* Fix recibo proposição e adiciona rate limiter em matéria e norma
* Conserta bug na pesquisa do Relatório de Votações Nominais
Na pesquisa, ao selecionar filtro por Tipo de Matéria, Número e/ou Ano da Matéria, o sistema retorna erro 500.
* Fix recibo proposição e adiciona rate limiter em matéria e norma
---------
Co-authored-by: Edward Oliveira <edwardr@senado.gov.br>
* feat: impl filtro m2m com lookup '__in' para buscas com multiplos ids
* fix: remove alteração na criação de rotas da api
A classe DrfautoapiRouter removida nesse commit bem como a alteração no classmethod router é uma aleração necessária para que a api rode no django 5.2. Por outro lado, esta alteração quebra os links gerados pela template tag url e exigiria refatoração das mesmas. Esta alteração não é necessária para o propósito do PR.
* Implementa Relatório de Votações Nominais
* Alterações solicitadas - Relatório de Votações Nominais
* Apply suggestions from code review
Todas as sugestões de alteração acatadas.
Co-authored-by: Edward <9326037+edwardoliveira@users.noreply.github.com>
* Update views.py
Conforme observação sobre o retorno da QuerySet, escolha da opção 2 - colocar o qs dentro dos if's. Também houve a alteração na view, utilizando diretamente a classe genérica MultiFormatOutputMixin. Com a refatoração efetuada, não foi necessário definir uma especificação da mesma.
---------
Co-authored-by: root <root@info38.camaranh>
Co-authored-by: Edward <9326037+edwardoliveira@users.noreply.github.com>