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. 272
      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.utils.decorators import classonlymethod
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 ugettext_lazy as _
from django.views.generic import (CreateView, DeleteView, DetailView, ListView,
UpdateView)
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.utils import normalize
logger = logging.getLogger(__name__)
ACTION_LIST, ACTION_CREATE, ACTION_DETAIL, ACTION_UPDATE, ACTION_DELETE = \
@ -163,7 +164,7 @@ class PermissionRequiredForAppCrudMixin(PermissionRequiredMixin):
apps = self.app_label
if isinstance(apps, str):
apps = apps,
# papp_label vazio dará acesso geral
# app_label vazio dará acesso geral
for app in apps:
if not self.request.user.has_module_perms(app):
return False
@ -870,13 +871,36 @@ class 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):
permission_required = ('base.view_tabelas_auxiliares',)
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):
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
return context
@ -886,8 +910,8 @@ class CrudAux(Crud):
ModelCrud = Crud.build(
_model, _help_path, _model_set, list_field_names)
class ModelCrudAux(ModelCrud):
BaseMixin = CrudAux.BaseMixin
class ModelCrudAux(CrudAux, ModelCrud):
pass
return ModelCrudAux

272
sapl/test_urls.py

@ -1,11 +1,11 @@
import pytest
from django.apps import apps
from django.contrib.auth import get_user_model
from django.contrib.auth.management import _get_all_permissions
from django.contrib.auth.models import Permission
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 ugettext_lazy as _
import pytest
from sapl.crud.base import PermissionRequiredForAppCrudMixin
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
pytestmark = pytest.mark.django_db
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)
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
# com /sistema/
# Verifica a url é de um CrudAux e, se for, testa se está
# na página Tabelas Auxiliares
key, url, var, app_name = url_item
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
""" % (url, app_name)
@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})
app_name = app_name[5:]
apps_url_patterns_prefixs = {
'base': [
apps_url_patterns_prefixs_and_users = {
'base': {
'users': {'operador_geral': ['/sistema']},
'prefixs': [
'/sistema',
'/login',
'/logout'
],
'comissoes': [
]},
'comissoes': {
'users': {'operador_geral': ['/sistema', '/comissao'],
'operador_comissoes': ['/comissao']},
'prefixs': [
'/comissao',
'/sistema'
],
'compilacao': [
]},
'compilacao': {
'prefixs': [
'/ta',
],
'lexml': [
]},
'lexml': {
'prefixs': [
'/lexml',
'/sistema'
],
'materia': [
]},
'materia': {
'users': {'operador_geral': ['/sistema', '/materia'],
'operador_autor': ['/proposicao'],
'operador_materia': ['/materia']},
'prefixs': [
'/materia',
'/proposicao',
'/sistema'
],
'norma': [
]},
'norma': {
'users': {'operador_geral': ['/sistema', '/norma'],
'operador_norma': ['/norma']},
'prefixs': [
'/norma',
'/sistema'
],
'painel': [
]},
'painel': {
'users': {'operador_geral': ['/sistema', '/painel'],
'operador_painel': ['/painel']},
'prefixs': [
'/painel',
'/sistema'
],
'parlamentares': [
]},
'parlamentares': {
'users': {'operador_geral': ['/sistema',
'/mesa-diretora',
'/parlamentar']},
'prefixs': [
'/parlamentar',
'/mesa-diretora',
'/sistema'
],
'protocoloadm': [
]},
'protocoloadm': {
'users': {'operador_geral': ['/sistema',
'/protocoloadm/docadm',
'/protocoloadm'],
'operador_administrativo': ['/protocoloadm/docadm'],
'operador_protocoloadm': ['/protocoloadm']},
'prefixs': [
'/protocoloadm',
'/sistema'
],
'relatorios': [
]},
'relatorios': {
'prefixs': [
'/relatorios',
],
'sessao': [
]},
'sessao': {
'users': {'operador_geral': ['/sistema', 'sessao'],
'operador_sessao': ['/sessao']},
'prefixs': [
'/sessao',
'/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
""" % (app_name, url)
if app_name in apps_url_patterns_prefixs:
prefixs = apps_url_patterns_prefixs[app_name]
if app_name in apps_url_patterns_prefixs_and_users:
prefixs = apps_url_patterns_prefixs_and_users[app_name]['prefixs']
isvalid = False
for prefix in prefixs:
@ -218,9 +256,23 @@ def test_urlpatterns(url_item, admin_client):
%s
""" % (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():
for app in sapl_appconfs:
# readequa permissões dos models adicionando
@ -230,50 +282,73 @@ def em_construcao_crud_permissions_urls(urls_app, client):
cria_grupos_permissoes()
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]
view_class = None
view = None
if hasattr(key, 'view_class'):
view_class = key.view_class
view = key.view_class()
"""
A classe PermissionRequiredForAppCrudMixin pode ser usada em uma
app mas envolver permissoes para outras
como é o caso de PainelView que está na app 'sessao'
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
assert hasattr(view_class, 'app_label')
assert hasattr(key.view_class, 'app_label')
# app_label deve ter conteudo
assert view_class.app_label
app_labels = view_class.app_label
assert key.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):
app_labels = app_labels,
for app in app_labels:
# monta o username correspondente de a app da url a ser testada
user_for_url_atual_app = 'operador_%s'
if app in ['base', 'parlamentares']:
user_for_url_atual_app = user_for_url_atual_app % 'geral'
elif app in 'protocoloadm':
user_for_url_atual_app = (
user_for_url_atual_app % 'administrativo')
elif app in ['compilacao']:
return # TODO implementar teste para compilacao
else:
user_for_url_atual_app = user_for_url_atual_app % app
assert app in apps_url_patterns_prefixs_and_users, """
O app_label (%s) associado a url (%s) não está na base de testes.
%s
""" % (app_name, url)
if 'users' not in apps_url_patterns_prefixs_and_users[app]:
continue
users_for_url_atual_app = apps_url_patterns_prefixs_and_users[
app]['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')
@ -302,34 +377,45 @@ def em_construcao_crud_permissions_urls(urls_app, client):
LOGIN ANTES DE SUA EXECUÇÃO", desta forma nunca gerando erro
interno dada qualquer incoerência de parâmetros nas urls
"""
if rg:
"""
Se o usuário a ser testado é o usuário da app da url de get
espera-se que não tenha recebido uma tela de login
"""
if username == user_for_url_atual_app and\
not url.startswith('/sistema/'):
assert btn_login not in str(rg.content)
elif username != 'operador_geral' and\
url.startswith('/sistema/'):
assert btn_login in str(rg.content)
elif username == 'operador_geral' and\
url.startswith('/sistema/'):
assert btn_login not in str(rg.content)
if rp:
"""
Se o usuário a ser testado é o usuário da app da url de
post espera-se que não tenha recebido uma tela de login
"""
if username == user_for_url_atual_app and\
not url.startswith('/sistema/'):
assert btn_login not in str(rp.content)
elif username != 'operador_geral' and\
url.startswith('/sistema/'):
assert btn_login in str(rp.content)
elif username == 'operador_geral' and\
url.startswith('/sistema/'):
assert btn_login not in str(rp.content)
for _type, content in (
('get', str(rg.content if rg else '')),
('post', str(rp.content if rp else ''))):
if not content:
continue
def _assert_login(_in):
if _in:
assert btn_login in content, """
No teste de requisição "%s" a url (%s).
App (%s)
O usuário (%s) deveria ser redirecionado
para tela de login.
""" % (_type, url, app, username)
else:
assert btn_login not in content, """
No teste de requisição "%s" a url (%s).
App (%s)
O usuário (%s) não deveria ser redirecionado
para tela de login. Se essa é uma url
invariavelmente pública, a adicione na variavel
abaixo localizada no arquivo que se encontra este
teste:
urls_publicas_sem_permission_required
""" % (_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)

3
scripts/lista_urls.py

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

Loading…
Cancel
Save