Método genérico (no controle) para obter dados de um objeto (discussão sobre segurança)
27/02/2013 11:50
1
Bom dia pessoal,

Esse é o meu 1o post aqui no Grails Brasil. Antes de mais nada, gostaria de elogiar o trabalho feito aqui. Grails é um framework que merece atenção e destaque.

Aqui vai a discussão que quero abrir. Estava fazendo uma tela ontem e me deparei com a necessidade de alterar um campo na tela, caso outro fosse atualizado (via AJAX). Assim, caso um item seja adicionado em uma aba, eu preciso mostrar uma informação associada a esse item em outra aba (via AJAX). O problema é que se eu fizesse um método no controle apenas para essa propriedade e no futuro precisasse de outra, eu teria que implementar outro método.

Como gosto de coisas genéricas, pensei o seguinte: estou em uma linguagem dinâmica com o poder do Eval.me() e foi o que fiz. Implementei o seguinte método no controlador:


def get() {
def pack = Pack.get(params.packId)

if (params.data != "") {
println params.data
if (!params.data.contains("=")) {
def s = Eval.me("pack", pack, "pack.${params.data}") as JSON
render s
}
}
else {
render pack as JSON
}
}


Explico:

Esse action recebe dois parametros, são eles: packId e data. packId é auto-explicativo. Já o data se trata de um código Groovy que nada mais é do que o acesso a uma determinada propriedade de um determinado instância da classe Pack.

Assim, eu poderia passar os parametros das seguintes maneiras:

[list]
packId=2&data=packSchemes.scheme.section.name
[/list]

Notem que na 1a opção eu acessei uma simples propriedade do objeto, já na segunda opção eu acessei uma coleção para dentro dela acessar um objeto, outro e depois o atributo name.

Isso tudo funciona graças ao Eval. No frigir dos ovos, o que estou fazendo é enviar código Groovy por meio do parametro data e isso levanta uma discussão sobre segurança, mais especificamente injeção de código.

Alguém poderia enviar, por exemplo: packId=2&data=addToPackSchemes(new PackScheme(xxxxxxxx))

Assim, um objeto seria indevidamente adicionado à coleção.

Algumas soluções:

Pensei em utilizar o método read da domain class
Pack.read(packId)
, porém, o read não evita que modificações sejam feitas e salvas automaticamente nas coleções. Além disso, se vc alterar as propriedades e der um save() funciona.

Outra opção é sempre que chamar o método get vc retornar todo o objeto no formato JSON. O problema disso é que as vezes vc quer uma simples propriedade e todo o objeto será retornado. Fica caro para o Hibernate caso o objeto possua muitos relacionamentos.

Outra opção é retornar um clone do objeto, mas é um tiro de canhão para matar uma borboleta.

Talvez verificar, como já faço acima (com o sinal de =), algumas palavras que possam indicar alteração (save por exemplo). Mas definitivamente isso não é uma solução elegante. Pois sempre haverá um furo.

Assim, pergunto, o que vocês fariam em uma situação dessa? Usariam ou não o poder de uma linguagem dinâmica para resovler essa questão?

Tags: eval injeção_de_código


1
Pessoal,

Por algum motivo as opções de passagem de parametros não foram completas, a primera é:


packId=2&data=dateCreated


a segunda é


packId=2&data=packSchemes.scheme.section.name
27/02/2013 11:53


1
Pessoal,

Criei uma solução que a princípio evita qualquer tipo de injeção de código. O que fiz foi utilizar Reflection (já embutida de forma facilitada no Groovy).

Toda classe no Groovy possui dois métodos que facilitam fazer reflection: getProperty e setProperty. Como os nomes dizem, eles servem para obter e setar valores de propriedades da instância da qual são chamados.

Assim, vamos imaginar uma classe Pessoa com os atributos nome e sexo.

Poderíamos fazer assim:


def pessoa = Pessoa.get(1)
println pessoa.getProperty("nome")


Isso nos imprime o valor do atributo nome para a respectiva instância.

Com isso, não é mais possível alterar o valor de algum campo (conforme podia ser feito com o Eval.me() - post anterior).

Vamos considerar o seguinte modelo: NotaFiscal (descricao), ItemNotaFiscal (descricao e valor) e UsuarioResponsavel (nome). Onde uma Nota pode ter n itens e apenas um usuário responsável por sua criação.

Assim, fica fácil em Groovy fazer o seguinte:


def nota = NotaFiscal.get(1)
println nota.usuarioResponsavel.nome


Isso vai imprimir o nome do usuário responsável pela criação da respectiva nota.

O meu problema é que não posso fazer a seguinte chamada: nota.getProperty("nota.usuarioResponsavel.nome"), pois, obviamente, o campo nome não é um atributo da nota, mas da classe UsuarioResponsavel.

Isso me gerou o 1o desafio, felizmente não foi tão complicado, pois eu já tinha algo parecido. Um algoritmo que navega recursivamente pela string "nota.usuarioResponsavel.nome" até mostrar, nesse caso, o nome do usuário.

Ele funciona assim:


def nota = Nota.get(1)
def nome = getFieldValue("nota.usuarioResponsavel.nome",nota)
println nome


O código acima, imprime o valor do atributo nome para a instancia usuarioResponsavel que está dentro da instancia nota.

Entretanto, o maior problema que enfrentei foi quando precisei fazer o mesmo, porém para os itens da nota, ou seja: "nota.itensNotaFiscal.descricao". Nesse caso não era só fazer a chamada getFieldValue("nota.itensNotaFiscal.descricao",nota). Pois itensNotaFiscal é uma coleção e ela não possui o campo descricao (o Groovy que faz a mágica de expandir isso). Porém, como estou fazendo tudo na mão, foi preciso tratar esses casos.

Enfim, o algoritmo são duas funções abaixo, uma delas recursiva:



def getFieldValue(f, o) {
def r = []
getFieldValueRec(f,o,r,f.substring(f.lastIndexOf(".")+1, f.length()))
}

private def getFieldValueRec(f, o, r, fResult) {
if (f.contains(".")) {
def current = f.substring(0,f.indexOf("."))
def others = f.substring(f.indexOf(".") + 1, f.length())

o = getFieldValueRec(current, o, r, fResult)

if (o instanceof Collection) {
for (item in o) {
getFieldValueRec(others, item, r, fResult)
}
}
else {
o = getFieldValueRec(others, o, r, fResult)
}

return r
}
else {
def aux = o.getProperty(f)

if (f == fResult)
r << aux

return aux
}
}


Para utilizar, basta chamar dessa maneira:


println getFieldValue("nota.itensNotaFiscal.descricao", Nota.get(1))


Com isso, fica fácil fazer a seguinte chamada http:

http://localhost:8080/ProgramaX/nota/get?notaId=1&data=nota.itensNotaFiscal.descricao

Isso vai nos retornar uma lista com a descrição de cada item da respectiva nota. A diferença para o anterior, é que o código Groovy injetado é limitado aos atributos. Não dá, por exemplo, para chamar um método.

Agora é injetar isso em cada classe de domínio e utilizar para os mais variados fins, como em chamdas AJAX onde é preciso atualizar um determinado campo HTML.
27/02/2013 16:29


0
Bacana Cantoni!

Muito bom quando vejo por aqui o pessoal publicando soluções interessnates como esta que você colocou.

Valeu!


0
Obrigado Kico!

Estou fazendo um novo post resumindo isso tudo e passando a solução final. Não vai ficar tão pequeno, mas acho que será útil. Contemplará Domínio, Controle e Visão (além de configuração no Bootstrap).

[]s

28/02/2013 16:49



Ainda não faz parte da comunidade???

Para se registrar, clique aqui.


Aprenda Groovy e Grails com a Formação itexto!

Newsletter Semana Groovy

Assinar

Envie seu link!


Livro de Grails


/dev/All

Os melhores blogs de TI (e em português) em um único lugar!

 
Creative Commons
RSS Grails Brasil é mantido por itexto Consultoria.
Em caso de problemas contacte Henrique Lobo Weissmann (Kico) por e-mail: kico@itexto.com.br
Todo o conteúdo presente neste site adota o Creative Commons como licença padrão.
Ver: 4.14.0
itexto