Browse Source

Add teste de perm as urls e fixa bug em CrudAux

O teste adicionado faz login com todos os perfis criados para testar os
grupos triviais. Segundo o mapa de usuários e prefixos de urls
permitidas, espera-se que não haja, ou haja, redirecionamento para login
mediante as configurações do mapa de usuários.

O mapa que serve tanto para o novo teste de conexão, quanto para o de
validação de prefixos de urls, é uma mapa base e não é absoluto.
Se algo novo for criado ou exitir uma alteração que não se encaixe no
mapa, o teste quebrará, portanto, caberá ao autor da inovação decidir
se, para passar pelo teste o mapa deve ser mudado, ou se algo deve ser
feito em sua implementação para se adequar ao mapa.
pull/726/head
LeandroRoberto 8 years ago
parent
commit
48fb3962a7
  1. 34
      sapl/crud/base.py
  2. 274
      sapl/test_urls.py
  3. 3
      scripts/lista_urls.py

34
sapl/crud/base.py

@ -13,8 +13,8 @@ from django.db import models
from django.http.response import Http404 from django.http.response import Http404
from django.utils.decorators import classonlymethod from django.utils.decorators import classonlymethod
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import string_concat from django.utils.translation import string_concat
from django.utils.translation import ugettext_lazy as _
from django.views.generic import (CreateView, DeleteView, DetailView, ListView, from django.views.generic import (CreateView, DeleteView, DetailView, ListView,
UpdateView) UpdateView)
from django.views.generic.base import ContextMixin from django.views.generic.base import ContextMixin
@ -23,6 +23,7 @@ from django.views.generic.list import MultipleObjectMixin
from sapl.crispy_layout_mixin import CrispyLayoutFormMixin, get_field_display from sapl.crispy_layout_mixin import CrispyLayoutFormMixin, get_field_display
from sapl.utils import normalize from sapl.utils import normalize
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
ACTION_LIST, ACTION_CREATE, ACTION_DETAIL, ACTION_UPDATE, ACTION_DELETE = \ ACTION_LIST, ACTION_CREATE, ACTION_DETAIL, ACTION_UPDATE, ACTION_DELETE = \
@ -163,7 +164,7 @@ class PermissionRequiredForAppCrudMixin(PermissionRequiredMixin):
apps = self.app_label apps = self.app_label
if isinstance(apps, str): if isinstance(apps, str):
apps = apps, apps = apps,
# papp_label vazio dará acesso geral # app_label vazio dará acesso geral
for app in apps: for app in apps:
if not self.request.user.has_module_perms(app): if not self.request.user.has_module_perms(app):
return False return False
@ -870,13 +871,36 @@ class Crud:
class CrudAux(Crud): class CrudAux(Crud):
"""
Checa permissão para ver qualquer dado de tabela auxiliar
a permissão base.view_tabelas_auxiliares está definada class Meta
do model sapl.base.models.AppConfig que, naturalmente é um arquivo
de configuração geral e pode ser acessado através das Tabelas
Auxiliares... Com isso o script de geração de perfis acaba que por
criar essa permissão apenas para o perfil Operador Geral.
"""
permission_required = ('base.view_tabelas_auxiliares',)
class BaseMixin(Crud.BaseMixin): class BaseMixin(Crud.BaseMixin):
permission_required = ('base.view_tabelas_auxiliares',)
subnav_template_name = None subnav_template_name = None
def __init__(self, **kwargs):
super().__init__(**kwargs)
"""
Mantem as permissões individuais geradas pelo Crud através do
Modelo e adiciona a obrigatoriedade de permissão para view
tabelas auxiliares.
"""
self.permission_required = self.permission_required + \
self.crud.permission_required
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
"""Força o template filter subnav em base/templatetags/menus.py
a abrir um yaml diferente do padrão.
Se o valor de subnav_template_name é nulo faz o filter subnav
não abrir o padrão e nem um outro arquivo.
"""
context['subnav_template_name'] = self.subnav_template_name context['subnav_template_name'] = self.subnav_template_name
return context return context
@ -886,8 +910,8 @@ class CrudAux(Crud):
ModelCrud = Crud.build( ModelCrud = Crud.build(
_model, _help_path, _model_set, list_field_names) _model, _help_path, _model_set, list_field_names)
class ModelCrudAux(ModelCrud): class ModelCrudAux(CrudAux, ModelCrud):
BaseMixin = CrudAux.BaseMixin pass
return ModelCrudAux return ModelCrudAux

274
sapl/test_urls.py

@ -1,11 +1,11 @@
import pytest
from django.apps import apps from django.apps import apps
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.management import _get_all_permissions from django.contrib.auth.management import _get_all_permissions
from django.contrib.auth.models import Permission from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import string_concat from django.utils.translation import string_concat
from django.utils.translation import ugettext_lazy as _
import pytest
from sapl.crud.base import PermissionRequiredForAppCrudMixin from sapl.crud.base import PermissionRequiredForAppCrudMixin
from scripts.inicializa_grupos_autorizacoes import cria_grupos_permissoes from scripts.inicializa_grupos_autorizacoes import cria_grupos_permissoes
@ -13,6 +13,7 @@ from scripts.lista_urls import lista_urls
from .settings import SAPL_APPS from .settings import SAPL_APPS
pytestmark = pytest.mark.django_db pytestmark = pytest.mark.django_db
sapl_appconfs = [apps.get_app_config(n[5:]) for n in SAPL_APPS] sapl_appconfs = [apps.get_app_config(n[5:]) for n in SAPL_APPS]
@ -111,8 +112,8 @@ def test_crudaux_formato_inicio_urls_associadas(url_item):
@pytest.mark.parametrize('url_item', _lista_urls) @pytest.mark.parametrize('url_item', _lista_urls)
def test_crudaux_list_do_crud_esta_na_pagina_sistema(url_item, admin_client): def test_crudaux_list_do_crud_esta_na_pagina_sistema(url_item, admin_client):
# Verifica se um crud é do tipo CrudAux, se sim, sua url deve começar # Verifica a url é de um CrudAux e, se for, testa se está
# com /sistema/ # na página Tabelas Auxiliares
key, url, var, app_name = url_item key, url, var, app_name = url_item
url = '/' + (url % {v: 1 for v in var}) url = '/' + (url % {v: 1 for v in var})
@ -144,67 +145,104 @@ def test_crudaux_list_do_crud_esta_na_pagina_sistema(url_item, admin_client):
Se encontra em %s.urls Se encontra em %s.urls
""" % (url, app_name) """ % (url, app_name)
apps_url_patterns_prefixs_and_users = {
@pytest.mark.parametrize('url_item', _lista_urls) 'base': {
def test_urlpatterns(url_item, admin_client): 'users': {'operador_geral': ['/sistema']},
'prefixs': [
key, url, var, app_name = url_item
url = '/' + (url % {v: 1 for v in var})
app_name = app_name[5:]
apps_url_patterns_prefixs = {
'base': [
'/sistema', '/sistema',
'/login', '/login',
'/logout' '/logout'
], ]},
'comissoes': [ 'comissoes': {
'users': {'operador_geral': ['/sistema', '/comissao'],
'operador_comissoes': ['/comissao']},
'prefixs': [
'/comissao', '/comissao',
'/sistema' '/sistema'
], ]},
'compilacao': [ 'compilacao': {
'prefixs': [
'/ta', '/ta',
], ]},
'lexml': [ 'lexml': {
'prefixs': [
'/lexml', '/lexml',
'/sistema' '/sistema'
], ]},
'materia': [ 'materia': {
'users': {'operador_geral': ['/sistema', '/materia'],
'operador_autor': ['/proposicao'],
'operador_materia': ['/materia']},
'prefixs': [
'/materia', '/materia',
'/proposicao', '/proposicao',
'/sistema' '/sistema'
], ]},
'norma': [ 'norma': {
'users': {'operador_geral': ['/sistema', '/norma'],
'operador_norma': ['/norma']},
'prefixs': [
'/norma', '/norma',
'/sistema' '/sistema'
], ]},
'painel': [ 'painel': {
'users': {'operador_geral': ['/sistema', '/painel'],
'operador_painel': ['/painel']},
'prefixs': [
'/painel', '/painel',
'/sistema' '/sistema'
], ]},
'parlamentares': [ 'parlamentares': {
'users': {'operador_geral': ['/sistema',
'/mesa-diretora',
'/parlamentar']},
'prefixs': [
'/parlamentar', '/parlamentar',
'/mesa-diretora', '/mesa-diretora',
'/sistema' '/sistema'
], ]},
'protocoloadm': [ 'protocoloadm': {
'users': {'operador_geral': ['/sistema',
'/protocoloadm/docadm',
'/protocoloadm'],
'operador_administrativo': ['/protocoloadm/docadm'],
'operador_protocoloadm': ['/protocoloadm']},
'prefixs': [
'/protocoloadm', '/protocoloadm',
'/sistema' '/sistema'
], ]},
'relatorios': [ 'relatorios': {
'prefixs': [
'/relatorios', '/relatorios',
], ]},
'sessao': [ 'sessao': {
'users': {'operador_geral': ['/sistema', 'sessao'],
'operador_sessao': ['/sessao']},
'prefixs': [
'/sessao', '/sessao',
'/sistema', '/sistema',
], ]},
} }
assert app_name in apps_url_patterns_prefixs, """
@pytest.mark.parametrize('url_item', _lista_urls)
def test_urlpatterns(url_item, admin_client):
key, url, var, app_name = url_item
url = '/' + (url % {v: 1 for v in var})
assert '\n' not in url, """
A url (%s) da app (%s) está mal formada.
""" % (app_name, url)
app_name = app_name[5:]
assert app_name in apps_url_patterns_prefixs_and_users, """
A app (%s) da url (%s) não consta na lista de prefixos do teste A app (%s) da url (%s) não consta na lista de prefixos do teste
""" % (app_name, url) """ % (app_name, url)
if app_name in apps_url_patterns_prefixs: if app_name in apps_url_patterns_prefixs_and_users:
prefixs = apps_url_patterns_prefixs[app_name] prefixs = apps_url_patterns_prefixs_and_users[app_name]['prefixs']
isvalid = False isvalid = False
for prefix in prefixs: for prefix in prefixs:
@ -218,9 +256,23 @@ def test_urlpatterns(url_item, admin_client):
%s %s
""" % (url, app_name, prefixs) """ % (url, app_name, prefixs)
urls_publicas_sem_permission_required = [
'/login',
'/logout',
'/comissao/1/materias-em-tramitacao',
'/materia/confirmar/1/1',
]
@pytest.mark.parametrize('url_item', _lista_urls)
def test_permissions_urls_for_users_by_apps(url_item, client):
key, url, var, app_name = url_item
url = '/' + (url % {v: 1 for v in var})
if url in urls_publicas_sem_permission_required:
return
@pytest.mark.parametrize('urls_app', _lista_urls)
def em_construcao_crud_permissions_urls(urls_app, client):
if not get_user_model().objects.exists(): if not get_user_model().objects.exists():
for app in sapl_appconfs: for app in sapl_appconfs:
# readequa permissões dos models adicionando # readequa permissões dos models adicionando
@ -230,50 +282,73 @@ def em_construcao_crud_permissions_urls(urls_app, client):
cria_grupos_permissoes() cria_grupos_permissoes()
users = get_user_model().objects.values_list('username', flat=True) users = get_user_model().objects.values_list('username', flat=True)
for url_item in _lista_urls[urls_app]:
key, url, var, app_name = url_item
url = '/' + (url % {v: 1 for v in var})
app_labels = app_name.split('.')[1] app_labels = app_name.split('.')[1]
view_class = None view = None
if hasattr(key, 'view_class'): if hasattr(key, 'view_class'):
view_class = key.view_class view = key.view_class()
""" """
A classe PermissionRequiredForAppCrudMixin pode ser usada em uma A classe PermissionRequiredForAppCrudMixin pode ser usada em uma
app mas envolver permissoes para outras app mas envolver permissoes para outras
como é o caso de PainelView que está na app 'sessao' como é o caso de PainelView que está na app 'sessao'
mas é um redirecionamento para 'painel'... aqui é feita mas é um redirecionamento para 'painel'... aqui é feita
a troca a urls_app a ser testada, por essas outras possíveis a troca da app a ser testada, por essas outras possíveis.
Este, até a ultima versão deste teste é o único tipo de view que
possui restrição restrição simples, por permissão, e não por
container, como é o caso de proposições que possui restrição
por usuário e não por, ou não tem, o campo permission_required
""" """
if PermissionRequiredForAppCrudMixin in type.mro(view_class): if PermissionRequiredForAppCrudMixin in type.mro(key.view_class):
# essa classe deve informar app_label # essa classe deve informar app_label
assert hasattr(view_class, 'app_label') assert hasattr(key.view_class, 'app_label')
# app_label deve ter conteudo # app_label deve ter conteudo
assert view_class.app_label assert key.view_class.app_label
app_labels = view_class.app_label app_labels = key.view_class.app_label
else:
if hasattr(view, 'permission_required') and \
view.permission_required is not None and\
len(view.permission_required) == 0:
"""
condição do Crud, se tem permission_required e ele é igual [],
então é uma view pública, teste liberado.
"""
return
else:
"""
Views que não se encaixam nãs condições acima, podem possuir
ou não restrição de acesso. Se o código continuar,
será tratado como tentativa de validar pois é possível
ter restrição local, como uma anotação method_required.
Caberá ao desenvolvedor de uma nova view, se for pública e
sem necessidade de nenhum tratamento de permissão, para limpar
o teste to py.test adicionar sua url
representativa na variavel externa ao teste:
urls_publicas_sem_permission_required, logo acima do teste
"""
pass
if isinstance(app_labels, str): if isinstance(app_labels, str):
app_labels = app_labels, app_labels = app_labels,
for app in app_labels: for app in app_labels:
# monta o username correspondente de a app da url a ser testada assert app in apps_url_patterns_prefixs_and_users, """
user_for_url_atual_app = 'operador_%s' O app_label (%s) associado a url (%s) não está na base de testes.
if app in ['base', 'parlamentares']: %s
user_for_url_atual_app = user_for_url_atual_app % 'geral' """ % (app_name, url)
elif app in 'protocoloadm':
user_for_url_atual_app = ( if 'users' not in apps_url_patterns_prefixs_and_users[app]:
user_for_url_atual_app % 'administrativo') continue
elif app in ['compilacao']:
return # TODO implementar teste para compilacao users_for_url_atual_app = apps_url_patterns_prefixs_and_users[
else: app]['users']
user_for_url_atual_app = user_for_url_atual_app % app
for username in users: for username in users:
print(username, user_for_url_atual_app, url) print(username, users_for_url_atual_app, url)
client.login(username=username, password='interlegis') client.login(username=username, password='interlegis')
@ -302,34 +377,45 @@ def em_construcao_crud_permissions_urls(urls_app, client):
LOGIN ANTES DE SUA EXECUÇÃO", desta forma nunca gerando erro LOGIN ANTES DE SUA EXECUÇÃO", desta forma nunca gerando erro
interno dada qualquer incoerência de parâmetros nas urls interno dada qualquer incoerência de parâmetros nas urls
""" """
if rg:
""" for _type, content in (
Se o usuário a ser testado é o usuário da app da url de get ('get', str(rg.content if rg else '')),
espera-se que não tenha recebido uma tela de login ('post', str(rp.content if rp else ''))):
"""
if username == user_for_url_atual_app and\ if not content:
not url.startswith('/sistema/'): continue
assert btn_login not in str(rg.content)
elif username != 'operador_geral' and\ def _assert_login(_in):
url.startswith('/sistema/'): if _in:
assert btn_login in str(rg.content) assert btn_login in content, """
elif username == 'operador_geral' and\ No teste de requisição "%s" a url (%s).
url.startswith('/sistema/'): App (%s)
assert btn_login not in str(rg.content) O usuário (%s) deveria ser redirecionado
para tela de login.
if rp: """ % (_type, url, app, username)
""" else:
Se o usuário a ser testado é o usuário da app da url de assert btn_login not in content, """
post espera-se que não tenha recebido uma tela de login No teste de requisição "%s" a url (%s).
""" App (%s)
if username == user_for_url_atual_app and\ O usuário (%s) não deveria ser redirecionado
not url.startswith('/sistema/'): para tela de login. Se essa é uma url
assert btn_login not in str(rp.content) invariavelmente pública, a adicione na variavel
elif username != 'operador_geral' and\ abaixo localizada no arquivo que se encontra este
url.startswith('/sistema/'): teste:
assert btn_login in str(rp.content)
elif username == 'operador_geral' and\ urls_publicas_sem_permission_required
url.startswith('/sistema/'):
assert btn_login not in str(rp.content)
""" % (_type, url, app, username)
if username not in users_for_url_atual_app:
# se não é usuário da app deve ser redirecionado para login
_assert_login(True)
else:
prefixs = users_for_url_atual_app[username]
for pr in prefixs:
if url.startswith(pr):
_assert_login(False)
break
client.get('/logout/', follow=True) client.get('/logout/', follow=True)

3
scripts/lista_urls.py

@ -25,7 +25,10 @@ class ListaUrls():
if value: if value:
url = value[0][0][0] url = value[0][0][0]
var = value[0][0][1] var = value[0][0][1]
# if url.endswith('anexada/create'):
# if url.startswith('materia/confirmar/'):
urls.append((key, url, var, item.app_name)) urls.append((key, url, var, item.app_name))
urls.sort(key=lambda x: x[1]) urls.sort(key=lambda x: x[1])
return urls return urls

Loading…
Cancel
Save