From 8c77e216e4ba2001f8a57520d4c6b650ed591a29 Mon Sep 17 00:00:00 2001 From: Leandro Roberto da Silva Date: Mon, 11 Feb 2019 11:03:15 -0200 Subject: [PATCH] Fix #130 Implementa a API Rest no Sapl (#2393) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * impl api rest full automática * ajuste django-filter * ajusta filter para FileField --- sapl/api/permissions.py | 39 ++++- sapl/api/serializers.py | 6 +- sapl/api/urls.py | 19 ++- sapl/api/views.py | 326 ++++++++++++++++++++++++++++++++++++++-- sapl/rules/apps.py | 17 +-- sapl/rules/map_rules.py | 310 +++++++++++++++++++++----------------- sapl/settings.py | 6 +- 7 files changed, 556 insertions(+), 167 deletions(-) diff --git a/sapl/api/permissions.py b/sapl/api/permissions.py index 5e17d1fe1..b7df6c63a 100644 --- a/sapl/api/permissions.py +++ b/sapl/api/permissions.py @@ -1,7 +1,8 @@ from rest_framework.permissions import DjangoModelPermissions +from sapl.rules.map_rules import rules_patterns_public -class DjangoModelPermissions(DjangoModelPermissions): +class SaplModelPermissions(DjangoModelPermissions): perms_map = { 'GET': ['%(app_label)s.list_%(model_name)s', @@ -10,9 +11,43 @@ class DjangoModelPermissions(DjangoModelPermissions): '%(app_label)s.detail_%(model_name)s'], 'HEAD': ['%(app_label)s.list_%(model_name)s', '%(app_label)s.detail_%(model_name)s'], - 'POST': ['%(app_label)s.list_%(model_name)s'], + 'POST': ['%(app_label)s.add_%(model_name)s'], 'PUT': ['%(app_label)s.change_%(model_name)s'], 'PATCH': ['%(app_label)s.change_%(model_name)s'], 'DELETE': ['%(app_label)s.delete_%(model_name)s'], } + + def has_permission(self, request, view): + if getattr(view, '_ignore_model_permissions', False): + return True + + if hasattr(view, 'get_queryset'): + queryset = view.get_queryset() + else: + queryset = getattr(view, 'queryset', None) + + assert queryset is not None, ( + 'Cannot apply DjangoModelPermissions on a view that ' + 'does not set `.queryset` or have a `.get_queryset()` method.' + ) + + perms = self.get_required_permissions(request.method, queryset.model) + + key = '{}:{}'.format( + queryset.model._meta.app_label, + queryset.model._meta.model_name) + + if key in rules_patterns_public: + perms = set(perms) + perms_publicas = rules_patterns_public[key] + + private_perms = perms - perms_publicas + if not private_perms: + return True + + return ( + request.user and + (request.user.is_authenticated() or not self.authenticated_users_only) and + request.user.has_perms(perms) + ) diff --git a/sapl/api/serializers.py b/sapl/api/serializers.py index 9219bf0e4..6f686fb0f 100644 --- a/sapl/api/serializers.py +++ b/sapl/api/serializers.py @@ -49,14 +49,14 @@ class AutorSerializer(serializers.ModelSerializer): fields = '__all__' -class MateriaLegislativaSerializer(serializers.ModelSerializer): +class MateriaLegislativaOldSerializer(serializers.ModelSerializer): class Meta: model = MateriaLegislativa fields = '__all__' -class SessaoPlenariaSerializer(serializers.ModelSerializer): +class SessaoPlenariaOldSerializer(serializers.ModelSerializer): codReuniao = serializers.SerializerMethodField('get_pk_sessao') codReuniaoPrincipal = serializers.SerializerMethodField('get_pk_sessao') @@ -109,7 +109,7 @@ class SessaoPlenariaSerializer(serializers.ModelSerializer): ) def __init__(self, *args, **kwargs): - super(SessaoPlenariaSerializer, self).__init__(args, kwargs) + super(SessaoPlenariaOldSerializer, self).__init__(args, kwargs) def get_pk_sessao(self, obj): return obj.pk diff --git a/sapl/api/urls.py b/sapl/api/urls.py index 1f19e9d1f..88d78a634 100644 --- a/sapl/api/urls.py +++ b/sapl/api/urls.py @@ -4,7 +4,8 @@ from rest_framework.routers import DefaultRouter from sapl.api.views import (AutoresPossiveisListView, AutoresProvaveisListView, AutorListView, MateriaLegislativaViewSet, - ModelChoiceView, SessaoPlenariaViewSet) + ModelChoiceView, SessaoPlenariaViewSet, + SaplSetViews) from .apps import AppConfig @@ -12,10 +13,18 @@ app_name = AppConfig.name router = DefaultRouter() -router.register(r'materia', MateriaLegislativaViewSet) +router.register(r'materia$', MateriaLegislativaViewSet) router.register(r'sessao-plenaria', SessaoPlenariaViewSet) + + +for app, built_sets in SaplSetViews.items(): + for view_prefix, viewset in built_sets.items(): + router.register(app + '/' + view_prefix, viewset) + + urlpatterns_router = router.urls + urlpatterns_api = [ url(r'^autor/provaveis', @@ -36,5 +45,9 @@ if settings.DEBUG: urlpatterns = [ url(r'^api/', include(urlpatterns_api)), - url(r'^api/', include(urlpatterns_router)) + url(r'^api/', include(urlpatterns_router)), + + # implementar caminho para autenticação + # https://www.django-rest-framework.org/tutorial/4-authentication-and-permissions/ + # url(r'^api/auth/', include('rest_framework.urls', namespace='rest_framework')), ] diff --git a/sapl/api/views.py b/sapl/api/views.py index 336bb23d7..1c1b2ae7f 100644 --- a/sapl/api/views.py +++ b/sapl/api/views.py @@ -1,25 +1,42 @@ + import logging +from django import apps +from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.db.models import Q +from django.db.models import Q +from django.db.models.fields.files import FileField from django.http import Http404 +from django.utils.decorators import classonlymethod +from django.utils.text import capfirst from django.utils.translation import ugettext_lazy as _ +import django_filters from django_filters.rest_framework.backends import DjangoFilterBackend +from django_filters.rest_framework.filterset import FilterSet +from django_filters.utils import resolve_field +from rest_framework import serializers as rest_serializers +from rest_framework.decorators import list_route, detail_route from rest_framework.generics import ListAPIView from rest_framework.mixins import ListModelMixin, RetrieveModelMixin -from rest_framework.permissions import (AllowAny, IsAuthenticated, - IsAuthenticatedOrReadOnly) -from rest_framework.viewsets import GenericViewSet +from rest_framework.permissions import (IsAuthenticated, + IsAuthenticatedOrReadOnly, AllowAny) +from rest_framework.response import Response +from rest_framework.viewsets import GenericViewSet, ModelViewSet from sapl.api.forms import (AutorChoiceFilterSet, AutoresPossiveisFilterSet, AutorSearchForFieldFilterSet) +from sapl.api.permissions import SaplModelPermissions from sapl.api.serializers import (AutorChoiceSerializer, AutorSerializer, ChoiceSerializer, - MateriaLegislativaSerializer, ModelChoiceSerializer, - SessaoPlenariaSerializer) -from sapl.base.models import Autor, TipoAutor -from sapl.materia.models import MateriaLegislativa + SessaoPlenariaOldSerializer, + MateriaLegislativaOldSerializer) +from sapl.base.models import TipoAutor, Autor +from sapl.comissoes.models import Comissao +from sapl.materia.models import MateriaLegislativa, Proposicao +from sapl.parlamentares.models import Parlamentar +from sapl.rules.map_rules import __base__ from sapl.sessao.models import SessaoPlenaria from sapl.utils import SaplGenericRelation @@ -262,7 +279,7 @@ class MateriaLegislativaViewSet(ListModelMixin, GenericViewSet): permission_classes = (IsAuthenticated,) - serializer_class = MateriaLegislativaSerializer + serializer_class = MateriaLegislativaOldSerializer queryset = MateriaLegislativa.objects.all() filter_backends = (DjangoFilterBackend,) filter_fields = ('numero', 'ano', 'tipo', ) @@ -273,7 +290,298 @@ class SessaoPlenariaViewSet(ListModelMixin, GenericViewSet): permission_classes = (AllowAny,) - serializer_class = SessaoPlenariaSerializer + serializer_class = SessaoPlenariaOldSerializer queryset = SessaoPlenaria.objects.all() filter_backends = (DjangoFilterBackend,) filter_fields = ('data_inicio', 'data_fim', 'interativa') + + +class SaplApiViewSetConstrutor(ModelViewSet): + + filter_backends = (DjangoFilterBackend,) + + @classonlymethod + def build_class(cls): + import inspect + from sapl.api import serializers + + # Carrega todas as classes de sapl.api.serializers que possuam + # "Serializer" como Sufixo. + serializers_classes = inspect.getmembers(serializers) + serializers_classes = {i[0]: i[1] for i in filter( + lambda x: x[0].endswith('Serializer'), + serializers_classes + )} + + # Carrega todas as classes de sapl.api.forms que possuam + # "FilterSet" como Sufixo. + from sapl.api import forms + filters_classes = inspect.getmembers(forms) + filters_classes = {i[0]: i[1] for i in filter( + lambda x: x[0].endswith('FilterSet'), + filters_classes + )} + + built_sets = {} + + def build(_model): + object_name = _model._meta.object_name + + # Caso Exista, pega a classe sapl.api.serializers.{model}Serializer + serializer_name = '{model}Serializer'.format(model=object_name) + _serializer_class = serializers_classes.get(serializer_name, None) + + # Caso Exista, pega a classe sapl.api.forms.{model}FilterSet + filter_name = '{model}FilterSet'.format(model=object_name) + _filter_class = filters_classes.get(filter_name, None) + + def create_class(): + # Define uma classe padrão para serializer caso não tenha sido + # criada a classe sapl.api.serializers.{model}Serializer + class SaplSerializer(rest_serializers.ModelSerializer): + class Meta: + model = _model + fields = '__all__' + + # Define uma classe padrão para filtro caso não tenha sido + # criada a classe sapl.api.forms.{model}FilterSet + class SaplFilterSet(FilterSet): + class Meta: + model = _model + fields = '__all__' + filter_overrides = { + FileField: { + 'filter_class': django_filters.CharFilter, + 'extra': lambda f: { + 'lookup_expr': 'exact', + }, + }, + } + + @classmethod + def filter_for_field(cls, f, name, lookup_expr='exact'): + # Redefine método estático para ignorar filtro para + # fields que não possuam lookup_expr informado + f, lookup_type = resolve_field(f, lookup_expr) + + default = { + 'field_name': name, + 'label': capfirst(f.verbose_name), + 'lookup_expr': lookup_expr + } + + filter_class, params = cls.filter_for_lookup( + f, lookup_type) + default.update(params) + if filter_class is not None: + return filter_class(**default) + return None + + # Define uma classe padrão ModelViewSet de DRF + class ModelSaplViewSet(cls): + queryset = _model.objects.all() + + # Utiliza o filtro customizado pela classe + # sapl.api.forms.{model}FilterSet + # ou utiliza o trivial SaplFilterSet definido acima + filter_class = _filter_class \ + if _filter_class else SaplFilterSet + + # Utiliza o serializer customizado pela classe + # sapl.api.serializers.{model}Serializer + # ou utiliza o trivial SaplSerializer definido acima + serializer_class = _serializer_class \ + if _serializer_class else SaplSerializer + + return ModelSaplViewSet + + viewset = create_class() + viewset.__name__ = '%sModelSaplViewSet' % _model.__name__ + return viewset + + apps_sapl = [apps.apps.get_app_config( + n[5:]) for n in settings.SAPL_APPS] + for app in apps_sapl: + built_sets[app.label] = {} + for model in app.get_models(): + built_sets[app.label][model._meta.model_name] = build(model) + + return built_sets + + +""" +1. Constroi uma rest_framework.viewsets.ModelViewSet para + todos os models de todas as apps do sapl +2. Define DjangoFilterBackend como ferramenta de filtro dos campos +3. Define Serializer como a seguir: + 3.1 - Define um Serializer genérico para cada módel + 3.2 - Recupera Serializer customizado em sapl.api.serializers + 3.3 - Para todo model é opcional a existência de + sapl.api.serializers.{model}Serializer. + Caso não seja definido um Serializer customizado, utiliza-se o trivial +4. Define um FilterSet como a seguir: + 4.1 - Define um FilterSet genérico para cada módel + 4.2 - Recupera FilterSet customizado em sapl.api.forms + 4.3 - Para todo model é opcional a existência de + sapl.api.forms.{model}FilterSet. + Caso não seja definido um FilterSet customizado, utiliza-se o trivial + 4.4 - todos os campos que aceitam lookup 'exact' + podem ser filtrados por default + +5. SaplApiViewSetConstrutor não cria padrões e/ou exige conhecimento alem dos + exigidos pela DRF. + +6. As rotas são criadas seguindo nome da app e nome do model + http://localhost:9000/api/{applabel}/{model_name}/ + e seguem as variações definidas em: + https://www.django-rest-framework.org/api-guide/routers/#defaultrouter + +7. Todas as viewsets construídas por SaplApiViewSetConstrutor e suas rotas + (paginate list, detail, edit, create, delete) + bem como testes em ambiente de desenvolvimento podem ser conferidas em: + http://localhost:9000/api/ + desde que settings.DEBUG=True + +**SaplSetViews** é um dict de dicts de models conforme: + { + ... + + 'audiencia': { + 'tipoaudienciapublica': TipoAudienciaPublicaViewSet, + 'audienciapublica': AudienciaPublicaViewSet, + 'anexoaudienciapublica': AnexoAudienciaPublicaViewSet + + ... + + }, + + ... + + 'base': { + 'casalegislativa': CasaLegislativaViewSet, + 'appconfig': AppConfigViewSet, + + ... + + } + + ... + + } +""" + +SaplSetViews = SaplApiViewSetConstrutor.build_class() + +# Toda Classe construida acima, pode ser redefinida e aplicado quaisquer +# das possibilidades para uma classe normal criada a partir de +# rest_framework.viewsets.ModelViewSet conforme exemplo para a classe autor + +# ALGUNS EXEMPLOS + + +class _AutorViewSet(SaplSetViews['base']['autor']): + # OBS: esta classe é um exemplo e não contempla uma customização completa. + """ + Neste exemplo de customização do que foi criado em + SaplApiViewSetConstrutor além do ofertado por + rest_framework.viewsets.ModelViewSet, dentre outras customizações + possíveis, foi adicionado mais duas rotas, que neste exemplo seria: + + padrão de ModelViewSet + http://localhost:9000/api/base/autor/ POST - create + http://localhost:9000/api/base/autor/ GET - list + http://localhost:9000/api/base/autor/{pk}/ GET - detail + http://localhost:9000/api/base/autor/{pk}/ PUT - update + http://localhost:9000/api/base/autor/{pk}/ DELETE - destroy + + rotas desta classe local: + http://localhost:9000/api/base/autor/parlamentares + devolve apenas autores que são parlamentares + http://localhost:9000/api/base/autor/comissoes + devolve apenas autores que são comissões + + estas mesmas listas oferecidas conforme acima, poderiam ser pesquisadas + sabendo a informação que propicia seu filtro através, pois do django_filter + + no caso o ambiente de desenvolvimento no momento da escrita desse how-to: + http://localhost:9000/api/base/autor/?content_type=26 para parlamentares + http://localhost:9000/api/base/autor/?content_type=37 para comissoes + + diferenças como estas podem ser crusciais para uso da api + neste caso em específico, content_types não são públicos e não possuem + clareza + isso: + http://localhost:9000/api/base/autor/parlamentares + faz o mesmo que isso: + http://localhost:9000/api/base/autor/?content_type=26 + mas o primeiro é indiscutivelmente de melhor compreensão. + + """ + + def list_for_content_type(self, content_type): + qs = self.get_queryset() + qs = qs.filter(content_type=content_type) + + page = self.paginate_queryset(qs) + if page is not None: + serializer = self.serializer_class(page, many=True) + return self.get_paginated_response(serializer.data) + + serializer = self.get_serializer(page, many=True) + return Response(serializer.data) + + @list_route() + def parlamentares(self, request, *args, **kwargs): + # list /api/base/autor/parlamentares + content_type = ContentType.objects.get_for_model(Parlamentar) + return self.list_for_content_type(content_type) + + @list_route() + def comissoes(self, request, *args, **kwargs): + # list /api/base/autor/comissoes + content_type = ContentType.objects.get_for_model(Comissao) + return self.list_for_content_type(content_type) + # Com isso redefinimos AutorViewSet com mais duas rotas + # além das rotas padrão + + +class _ParlamentarViewSet(SaplSetViews['parlamentares']['parlamentar']): + + @detail_route() + def proposicoes(self, request, *args, **kwargs): + # /api/parlamentares/parlamentar/{pk}/proposicoes/ + # recupera proposições enviadas e incorporadas do parlamentar + # deve coincidir com + # /parlamentar/{pk}/proposicao + content_type = ContentType.objects.get_for_model(Parlamentar) + + qs = Proposicao.objects.filter( + data_envio__isnull=False, + data_recebimento__isnull=False, + cancelado=False, + autor__object_id=kwargs['pk'], + autor__content_type=content_type + ) + + page = self.paginate_queryset(qs) + if page is not None: + serializer = SaplSetViews[ + 'materia']['proposicao'].serializer_class(page, many=True) + return self.get_paginated_response(serializer.data) + + serializer = self.get_serializer(page, many=True) + return Response(serializer.data) + + +class _ProposicaoViewSet(SaplSetViews['materia']['proposicao']): + def get_queryset(self): + qs = super().get_queryset() + if self.request.user.is_anonymous(): + return qs.none() + qs = qs.filter(autor__user=self.request.user) + return qs + + +SaplSetViews['base']['autor'] = _AutorViewSet +SaplSetViews['materia']['proposicao'] = _ProposicaoViewSet +SaplSetViews['parlamentares']['parlamentar'] = _ParlamentarViewSet diff --git a/sapl/rules/apps.py b/sapl/rules/apps.py index 522cced6a..c0dc19fd8 100644 --- a/sapl/rules/apps.py +++ b/sapl/rules/apps.py @@ -1,8 +1,7 @@ from builtins import LookupError - -import django import logging +import django from django.apps import apps from django.contrib.auth import get_user_model from django.contrib.auth.management import _get_all_permissions @@ -120,12 +119,12 @@ def create_proxy_permissions( for perm in perms: if len(perm.name) > permission_name_max_length: logger.error("The permission name %s of %s.%s " - "is longer than %s characters" % ( - perm.name, - perm.content_type.app_label, - perm.content_type.model, - permission_name_max_length, - )) + "is longer than %s characters" % ( + perm.name, + perm.content_type.app_label, + perm.content_type.model, + permission_name_max_length, + )) raise exceptions.ValidationError( 'The permission name %s of %s.%s ' 'is longer than %s characters' % ( @@ -177,7 +176,7 @@ def get_rules(): try: logger.info("Tentando associar grupos.") print(' ', group_name) - for model, perms in rules_list: + for model, perms, perms_publicas in rules_list: self.associar(group, model, perms) except Exception as e: logger.error(str(e)) diff --git a/sapl/rules/map_rules.py b/sapl/rules/map_rules.py index f9b63fd4b..13c89a547 100644 --- a/sapl/rules/map_rules.py +++ b/sapl/rules/map_rules.py @@ -50,23 +50,27 @@ from sapl.sessao import models as sessao __base__ = [RP_LIST, RP_DETAIL, RP_ADD, RP_CHANGE, RP_DELETE] __listdetailchange__ = [RP_LIST, RP_DETAIL, RP_CHANGE] +__perms_publicas__ = {RP_LIST, RP_DETAIL} + rules_group_administrativo = { 'group': SAPL_GROUP_ADMINISTRATIVO, 'rules': [ - (materia.MateriaLegislativa, ['can_access_impressos']), - (protocoloadm.DocumentoAdministrativo, __base__), - (protocoloadm.DocumentoAcessorioAdministrativo, __base__), - (protocoloadm.TramitacaoAdministrativo, __base__), + (materia.MateriaLegislativa, [ + 'can_access_impressos'], __perms_publicas__), + # TODO: tratar em sapl.api a questão de ostencivo e restritivo + (protocoloadm.DocumentoAdministrativo, __base__, set()), + (protocoloadm.DocumentoAcessorioAdministrativo, __base__, set()), + (protocoloadm.TramitacaoAdministrativo, __base__, set()), ] } rules_group_audiencia = { 'group': SAPL_GROUP_GERAL, 'rules': [ - (audiencia.AudienciaPublica, __base__), - (audiencia.TipoAudienciaPublica, __base__), - (audiencia.AnexoAudienciaPublica, __base__), + (audiencia.AudienciaPublica, __base__, __perms_publicas__), + (audiencia.TipoAudienciaPublica, __base__, __perms_publicas__), + (audiencia.AnexoAudienciaPublica, __base__, __perms_publicas__), ] } @@ -75,49 +79,52 @@ rules_group_protocolo = { 'group': SAPL_GROUP_PROTOCOLO, 'rules': [ (protocoloadm.Protocolo, __base__ + [ - 'action_anular_protocolo']), + 'action_anular_protocolo'], set()), (protocoloadm.DocumentoAdministrativo, - [RP_ADD] + __listdetailchange__), - (protocoloadm.DocumentoAcessorioAdministrativo, __listdetailchange__), + [RP_ADD] + __listdetailchange__, set()), + (protocoloadm.DocumentoAcessorioAdministrativo, __listdetailchange__, set()), - (materia.MateriaLegislativa, __listdetailchange__), - (materia.MateriaLegislativa, ['can_access_impressos']), - (materia.DocumentoAcessorio, __listdetailchange__), - (materia.Anexada, __base__), - (materia.Autoria, __base__), + (materia.MateriaLegislativa, __listdetailchange__, __perms_publicas__), + (materia.MateriaLegislativa, [ + 'can_access_impressos'], __perms_publicas__), + (materia.DocumentoAcessorio, __listdetailchange__, __perms_publicas__), + (materia.Anexada, __base__, __perms_publicas__), + (materia.Autoria, __base__, __perms_publicas__), (materia.Proposicao, ['detail_proposicao_enviada', 'detail_proposicao_devolvida', - 'detail_proposicao_incorporada']), - (compilacao.TextoArticulado, ['view_restricted_textoarticulado']) + 'detail_proposicao_incorporada'], set()), # TODO: tratar em sapl.api questão de que proposições incorporadas serem públicas + (compilacao.TextoArticulado, [ + 'view_restricted_textoarticulado'], __perms_publicas__) ] } rules_group_comissoes = { 'group': SAPL_GROUP_COMISSOES, 'rules': [ - (comissoes.Comissao, __base__), - (comissoes.Composicao, __base__), - (comissoes.Participacao, __base__), - (materia.Relatoria, __base__), - (comissoes.Reuniao, __base__), - (comissoes.DocumentoAcessorio, __base__), + (comissoes.Comissao, __base__, __perms_publicas__), + (comissoes.Composicao, __base__, __perms_publicas__), + (comissoes.Participacao, __base__, __perms_publicas__), + (materia.Relatoria, __base__, __perms_publicas__), + (comissoes.Reuniao, __base__, __perms_publicas__), + (comissoes.DocumentoAcessorio, __base__, __perms_publicas__), ] } rules_group_materia = { 'group': SAPL_GROUP_MATERIA, 'rules': [ - (materia.Anexada, __base__), - (materia.Autoria, __base__), - (materia.DespachoInicial, __base__), - (materia.DocumentoAcessorio, __base__), - - (materia.MateriaLegislativa, __base__ + ['can_access_impressos']), - (materia.Numeracao, __base__), - (materia.Tramitacao, __base__), - (norma.LegislacaoCitada, __base__), - (norma.AutoriaNorma, __base__), + (materia.Anexada, __base__, __perms_publicas__), + (materia.Autoria, __base__, __perms_publicas__), + (materia.DespachoInicial, __base__, __perms_publicas__), + (materia.DocumentoAcessorio, __base__, __perms_publicas__), + + (materia.MateriaLegislativa, __base__ + + ['can_access_impressos'], __perms_publicas__), + (materia.Numeracao, __base__, __perms_publicas__), + (materia.Tramitacao, __base__, __perms_publicas__), + (norma.LegislacaoCitada, __base__, __perms_publicas__), + (norma.AutoriaNorma, __base__, __perms_publicas__), (compilacao.Dispositivo, __base__ + [ 'change_dispositivo_edicao_dinamica', @@ -128,70 +135,70 @@ rules_group_materia = { # uma matéria original. # Fazer esse registro de compilação ofereceria # um autografo eletrônico pronto para ser convertido em Norma. - ]) + ], __perms_publicas__) ] } rules_group_norma = { 'group': SAPL_GROUP_NORMA, 'rules': [ - (norma.NormaJuridica, __base__), - (norma.NormaRelacionada, __base__), - (norma.AnexoNormaJuridica, __base__), - (norma.AutoriaNorma, __base__), - (norma.NormaEstatisticas, __base__), + (norma.NormaJuridica, __base__, __perms_publicas__), + (norma.NormaRelacionada, __base__, __perms_publicas__), + (norma.AnexoNormaJuridica, __base__, __perms_publicas__), + (norma.AutoriaNorma, __base__, __perms_publicas__), + (norma.NormaEstatisticas, __base__, __perms_publicas__), # Publicacao está com permissão apenas para norma e não para matéria # e proposições apenas por análise do contexto, não é uma limitação # da ferramenta. - (compilacao.Publicacao, __base__), - (compilacao.Vide, __base__), - (compilacao.Nota, __base__), + (compilacao.Publicacao, __base__, __perms_publicas__), + (compilacao.Vide, __base__, __perms_publicas__), + (compilacao.Nota, __base__, __perms_publicas__), (compilacao.Dispositivo, __base__ + [ 'view_dispositivo_notificacoes', 'change_dispositivo_edicao_dinamica', 'change_dispositivo_edicao_avancada', 'change_dispositivo_registros_compilacao', 'change_dispositivo_de_vigencia_global' - ]) + ], __perms_publicas__) ] } rules_group_sessao = { 'group': SAPL_GROUP_SESSAO, 'rules': [ - (sessao.SessaoPlenaria, __base__), - (sessao.SessaoPlenariaPresenca, __base__), - (sessao.ExpedienteMateria, __base__), - (sessao.OcorrenciaSessao, __base__), - (sessao.IntegranteMesa, __base__), - (sessao.ExpedienteSessao, __base__), - (sessao.Orador, __base__), - (sessao.OradorExpediente, __base__), - (sessao.OrdemDia, __base__), - (sessao.PresencaOrdemDia, __base__), - (sessao.RegistroVotacao, __base__), - (sessao.VotoParlamentar, __base__), - (sessao.JustificativaAusencia, __base__), - (sessao.RetiradaPauta, __base__) + (sessao.SessaoPlenaria, __base__, __perms_publicas__), + (sessao.SessaoPlenariaPresenca, __base__, __perms_publicas__), + (sessao.ExpedienteMateria, __base__, __perms_publicas__), + (sessao.OcorrenciaSessao, __base__, __perms_publicas__), + (sessao.IntegranteMesa, __base__, __perms_publicas__), + (sessao.ExpedienteSessao, __base__, __perms_publicas__), + (sessao.Orador, __base__, __perms_publicas__), + (sessao.OradorExpediente, __base__, __perms_publicas__), + (sessao.OrdemDia, __base__, __perms_publicas__), + (sessao.PresencaOrdemDia, __base__, __perms_publicas__), + (sessao.RegistroVotacao, __base__, __perms_publicas__), + (sessao.VotoParlamentar, __base__, __perms_publicas__), + (sessao.JustificativaAusencia, __base__, __perms_publicas__), + (sessao.RetiradaPauta, __base__, __perms_publicas__), ] } rules_group_painel = { 'group': SAPL_GROUP_PAINEL, 'rules': [ - (painel.Painel, __base__), - (painel.Cronometro, __base__), + (painel.Painel, __base__, __perms_publicas__), + (painel.Cronometro, __base__, __perms_publicas__), ] } rules_group_autor = { 'group': SAPL_GROUP_AUTOR, 'rules': [ - (materia.Proposicao, __base__), + (materia.Proposicao, __base__, set()), (compilacao.Dispositivo, __base__ + [ 'change_your_dispositivo_edicao_dinamica', - ]) + ], __perms_publicas__) ] } @@ -203,7 +210,7 @@ rules_group_parlamentar = { rules_group_votante = { 'group': SAPL_GROUP_VOTANTE, 'rules': [ - (parlamentares.Votante, ['can_vote']) + (parlamentares.Votante, ['can_vote'], set()) ] } @@ -213,89 +220,94 @@ rules_group_geral = { (base.AppConfig, __base__ + [ 'menu_sistemas', 'view_tabelas_auxiliares' - ]), - - (base.CasaLegislativa, __listdetailchange__ + [RP_ADD]), - (base.TipoAutor, __base__), - (base.Autor, __base__), - - (protocoloadm.StatusTramitacaoAdministrativo, __base__), - (protocoloadm.TipoDocumentoAdministrativo, __base__), - - (comissoes.CargoComissao, __base__), - (comissoes.TipoComissao, __base__), - (comissoes.Periodo, __base__), - - (materia.AssuntoMateria, __base__), # não há implementação - (materia.MateriaAssunto, __base__), # não há implementação - (materia.MateriaLegislativa, ['can_access_impressos']), - (materia.TipoProposicao, __base__), - (materia.TipoMateriaLegislativa, __base__), - (materia.RegimeTramitacao, __base__), - (materia.Origem, __base__), - (materia.TipoDocumento, __base__), - (materia.Orgao, __base__), - (materia.TipoFimRelatoria, __base__), - (materia.Parecer, __base__), - (materia.StatusTramitacao, __base__), - (materia.UnidadeTramitacao, __base__), - - (norma.AssuntoNorma, __base__), - (norma.TipoNormaJuridica, __base__), - (norma.TipoVinculoNormaJuridica, __base__), - (norma.NormaEstatisticas, __base__), - - (parlamentares.Legislatura, __base__), - (parlamentares.SessaoLegislativa, __base__), - (parlamentares.Coligacao, __base__), - (parlamentares.ComposicaoColigacao, __base__), - (parlamentares.Partido, __base__), - (parlamentares.NivelInstrucao, __base__), - (parlamentares.SituacaoMilitar, __base__), - (parlamentares.Parlamentar, __base__), - (parlamentares.TipoDependente, __base__), - (parlamentares.Dependente, __base__), - (parlamentares.Filiacao, __base__), - (parlamentares.TipoAfastamento, __base__), - (parlamentares.Mandato, __base__), - (parlamentares.CargoMesa, __base__), - (parlamentares.ComposicaoMesa, __base__), - (parlamentares.Frente, __base__), - (parlamentares.Votante, __base__), - - (sessao.CargoBancada, __base__), - (sessao.Bancada, __base__), - (sessao.TipoSessaoPlenaria, __base__), - (sessao.TipoResultadoVotacao, __base__), - (sessao.TipoExpediente, __base__), - (sessao.TipoJustificativa, __base__), - (sessao.JustificativaAusencia, __base__), - (sessao.Bloco, __base__), - (sessao.ResumoOrdenacao, __base__), - (sessao.TipoRetiradaPauta, __base__), - - (lexml.LexmlProvedor, __base__), - (lexml.LexmlPublicador, __base__), - - (compilacao.VeiculoPublicacao, __base__), - (compilacao.TipoTextoArticulado, __base__), - (compilacao.TipoNota, __base__), - (compilacao.TipoVide, __base__), - (compilacao.TipoPublicacao, __base__), + ], set()), + + (base.CasaLegislativa, __listdetailchange__ + + [RP_ADD], __perms_publicas__), + (base.TipoAutor, __base__, __perms_publicas__), + (base.Autor, __base__, __perms_publicas__), + + (protocoloadm.StatusTramitacaoAdministrativo, __base__, set()), + (protocoloadm.TipoDocumentoAdministrativo, __base__, set()), + + (comissoes.CargoComissao, __base__, __perms_publicas__), + (comissoes.TipoComissao, __base__, __perms_publicas__), + (comissoes.Periodo, __base__, __perms_publicas__), + + (materia.AssuntoMateria, __base__, + __perms_publicas__), # não há implementação + (materia.MateriaAssunto, __base__, + __perms_publicas__), # não há implementação + (materia.MateriaLegislativa, [ + 'can_access_impressos'], __perms_publicas__), + (materia.TipoProposicao, __base__, __perms_publicas__), + (materia.TipoMateriaLegislativa, __base__, __perms_publicas__), + (materia.RegimeTramitacao, __base__, __perms_publicas__), + (materia.Origem, __base__, __perms_publicas__), + (materia.TipoDocumento, __base__, __perms_publicas__), + (materia.Orgao, __base__, __perms_publicas__), + (materia.TipoFimRelatoria, __base__, __perms_publicas__), + (materia.Parecer, __base__, __perms_publicas__), + (materia.StatusTramitacao, __base__, __perms_publicas__), + (materia.UnidadeTramitacao, __base__, __perms_publicas__), + + + (norma.AssuntoNorma, __base__, __perms_publicas__), + (norma.TipoNormaJuridica, __base__, __perms_publicas__), + (norma.TipoVinculoNormaJuridica, __base__, __perms_publicas__), + (norma.NormaEstatisticas, __base__, __perms_publicas__), + + (parlamentares.Legislatura, __base__, __perms_publicas__), + (parlamentares.SessaoLegislativa, __base__, __perms_publicas__), + (parlamentares.Coligacao, __base__, __perms_publicas__), + (parlamentares.ComposicaoColigacao, __base__, __perms_publicas__), + (parlamentares.Partido, __base__, __perms_publicas__), + (parlamentares.NivelInstrucao, __base__, __perms_publicas__), + (parlamentares.SituacaoMilitar, __base__, __perms_publicas__), + (parlamentares.Parlamentar, __base__, __perms_publicas__), + (parlamentares.TipoDependente, __base__, __perms_publicas__), + (parlamentares.Dependente, __base__, __perms_publicas__), + (parlamentares.Filiacao, __base__, __perms_publicas__), + (parlamentares.TipoAfastamento, __base__, __perms_publicas__), + (parlamentares.Mandato, __base__, __perms_publicas__), + (parlamentares.CargoMesa, __base__, __perms_publicas__), + (parlamentares.ComposicaoMesa, __base__, __perms_publicas__), + (parlamentares.Frente, __base__, __perms_publicas__), + (parlamentares.Votante, __base__, __perms_publicas__), + + (sessao.CargoBancada, __base__, __perms_publicas__), + (sessao.Bancada, __base__, __perms_publicas__), + (sessao.TipoSessaoPlenaria, __base__, __perms_publicas__), + (sessao.TipoResultadoVotacao, __base__, __perms_publicas__), + (sessao.TipoExpediente, __base__, __perms_publicas__), + (sessao.TipoJustificativa, __base__, __perms_publicas__), + (sessao.JustificativaAusencia, __base__, __perms_publicas__), + (sessao.Bloco, __base__, __perms_publicas__), + (sessao.ResumoOrdenacao, __base__, __perms_publicas__), + (sessao.TipoRetiradaPauta, __base__, __perms_publicas__), + + (lexml.LexmlProvedor, __base__, set()), + (lexml.LexmlPublicador, __base__, set()), + + (compilacao.VeiculoPublicacao, __base__, __perms_publicas__), + (compilacao.TipoTextoArticulado, __base__, __perms_publicas__), + (compilacao.TipoNota, __base__, __perms_publicas__), + (compilacao.TipoVide, __base__, __perms_publicas__), + (compilacao.TipoPublicacao, __base__, __perms_publicas__), # este model é um espelho do model integrado e sua edição pode # confundir Autores, operadores de matéria e/ou norma. # Por isso está adicionado apenas para o operador geral (compilacao.TextoArticulado, - __base__ + ['lock_unlock_textoarticulado']), + __base__ + ['lock_unlock_textoarticulado'], set()), # estes tres models são complexos e a principio apenas o admin tem perm - (compilacao.TipoDispositivo, []), - (compilacao.TipoDispositivoRelationship, []), - (compilacao.PerfilEstruturalTextoArticulado, []), + (compilacao.TipoDispositivo, [], set()), + (compilacao.TipoDispositivoRelationship, [], set()), + (compilacao.PerfilEstruturalTextoArticulado, [], set()), - (audiencia.AudienciaPublica, __base__), - (audiencia.TipoAudienciaPublica, __base__), + (audiencia.AudienciaPublica, __base__, __perms_publicas__), + (audiencia.TipoAudienciaPublica, __base__, __perms_publicas__), @@ -309,8 +321,8 @@ rules_group_geral = { rules_group_anonymous = { 'group': SAPL_GROUP_ANONYMOUS, 'rules': [ - (materia.AcompanhamentoMateria, [RP_ADD, RP_DELETE]), - (protocoloadm.AcompanhamentoDocumento, [RP_ADD, RP_DELETE]), + (materia.AcompanhamentoMateria, [RP_ADD, RP_DELETE], set()), + (protocoloadm.AcompanhamentoDocumento, [RP_ADD, RP_DELETE], set()), ] } @@ -348,3 +360,23 @@ rules_patterns = [ rules_group_anonymous, # anotação para validação do teste de rules rules_group_login_social # TODO não implementado ] + + +rules_patterns_public = {} + + +def _get_registration_key(model): + return '%s:%s' % (model._meta.app_label, model._meta.model_name) + + +for rules_group in rules_patterns: + for rules in rules_group['rules']: + key = _get_registration_key(rules[0]) + if key not in rules_patterns_public: + rules_patterns_public[key] = set() + + r = set(map(lambda x, m=rules[0]: '{}{}{}'.format( + m._meta.app_label, + x, + m._meta.model_name), rules[2])) + rules_patterns_public[key] = rules_patterns_public[key] | r diff --git a/sapl/settings.py b/sapl/settings.py index 41d8ba019..a7229a29a 100755 --- a/sapl/settings.py +++ b/sapl/settings.py @@ -157,9 +157,11 @@ REST_FRAMEWORK = { "DEFAULT_PARSER_CLASSES": ( "rest_framework.parsers.JSONParser", ), + 'DEFAULT_RENDERER_CLASSES': ( + 'rest_framework.renderers.JSONRenderer', + ), "DEFAULT_PERMISSION_CLASSES": ( - "rest_framework.permissions.IsAuthenticated", - "sapl.api.permissions.DjangoModelPermissions", + "sapl.api.permissions.SaplModelPermissions", ), "DEFAULT_AUTHENTICATION_CLASSES": ( "rest_framework.authentication.SessionAuthentication",