Browse Source

Refatora rate limiter

pull/3800/merge
Edward Ribeiro 3 weeks ago
parent
commit
55f9312ae1
  1. 9
      sapl/audiencia/views.py
  2. 8
      sapl/base/views.py
  3. 11
      sapl/comissoes/views.py
  4. 28
      sapl/crud/base.py
  5. 40
      sapl/materia/views.py
  6. 3
      sapl/middleware.py
  7. 20
      sapl/norma/views.py
  8. 19
      sapl/parlamentares/views.py
  9. 27
      sapl/protocoloadm/views.py
  10. 67
      sapl/relatorios/views.py
  11. 15
      sapl/sessao/views.py
  12. 2
      sapl/settings.py
  13. 5
      sapl/static/.well-known/traffic-advice
  14. 15
      sapl/urls.py
  15. 9
      scripts/test_ratelimiter.sh

9
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 .forms import AudienciaForm, AnexoAudienciaPublicaForm
from .models import AudienciaPublica, AnexoAudienciaPublica 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): def index(request):
return HttpResponse("Audiência Pública") return HttpResponse("Audiência Pública")
@ -105,6 +111,3 @@ class AnexoAudienciaPublicaCrud(MasterDetailCrud):
qs = super(MasterDetailCrud.ListView, self).get_queryset() qs = super(MasterDetailCrud.ListView, self).get_queryset()
kwargs = {self.crud.parent_field: self.kwargs['pk']} kwargs = {self.crud.parent_field: self.kwargs['pk']}
return qs.filter(**kwargs).order_by('-data', '-id') return qs.filter(**kwargs).order_by('-data', '-id')
class DetailView(AudienciaPublicaMixin, MasterDetailCrud.DetailView):
pass

8
sapl/base/views.py

@ -48,7 +48,7 @@ from sapl.parlamentares.models import (
from sapl.protocoloadm.models import (Anexado, Protocolo) from sapl.protocoloadm.models import (Anexado, Protocolo)
from sapl.relatorios.views import (relatorio_estatisticas_acesso_normas) from sapl.relatorios.views import (relatorio_estatisticas_acesso_normas)
from sapl.sessao.models import (Bancada, SessaoPlenaria) 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, 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, ratelimit_ip) get_client_ip, sapn_is_enabled, is_weak_password, ratelimit_ip)
@ -68,7 +68,7 @@ class IndexView(TemplateView):
@method_decorator(ratelimit(key=ratelimit_ip, @method_decorator(ratelimit(key=ratelimit_ip,
rate='10/m', rate=RATE_LIMITER_RATE,
method=ratelimit.UNSAFE, method=ratelimit.UNSAFE,
block=True), block=True),
name='dispatch') name='dispatch')
@ -1400,6 +1400,10 @@ class SaplSearchView(SearchView):
return context return context
@method_decorator(ratelimit(key=ratelimit_ip,
rate=RATE_LIMITER_RATE,
block=True),
name='dispatch')
class PesquisarAuditLogView(PermissionRequiredMixin, FilterView): class PesquisarAuditLogView(PermissionRequiredMixin, FilterView):
model = AuditLog model = AuditLog
filterset_class = AuditLogFilterSet filterset_class = AuditLogFilterSet

11
sapl/comissoes/views.py

@ -28,11 +28,16 @@ from sapl.crud.base import (Crud, CrudAux, MasterDetailCrud,
RP_LIST) RP_LIST)
from sapl.materia.models import (MateriaEmTramitacao, MateriaLegislativa, from sapl.materia.models import (MateriaEmTramitacao, MateriaLegislativa,
PautaReuniao, Tramitacao) 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, from .models import (CargoComissao, Comissao, Composicao, DocumentoAcessorio,
Participacao, Periodo, Reuniao, TipoComissao) 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): def pegar_url_composicao(pk):
participacao = Participacao.objects.get(id=pk) participacao = Participacao.objects.get(id=pk)
@ -333,6 +338,10 @@ class RemovePautaView(PermissionRequiredMixin, CreateView):
return HttpResponseRedirect(success_url) return HttpResponseRedirect(success_url)
@method_decorator(ratelimit(key=ratelimit_ip,
rate=RATE_LIMITER_RATE,
block=True),
name='dispatch')
class AdicionaPautaView(PermissionRequiredMixin, FilterView): class AdicionaPautaView(PermissionRequiredMixin, FilterView):
filterset_class = PautaReuniaoFilterSet filterset_class = PautaReuniaoFilterSet
template_name = 'comissoes/pauta.html' template_name = 'comissoes/pauta.html'

28
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.crispy_layout_mixin import SaplFormHelper
from sapl.rules import (RP_ADD, RP_CHANGE, RP_DELETE, RP_DETAIL, from sapl.rules import (RP_ADD, RP_CHANGE, RP_DELETE, RP_DETAIL,
RP_LIST) 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) logger = logging.getLogger(settings.BASE_DIR.name)
@ -101,7 +105,6 @@ variáveis do crud:
class SearchMixin(models.Model): class SearchMixin(models.Model):
search = models.TextField(blank=True, default='') search = models.TextField(blank=True, default='')
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -267,7 +270,6 @@ class CrudBaseMixin(CrispyLayoutFormMixin):
obj.public = [] obj.public = []
if hasattr(self, 'permission_required') and self.permission_required: if hasattr(self, 'permission_required') and self.permission_required:
self.permission_required = tuple( self.permission_required = tuple(
( (
self.permission(pr) for pr in ( self.permission(pr) for pr in (
@ -388,6 +390,10 @@ class CrudBaseMixin(CrispyLayoutFormMixin):
return self.model._meta.verbose_name_plural 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): class CrudListView(PermissionRequiredContainerCrudMixin, ListView):
permission_required = (RP_LIST,) permission_required = (RP_LIST,)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -724,9 +730,12 @@ class CrudCreateView(PermissionRequiredContainerCrudMixin,
return super().form_valid(form) return super().form_valid(form)
@method_decorator(ratelimit(key=ratelimit_ip,
rate=RATE_LIMITER_RATE,
block=True),
name='dispatch')
class CrudDetailView(PermissionRequiredContainerCrudMixin, class CrudDetailView(PermissionRequiredContainerCrudMixin,
DetailView, MultipleObjectMixin): DetailView, MultipleObjectMixin):
permission_required = (RP_DETAIL,) permission_required = (RP_DETAIL,)
no_entries_msg = _('Nenhum registro Associado.') no_entries_msg = _('Nenhum registro Associado.')
paginate_by = 10 paginate_by = 10
@ -976,7 +985,6 @@ class Crud:
view.permission_required and \ view.permission_required and \
hasattr(cls, 'public') and \ hasattr(cls, 'public') and \
cls.public: cls.public:
# print(view.permission_required, view) # print(view.permission_required, view)
# print(cls.public, cls) # print(cls.public, cls)
@ -1036,7 +1044,6 @@ class Crud:
def build(cls, _model, _help_topic, _model_set=None, list_field_names=[]): def build(cls, _model, _help_topic, _model_set=None, list_field_names=[]):
def create_class(_list_field_names): def create_class(_list_field_names):
class ModelCrud(cls): class ModelCrud(cls):
model = _model model = _model
model_set = _model_set model_set = _model_set
@ -1080,7 +1087,6 @@ class CrudAux(Crud):
@classonlymethod @classonlymethod
def build(cls, _model, _help_topic, _model_set=None, list_field_names=[]): def build(cls, _model, _help_topic, _model_set=None, list_field_names=[]):
ModelCrud = Crud.build( ModelCrud = Crud.build(
_model, _help_topic, _model_set, list_field_names) _model, _help_topic, _model_set, list_field_names)
@ -1182,6 +1188,10 @@ class MasterDetailCrud(Crud):
context['title'] = title context['title'] = title
return context return context
@method_decorator(ratelimit(key=ratelimit_ip,
rate=RATE_LIMITER_RATE,
block=True),
name='dispatch')
class ListView(Crud.ListView): class ListView(Crud.ListView):
permission_required = RP_LIST, permission_required = RP_LIST,
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -1414,6 +1424,10 @@ class MasterDetailCrud(Crud):
else: else:
return self.resolve_url(ACTION_LIST, args=(pk,)) return self.resolve_url(ACTION_LIST, args=(pk,))
@method_decorator(ratelimit(key=ratelimit_ip,
rate=RATE_LIMITER_RATE,
block=True),
name='dispatch')
class DetailView(Crud.DetailView): class DetailView(Crud.DetailView):
permission_required = RP_DETAIL, permission_required = RP_DETAIL,
template_name = 'crud/detail_detail.html' template_name = 'crud/detail_detail.html'

40
sapl/materia/views.py

@ -51,7 +51,7 @@ from sapl.materia.forms import (AnexadaForm, AutoriaForm, AutoriaMultiCreateForm
from sapl.norma.models import LegislacaoCitada from sapl.norma.models import LegislacaoCitada
from sapl.parlamentares.models import Legislatura from sapl.parlamentares.models import Legislatura
from sapl.protocoloadm.models import Protocolo from sapl.protocoloadm.models import Protocolo
from sapl.settings import MAX_DOC_UPLOAD_SIZE, MEDIA_ROOT from sapl.settings import MAX_DOC_UPLOAD_SIZE, MEDIA_ROOT, RATE_LIMITER_RATE
from sapl.utils import (autor_label, autor_modal, gerar_hash_arquivo, get_base_url, from sapl.utils import (autor_label, autor_modal, gerar_hash_arquivo, get_base_url,
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,
@ -134,6 +134,10 @@ def proposicao_texto(request, pk):
raise Http404 raise Http404
@method_decorator(ratelimit(key=ratelimit_ip,
rate=RATE_LIMITER_RATE,
block=True),
name='dispatch')
class AdicionarVariasAutorias(PermissionRequiredForAppCrudMixin, FilterView): class AdicionarVariasAutorias(PermissionRequiredForAppCrudMixin, FilterView):
app_label = sapl.materia.apps.AppConfig.label app_label = sapl.materia.apps.AppConfig.label
filterset_class = AdicionarVariasAutoriasFilterSet filterset_class = AdicionarVariasAutoriasFilterSet
@ -394,6 +398,10 @@ class StatusTramitacaoCrud(CrudAux):
return reverse('sapl.materia:pesquisar_statustramitacao') return reverse('sapl.materia:pesquisar_statustramitacao')
@method_decorator(ratelimit(key=ratelimit_ip,
rate=RATE_LIMITER_RATE,
block=True),
name='dispatch')
class PesquisarStatusTramitacaoView(FilterView): class PesquisarStatusTramitacaoView(FilterView):
model = StatusTramitacao model = StatusTramitacao
filterset_class = StatusTramitacaoFilterSet filterset_class = StatusTramitacaoFilterSet
@ -1461,10 +1469,6 @@ class TramitacaoCrud(MasterDetailCrud):
return initial return initial
@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):
@ -1537,10 +1541,6 @@ class TramitacaoCrud(MasterDetailCrud):
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@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"
@ -1918,10 +1918,6 @@ 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=ratelimit_ip,
rate='10/m',
block=True),
name='dispatch')
class DetailView(Crud.DetailView): class DetailView(Crud.DetailView):
layout_key = 'MateriaLegislativaDetail' layout_key = 'MateriaLegislativaDetail'
@ -1934,10 +1930,6 @@ class MateriaLegislativaCrud(Crud):
pk=self.kwargs['pk']) pk=self.kwargs['pk'])
return context return context
@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):
@ -2059,7 +2051,7 @@ class AcompanhamentoExcluirView(TemplateView):
@method_decorator(ratelimit(key=ratelimit_ip, @method_decorator(ratelimit(key=ratelimit_ip,
rate='10/m', rate=RATE_LIMITER_RATE,
block=True), block=True),
name='dispatch') name='dispatch')
class MateriaLegislativaPesquisaView(MultiFormatOutputMixin, FilterView): class MateriaLegislativaPesquisaView(MultiFormatOutputMixin, FilterView):
@ -2323,6 +2315,10 @@ class AcompanhamentoMateriaView(CreateView):
kwargs={'pk': self.kwargs['pk']}) kwargs={'pk': self.kwargs['pk']})
@method_decorator(ratelimit(key=ratelimit_ip,
rate=RATE_LIMITER_RATE,
block=True),
name='dispatch')
class DocumentoAcessorioEmLoteView(PermissionRequiredMixin, FilterView): class DocumentoAcessorioEmLoteView(PermissionRequiredMixin, FilterView):
filterset_class = AcessorioEmLoteFilterSet filterset_class = AcessorioEmLoteFilterSet
template_name = 'materia/em_lote/acessorio.html' template_name = 'materia/em_lote/acessorio.html'
@ -2435,6 +2431,10 @@ class DocumentoAcessorioEmLoteView(PermissionRequiredMixin, FilterView):
return self.get(request, self.kwargs) return self.get(request, self.kwargs)
@method_decorator(ratelimit(key=ratelimit_ip,
rate=RATE_LIMITER_RATE,
block=True),
name='dispatch')
class MateriaAnexadaEmLoteView(PermissionRequiredMixin, FilterView): class MateriaAnexadaEmLoteView(PermissionRequiredMixin, FilterView):
filterset_class = AnexadaEmLoteFilterSet filterset_class = AnexadaEmLoteFilterSet
template_name = 'materia/em_lote/anexada.html' template_name = 'materia/em_lote/anexada.html'
@ -2559,6 +2559,10 @@ class MateriaAnexadaEmLoteView(PermissionRequiredMixin, FilterView):
return HttpResponseRedirect(success_url) return HttpResponseRedirect(success_url)
@method_decorator(ratelimit(key=ratelimit_ip,
rate=RATE_LIMITER_RATE,
block=True),
name='dispatch')
class PrimeiraTramitacaoEmLoteView(PermissionRequiredMixin, FilterView): class PrimeiraTramitacaoEmLoteView(PermissionRequiredMixin, FilterView):
filterset_class = PrimeiraTramitacaoEmLoteFilterSet filterset_class = PrimeiraTramitacaoEmLoteFilterSet
template_name = 'materia/em_lote/tramitacao.html' template_name = 'materia/em_lote/tramitacao.html'

3
sapl/middleware.py

@ -19,3 +19,6 @@ class CheckWeakPasswordMiddleware:
return redirect('sapl.base:alterar_senha') return redirect('sapl.base:alterar_senha')
return self.get_response(request) return self.get_response(request)

20
sapl/norma/views.py

@ -38,7 +38,7 @@ from .forms import (AnexoNormaJuridicaForm, NormaFilterSet, NormaJuridicaForm,
AutoriaNormaForm, AssuntoNormaFilterSet) AutoriaNormaForm, AssuntoNormaFilterSet)
from .models import (AnexoNormaJuridica, AssuntoNorma, NormaJuridica, NormaRelacionada, from .models import (AnexoNormaJuridica, AssuntoNorma, NormaJuridica, NormaRelacionada,
TipoNormaJuridica, TipoVinculoNormaJuridica, AutoriaNorma, NormaEstatisticas) TipoNormaJuridica, TipoVinculoNormaJuridica, AutoriaNorma, NormaEstatisticas)
from ..settings import RATE_LIMITER_RATE
# LegislacaoCitadaCrud = Crud.build(LegislacaoCitada, '') # LegislacaoCitadaCrud = Crud.build(LegislacaoCitada, '')
TipoNormaCrud = CrudAux.build( TipoNormaCrud = CrudAux.build(
@ -60,6 +60,10 @@ class AssuntoNormaCrud(CrudAux):
return reverse('sapl.norma:pesquisar_assuntonorma') return reverse('sapl.norma:pesquisar_assuntonorma')
@method_decorator(ratelimit(key=ratelimit_ip,
rate=RATE_LIMITER_RATE,
block=True),
name='dispatch')
class PesquisarAssuntoNormaView(FilterView): class PesquisarAssuntoNormaView(FilterView):
model = AssuntoNorma model = AssuntoNorma
filterset_class = AssuntoNormaFilterSet filterset_class = AssuntoNormaFilterSet
@ -151,7 +155,7 @@ class NormaRelacionadaCrud(MasterDetailCrud):
@method_decorator(ratelimit(key=ratelimit_ip, @method_decorator(ratelimit(key=ratelimit_ip,
rate='10/m', rate=RATE_LIMITER_RATE,
block=True), block=True),
name='dispatch') name='dispatch')
class NormaPesquisaView(MultiFormatOutputMixin, FilterView): class NormaPesquisaView(MultiFormatOutputMixin, FilterView):
@ -239,10 +243,6 @@ class AnexoNormaJuridicaCrud(MasterDetailCrud):
initial['ano'] = self.object.ano initial['ano'] = self.object.ano
return initial return initial
@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'
@ -291,10 +291,6 @@ 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=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
@ -352,10 +348,6 @@ class NormaCrud(Crud):
layout_key = 'NormaJuridicaCreate' layout_key = 'NormaJuridicaCreate'
@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):

19
sapl/parlamentares/views.py

@ -33,7 +33,7 @@ from sapl.materia.models import Autoria, Proposicao, Relatoria
from sapl.norma.models import AutoriaNorma, NormaJuridica from sapl.norma.models import AutoriaNorma, NormaJuridica
from sapl.parlamentares.apps import AppConfig from sapl.parlamentares.apps import AppConfig
from sapl.rules import SAPL_GROUP_VOTANTE from sapl.rules import SAPL_GROUP_VOTANTE
from sapl.utils import (parlamentares_ativos, show_results_filter_set) from sapl.utils import (parlamentares_ativos, show_results_filter_set, ratelimit_ip)
from .forms import (ColigacaoFilterSet, FiliacaoForm, FrenteForm, LegislaturaForm, MandatoForm, from .forms import (ColigacaoFilterSet, FiliacaoForm, FrenteForm, LegislaturaForm, MandatoForm,
ParlamentarCreateForm, ParlamentarForm, VotanteForm, ParlamentarCreateForm, ParlamentarForm, VotanteForm,
@ -45,6 +45,11 @@ from .models import (CargoMesa, Coligacao, ComposicaoColigacao, ComposicaoMesa,
SituacaoMilitar, TipoAfastamento, TipoDependente, Votante, SituacaoMilitar, TipoAfastamento, TipoDependente, Votante,
Bloco, FrenteCargo, FrenteParlamentar, BlocoCargo, BlocoMembro, MesaDiretora) Bloco, FrenteCargo, FrenteParlamentar, BlocoCargo, BlocoMembro, MesaDiretora)
from ratelimit.decorators import ratelimit
from django.utils.decorators import method_decorator
from ..settings import RATE_LIMITER_RATE
FrenteCargoCrud = CrudAux.build(FrenteCargo, 'frente_cargo') FrenteCargoCrud = CrudAux.build(FrenteCargo, 'frente_cargo')
BlocoCargoCrud = CrudAux.build(BlocoCargo, 'bloco_cargo') BlocoCargoCrud = CrudAux.build(BlocoCargo, 'bloco_cargo')
CargoMesaCrud = CrudAux.build(CargoMesa, 'cargo_mesa') CargoMesaCrud = CrudAux.build(CargoMesa, 'cargo_mesa')
@ -183,6 +188,10 @@ class ProposicaoParlamentarCrud(CrudBaseForListAndDetailExternalAppView):
_('Texto Eletrônico')) _('Texto Eletrônico'))
@method_decorator(ratelimit(key=ratelimit_ip,
rate=RATE_LIMITER_RATE,
block=True),
name='dispatch')
class PesquisarParlamentarView(FilterView): class PesquisarParlamentarView(FilterView):
model = Parlamentar model = Parlamentar
filterset_class = ParlamentarFilterSet filterset_class = ParlamentarFilterSet
@ -245,6 +254,10 @@ class PesquisarParlamentarView(FilterView):
return self.render_to_response(context) return self.render_to_response(context)
@method_decorator(ratelimit(key=ratelimit_ip,
rate=RATE_LIMITER_RATE,
block=True),
name='dispatch')
class PesquisarColigacaoView(FilterView): class PesquisarColigacaoView(FilterView):
model = Coligacao model = Coligacao
filterset_class = ColigacaoFilterSet filterset_class = ColigacaoFilterSet
@ -301,6 +314,10 @@ class PesquisarColigacaoView(FilterView):
return self.render_to_response(context) return self.render_to_response(context)
@method_decorator(ratelimit(key=ratelimit_ip,
rate=RATE_LIMITER_RATE,
block=True),
name='dispatch')
class PesquisarPartidoView(FilterView): class PesquisarPartidoView(FilterView):
model = Partido model = Partido
filterset_class = PartidoFilterSet filterset_class = PartidoFilterSet

27
sapl/protocoloadm/views.py

@ -47,7 +47,7 @@ from sapl.relatorios.views import relatorio_doc_administrativos
from sapl.utils import (create_barcode, get_base_url, get_client_ip, from sapl.utils import (create_barcode, get_base_url, get_client_ip,
get_mime_type_from_file_extension, lista_anexados, get_mime_type_from_file_extension, lista_anexados,
show_results_filter_set, mail_service_configured, from_date_to_datetime_utc, show_results_filter_set, mail_service_configured, from_date_to_datetime_utc,
google_recaptcha_configured, get_tempfile_dir, MultiFormatOutputMixin) google_recaptcha_configured, get_tempfile_dir, MultiFormatOutputMixin, ratelimit_ip)
from .forms import (AcompanhamentoDocumentoForm, AnexadoEmLoteFilterSet, AnexadoForm, from .forms import (AcompanhamentoDocumentoForm, AnexadoEmLoteFilterSet, AnexadoForm,
AnularProtocoloAdmForm, compara_tramitacoes_doc, AnularProtocoloAdmForm, compara_tramitacoes_doc,
@ -62,7 +62,10 @@ from .forms import (AcompanhamentoDocumentoForm, AnexadoEmLoteFilterSet, Anexado
from .models import (Anexado, AcompanhamentoDocumento, DocumentoAcessorioAdministrativo, from .models import (Anexado, AcompanhamentoDocumento, DocumentoAcessorioAdministrativo,
DocumentoAdministrativo, StatusTramitacaoAdministrativo, DocumentoAdministrativo, StatusTramitacaoAdministrativo,
TipoDocumentoAdministrativo, TramitacaoAdministrativo) TipoDocumentoAdministrativo, TramitacaoAdministrativo)
from ..settings import MEDIA_ROOT from ..settings import MEDIA_ROOT, RATE_LIMITER_RATE
from ratelimit.decorators import ratelimit
from django.utils.decorators import method_decorator
TipoDocumentoAdministrativoCrud = CrudAux.build( TipoDocumentoAdministrativoCrud = CrudAux.build(
TipoDocumentoAdministrativo, '') TipoDocumentoAdministrativo, '')
@ -535,6 +538,10 @@ class StatusTramitacaoAdministrativoCrud(CrudAux):
ordering = 'sigla' ordering = 'sigla'
@method_decorator(ratelimit(key=ratelimit_ip,
rate=RATE_LIMITER_RATE,
block=True),
name='dispatch')
class ProtocoloPesquisaView(PermissionRequiredMixin, FilterView): class ProtocoloPesquisaView(PermissionRequiredMixin, FilterView):
model = Protocolo model = Protocolo
filterset_class = ProtocoloFilterSet filterset_class = ProtocoloFilterSet
@ -1032,6 +1039,10 @@ class ProtocoloMateriaTemplateView(PermissionRequiredMixin, TemplateView):
return context return context
@method_decorator(ratelimit(key=ratelimit_ip,
rate=RATE_LIMITER_RATE,
block=True),
name='dispatch')
class PesquisarDocumentoAdministrativoView(DocumentoAdministrativoMixin, class PesquisarDocumentoAdministrativoView(DocumentoAdministrativoMixin,
MultiFormatOutputMixin, MultiFormatOutputMixin,
PermissionRequiredMixin, PermissionRequiredMixin,
@ -1165,6 +1176,10 @@ class AnexadoCrud(MasterDetailCrud):
return 'AnexadoDetail' return 'AnexadoDetail'
@method_decorator(ratelimit(key=ratelimit_ip,
rate=RATE_LIMITER_RATE,
block=True),
name='dispatch')
class DocumentoAnexadoEmLoteView(PermissionRequiredMixin, FilterView): class DocumentoAnexadoEmLoteView(PermissionRequiredMixin, FilterView):
filterset_class = AnexadoEmLoteFilterSet filterset_class = AnexadoEmLoteFilterSet
template_name = 'protocoloadm/em_lote/anexado.html' template_name = 'protocoloadm/em_lote/anexado.html'
@ -1642,6 +1657,10 @@ class FichaSelecionaAdmView(PermissionRequiredMixin, FormView):
'materia/impressos/ficha_adm_pdf.html') 'materia/impressos/ficha_adm_pdf.html')
@method_decorator(ratelimit(key=ratelimit_ip,
rate=RATE_LIMITER_RATE,
block=True),
name='dispatch')
class PrimeiraTramitacaoEmLoteAdmView(PermissionRequiredMixin, FilterView): class PrimeiraTramitacaoEmLoteAdmView(PermissionRequiredMixin, FilterView):
filterset_class = PrimeiraTramitacaoEmLoteAdmFilterSet filterset_class = PrimeiraTramitacaoEmLoteAdmFilterSet
template_name = 'protocoloadm/em_lote/tramitacaoadm.html' template_name = 'protocoloadm/em_lote/tramitacaoadm.html'
@ -1878,6 +1897,10 @@ class VinculoDocAdminMateriaCrud(MasterDetailCrud):
return context return context
@method_decorator(ratelimit(key=ratelimit_ip,
rate=RATE_LIMITER_RATE,
block=True),
name='dispatch')
class VinculoDocAdminMateriaEmLoteView(PermissionRequiredMixin, FilterView): class VinculoDocAdminMateriaEmLoteView(PermissionRequiredMixin, FilterView):
filterset_class = VinculoDocAdminMateriaEmLoteFilterSet filterset_class = VinculoDocAdminMateriaEmLoteFilterSet
template_name = 'protocoloadm/em_lote/vinculodocadminmateria.html' template_name = 'protocoloadm/em_lote/vinculodocadminmateria.html'

67
sapl/relatorios/views.py

@ -48,10 +48,10 @@ from sapl.sessao.views import (get_identificacao_basica, get_mesa_diretora,
get_oradores_explicacoes_pessoais, get_consideracoes_finais, get_oradores_explicacoes_pessoais, get_consideracoes_finais,
get_ocorrencias_da_sessao, get_assinaturas, get_ocorrencias_da_sessao, get_assinaturas,
get_correspondencias) get_correspondencias)
from sapl.settings import MEDIA_URL from sapl.settings import MEDIA_URL, RATE_LIMITER_RATE
from sapl.settings import STATIC_ROOT from sapl.settings import STATIC_ROOT
from sapl.utils import LISTA_DE_UFS, TrocaTag, filiacao_data, create_barcode, show_results_filter_set, \ from sapl.utils import LISTA_DE_UFS, TrocaTag, filiacao_data, create_barcode, show_results_filter_set, \
num_materias_por_tipo, parlamentares_ativos, MultiFormatOutputMixin num_materias_por_tipo, parlamentares_ativos, MultiFormatOutputMixin, ratelimit_ip
from .templates import (pdf_capa_processo_gerar, from .templates import (pdf_capa_processo_gerar,
pdf_documento_administrativo_gerar, pdf_espelho_gerar, pdf_documento_administrativo_gerar, pdf_espelho_gerar,
pdf_etiqueta_protocolo_gerar, pdf_materia_gerar, pdf_etiqueta_protocolo_gerar, pdf_materia_gerar,
@ -59,6 +59,9 @@ from .templates import (pdf_capa_processo_gerar,
pdf_protocolo_gerar, pdf_sessao_plenaria_gerar) pdf_protocolo_gerar, pdf_sessao_plenaria_gerar)
from sapl.crud.base import make_pagination from sapl.crud.base import make_pagination
from ratelimit.decorators import ratelimit
from django.utils.decorators import method_decorator
def get_kwargs_params(request, fields): def get_kwargs_params(request, fields):
kwargs = {} kwargs = {}
@ -1841,6 +1844,10 @@ class RelatorioMixin:
return self.render_to_response(context) return self.render_to_response(context)
@method_decorator(ratelimit(key=ratelimit_ip,
rate=RATE_LIMITER_RATE,
block=True),
name='dispatch')
class RelatorioDocumentosAcessoriosView(RelatorioMixin, FilterView): class RelatorioDocumentosAcessoriosView(RelatorioMixin, FilterView):
model = DocumentoAcessorio model = DocumentoAcessorio
filterset_class = RelatorioDocumentosAcessoriosFilterSet filterset_class = RelatorioDocumentosAcessoriosFilterSet
@ -1885,6 +1892,10 @@ class RelatorioDocumentosAcessoriosView(RelatorioMixin, FilterView):
return context return context
@method_decorator(ratelimit(key=ratelimit_ip,
rate=RATE_LIMITER_RATE,
block=True),
name='dispatch')
class RelatorioVotacoesNominaisView(RelatorioMixin, MultiFormatOutputMixin, FilterView): class RelatorioVotacoesNominaisView(RelatorioMixin, MultiFormatOutputMixin, FilterView):
model = VotoParlamentar model = VotoParlamentar
filterset_class = RelatorioVotacoesNominaisFilterSet filterset_class = RelatorioVotacoesNominaisFilterSet
@ -1954,6 +1965,10 @@ class RelatorioVotacoesNominaisView(RelatorioMixin, MultiFormatOutputMixin, Filt
return context return context
@method_decorator(ratelimit(key=ratelimit_ip,
rate=RATE_LIMITER_RATE,
block=True),
name='dispatch')
class RelatorioAtasView(RelatorioMixin, FilterView): class RelatorioAtasView(RelatorioMixin, FilterView):
model = SessaoPlenaria model = SessaoPlenaria
filterset_class = RelatorioAtasFilterSet filterset_class = RelatorioAtasFilterSet
@ -1979,6 +1994,10 @@ class RelatorioAtasView(RelatorioMixin, FilterView):
return context return context
@method_decorator(ratelimit(key=ratelimit_ip,
rate=RATE_LIMITER_RATE,
block=True),
name='dispatch')
class RelatorioPresencaSessaoView(RelatorioMixin, FilterView): class RelatorioPresencaSessaoView(RelatorioMixin, FilterView):
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
model = SessaoPlenaria model = SessaoPlenaria
@ -2213,6 +2232,10 @@ class RelatorioPresencaSessaoView(RelatorioMixin, FilterView):
return context return context
@method_decorator(ratelimit(key=ratelimit_ip,
rate=RATE_LIMITER_RATE,
block=True),
name='dispatch')
class RelatorioHistoricoTramitacaoView(RelatorioMixin, FilterView): class RelatorioHistoricoTramitacaoView(RelatorioMixin, FilterView):
model = MateriaLegislativa model = MateriaLegislativa
filterset_class = RelatorioHistoricoTramitacaoFilterSet filterset_class = RelatorioHistoricoTramitacaoFilterSet
@ -2270,6 +2293,10 @@ class RelatorioHistoricoTramitacaoView(RelatorioMixin, FilterView):
return context return context
@method_decorator(ratelimit(key=ratelimit_ip,
rate=RATE_LIMITER_RATE,
block=True),
name='dispatch')
class RelatorioDataFimPrazoTramitacaoView(RelatorioMixin, FilterView): class RelatorioDataFimPrazoTramitacaoView(RelatorioMixin, FilterView):
model = MateriaEmTramitacao model = MateriaEmTramitacao
filterset_class = RelatorioDataFimPrazoTramitacaoFilterSet filterset_class = RelatorioDataFimPrazoTramitacaoFilterSet
@ -2333,6 +2360,10 @@ class RelatorioDataFimPrazoTramitacaoView(RelatorioMixin, FilterView):
return context return context
@method_decorator(ratelimit(key=ratelimit_ip,
rate=RATE_LIMITER_RATE,
block=True),
name='dispatch')
class RelatorioReuniaoView(RelatorioMixin, FilterView): class RelatorioReuniaoView(RelatorioMixin, FilterView):
model = Reuniao model = Reuniao
filterset_class = RelatorioReuniaoFilterSet filterset_class = RelatorioReuniaoFilterSet
@ -2367,6 +2398,10 @@ class RelatorioReuniaoView(RelatorioMixin, FilterView):
return context return context
@method_decorator(ratelimit(key=ratelimit_ip,
rate=RATE_LIMITER_RATE,
block=True),
name='dispatch')
class RelatorioAudienciaView(RelatorioMixin, FilterView): class RelatorioAudienciaView(RelatorioMixin, FilterView):
model = AudienciaPublica model = AudienciaPublica
filterset_class = RelatorioAudienciaFilterSet filterset_class = RelatorioAudienciaFilterSet
@ -2401,6 +2436,10 @@ class RelatorioAudienciaView(RelatorioMixin, FilterView):
return context return context
@method_decorator(ratelimit(key=ratelimit_ip,
rate=RATE_LIMITER_RATE,
block=True),
name='dispatch')
class RelatorioMateriasTramitacaoView(RelatorioMixin, FilterView): class RelatorioMateriasTramitacaoView(RelatorioMixin, FilterView):
model = MateriaEmTramitacao model = MateriaEmTramitacao
filterset_class = RelatorioMateriasTramitacaoFilterSet filterset_class = RelatorioMateriasTramitacaoFilterSet
@ -2515,6 +2554,10 @@ class RelatorioMateriasTramitacaoView(RelatorioMixin, FilterView):
return context return context
@method_decorator(ratelimit(key=ratelimit_ip,
rate=RATE_LIMITER_RATE,
block=True),
name='dispatch')
class RelatorioMateriasPorAnoAutorTipoView(RelatorioMixin, FilterView): class RelatorioMateriasPorAnoAutorTipoView(RelatorioMixin, FilterView):
model = MateriaLegislativa model = MateriaLegislativa
filterset_class = RelatorioMateriasPorAnoAutorTipoFilterSet filterset_class = RelatorioMateriasPorAnoAutorTipoFilterSet
@ -2594,6 +2637,10 @@ class RelatorioMateriasPorAnoAutorTipoView(RelatorioMixin, FilterView):
return context return context
@method_decorator(ratelimit(key=ratelimit_ip,
rate=RATE_LIMITER_RATE,
block=True),
name='dispatch')
class RelatorioMateriasPorAutorView(RelatorioMixin, FilterView): class RelatorioMateriasPorAutorView(RelatorioMixin, FilterView):
model = MateriaLegislativa model = MateriaLegislativa
filterset_class = RelatorioMateriasPorAutorFilterSet filterset_class = RelatorioMateriasPorAutorFilterSet
@ -2665,6 +2712,10 @@ class RelatorioMateriaAnoAssuntoView(ListView):
return context return context
@method_decorator(ratelimit(key=ratelimit_ip,
rate=RATE_LIMITER_RATE,
block=True),
name='dispatch')
class RelatorioNormasPublicadasMesView(RelatorioMixin, FilterView): class RelatorioNormasPublicadasMesView(RelatorioMixin, FilterView):
model = NormaJuridica model = NormaJuridica
filterset_class = RelatorioNormasMesFilterSet filterset_class = RelatorioNormasMesFilterSet
@ -2705,6 +2756,10 @@ class RelatorioNormasPublicadasMesView(RelatorioMixin, FilterView):
return context return context
@method_decorator(ratelimit(key=ratelimit_ip,
rate=RATE_LIMITER_RATE,
block=True),
name='dispatch')
class RelatorioNormasVigenciaView(RelatorioMixin, FilterView): class RelatorioNormasVigenciaView(RelatorioMixin, FilterView):
model = NormaJuridica model = NormaJuridica
filterset_class = RelatorioNormasVigenciaFilterSet filterset_class = RelatorioNormasVigenciaFilterSet
@ -2769,6 +2824,10 @@ class RelatorioNormasVigenciaView(RelatorioMixin, FilterView):
return context return context
@method_decorator(ratelimit(key=ratelimit_ip,
rate=RATE_LIMITER_RATE,
block=True),
name='dispatch')
class RelatorioHistoricoTramitacaoAdmView(RelatorioMixin, FilterView): class RelatorioHistoricoTramitacaoAdmView(RelatorioMixin, FilterView):
model = DocumentoAdministrativo model = DocumentoAdministrativo
filterset_class = RelatorioHistoricoTramitacaoAdmFilterSet filterset_class = RelatorioHistoricoTramitacaoAdmFilterSet
@ -2819,6 +2878,10 @@ class RelatorioHistoricoTramitacaoAdmView(RelatorioMixin, FilterView):
return context return context
@method_decorator(ratelimit(key=ratelimit_ip,
rate=RATE_LIMITER_RATE,
block=True),
name='dispatch')
class RelatorioNormasPorAutorView(RelatorioMixin, FilterView): class RelatorioNormasPorAutorView(RelatorioMixin, FilterView):
model = NormaJuridica model = NormaJuridica
filterset_class = RelatorioNormasPorAutorFilterSet filterset_class = RelatorioNormasPorAutorFilterSet

15
sapl/sessao/views.py

@ -47,7 +47,7 @@ from sapl.sessao.apps import AppConfig
from sapl.sessao.forms import ExpedienteMateriaForm, OrdemDiaForm, OrdemExpedienteLeituraForm, \ from sapl.sessao.forms import ExpedienteMateriaForm, OrdemDiaForm, OrdemExpedienteLeituraForm, \
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, RATE_LIMITER_RATE
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, ratelimit_ip MultiFormatOutputMixin, PautaMultiFormatOutputMixin, ratelimit_ip
@ -3798,7 +3798,7 @@ class SessaoListView(ListView):
@method_decorator(ratelimit(key=ratelimit_ip, @method_decorator(ratelimit(key=ratelimit_ip,
rate='10/m', rate=RATE_LIMITER_RATE,
block=True), block=True),
name='dispatch') name='dispatch')
class PautaSessaoView(TemplateView): class PautaSessaoView(TemplateView):
@ -3817,7 +3817,7 @@ class PautaSessaoView(TemplateView):
@method_decorator(ratelimit(key=ratelimit_ip, @method_decorator(ratelimit(key=ratelimit_ip,
rate='10/m', rate=RATE_LIMITER_RATE,
block=True), block=True),
name='dispatch') name='dispatch')
class PautaSessaoDetailView(PautaMultiFormatOutputMixin, DetailView): class PautaSessaoDetailView(PautaMultiFormatOutputMixin, DetailView):
@ -4001,6 +4001,10 @@ class PautaSessaoDetailView(PautaMultiFormatOutputMixin, DetailView):
return self.render_to_response(context) return self.render_to_response(context)
@method_decorator(ratelimit(key=ratelimit_ip,
rate=RATE_LIMITER_RATE,
block=True),
name='dispatch')
class PesquisarSessaoPlenariaView(MultiFormatOutputMixin, FilterView): class PesquisarSessaoPlenariaView(MultiFormatOutputMixin, FilterView):
model = SessaoPlenaria model = SessaoPlenaria
filterset_class = SessaoPlenariaFilterSet filterset_class = SessaoPlenariaFilterSet
@ -4088,6 +4092,7 @@ class PesquisarSessaoPlenariaView(MultiFormatOutputMixin, FilterView):
return r return r
class PesquisarPautaSessaoView(PesquisarSessaoPlenariaView): class PesquisarPautaSessaoView(PesquisarSessaoPlenariaView):
filterset_class = PautaSessaoFilterSet filterset_class = PautaSessaoFilterSet
template_name = 'sessao/pauta_sessao_filter.html' template_name = 'sessao/pauta_sessao_filter.html'
@ -5386,6 +5391,10 @@ class CorrespondenciaCrud(MasterDetailCrud):
return obj return obj
@method_decorator(ratelimit(key=ratelimit_ip,
rate=RATE_LIMITER_RATE,
block=True),
name='dispatch')
class CorrespondenciaEmLoteView(PermissionRequiredMixin, FilterView): class CorrespondenciaEmLoteView(PermissionRequiredMixin, FilterView):
filterset_class = CorrespondenciaEmLoteFilterSet filterset_class = CorrespondenciaEmLoteFilterSet
template_name = 'sessao/em_lote/correspondencia.html' template_name = 'sessao/em_lote/correspondencia.html'

2
sapl/settings.py

@ -315,6 +315,8 @@ MAX_DOC_UPLOAD_SIZE = 150 * 1024 * 1024 # 150MB
MAX_IMAGE_UPLOAD_SIZE = 2 * 1024 * 1024 # 2MB MAX_IMAGE_UPLOAD_SIZE = 2 * 1024 * 1024 # 2MB
DATA_UPLOAD_MAX_MEMORY_SIZE = 10 * 1024 * 1024 # 10MB DATA_UPLOAD_MAX_MEMORY_SIZE = 10 * 1024 * 1024 # 10MB
RATE_LIMITER_RATE = config('RATE_LIMITER_RATE', default='10/m')
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/1.8/topics/i18n/ # https://docs.djangoproject.com/en/1.8/topics/i18n/
LANGUAGE_CODE = 'pt-br' LANGUAGE_CODE = 'pt-br'

5
sapl/static/.well-known/traffic-advice

@ -0,0 +1,5 @@
{
"interval": 300,
"requests-per-second": 5,
"burst": 10
}

15
sapl/urls.py

@ -100,3 +100,18 @@ if settings.DEBUG:
'document_root': settings.MEDIA_ROOT, 'document_root': settings.MEDIA_ROOT,
}), }),
] ]
# Make the rate limiter return 429 (Too Many Requests) instead of 403 (Forbidden Access)
def custom_permission_denied_view(request, exception=None):
from django.http import HttpResponse, HttpResponseForbidden
from ratelimit.exceptions import Ratelimited
if isinstance(exception, Ratelimited):
resp = HttpResponse('Too many requests', status=429)
resp['Retry-After'] = '60'
return resp
return HttpResponseForbidden('Forbidden')
handler403 = custom_permission_denied_view

9
scripts/test_ratelimiter.sh

@ -1,9 +1,14 @@
#!/bin/bash #!/bin/bash
#URL=http://localhost:8000/materia/4379 #URL=http://localhost:8000/materia/4379
URL=http://localhost:8000/norma/pesquisar #URL=http://localhost:8000/norma/pesquisar
#URL=http://localhost/norma/pesquisar #URL=http://localhost/norma/pesquisar
#URL=https://sapl31demo.interlegis.leg.br/docadm/45
#URL=https://sapl.joaopessoa.pb.leg.br/materia/186300
#URL=http://localhost:8000/materia/4379/materiaassunto
#URL=http://localhost:8000/sessao/4984
URL="http://localhost:8000/docadm/pesq-doc-adm?tipo=&o=&numero=&complemento=&ano=&protocolo__numero=&numero_externo=&data_0=&data_1=&interessado=&assunto=&tramitacao=&tramitacaoadministrativo__status=&tramitacaoadministrativo__unidade_tramitacao_destino=&pesquisar=Pesquisar"
for i in $(seq 1 6); do for i in $(seq 1 12); do
curl -sS -o /dev/null -w "req=$i http=%{http_code} time=%{time_total}\n" "$URL" curl -sS -o /dev/null -w "req=$i http=%{http_code} time=%{time_total}\n" "$URL"
done done

Loading…
Cancel
Save