Como funcionam as regras de segurança do Firebase na Realtime Database?
Parte 2 — Validações e Índices
Depois de termos aprendido a definir regras para leitura e escrita de dados na nossa Realtime Database no artigo anterior, neste artigo vamos aprender como validar esses dados e como definir índices para ordenar os mesmos.
Tal como sabemos, as regras de segurança são uma árvore JSON composta por nós, chaves e valores (tal como qualquer outra árvore JSON), e já conhecemos 2 das 4 chaves que fazem parte das regras de segurança: “.read” e “.write”. Por isso, neste artigo vamos olhar para as outras 2 que faltavam: “.validate” e “.indexOn”.
Validação de dados (.validate)
A validação de dados é importante para garantir que tenhamos uma base de dados consistente.(Lazaro Anibal que o diga*). Afinal de contas, se a nossa base de dados é apenas uma árvore JSON, não podemos permitir que um mesmo campo tenha o valor de um tipo num nó e outro tipo noutro nó (sendo que ambos fazem parte da mesma lista):
“utilizadores”:{
“uid1”:{
“nome”:”Rosário”,
“visto”:”201802282030"
},
“uid2”:{
“nome”:”Paulo”,
“visto”:{
“dia”:”07/03/2017",
“hora”:”20h”
}
}
}
Nem permitir que na mesma lista exista um nó que tenha um campo (importante) e outro que não tenha:
"utilizadores":{
"uid1":{
"nome":"Rosário",
"idade":20,
"email":"rosariofernandes51@gmail.com",
"visto":"201802282030"
},
"uid2":{
"nome":"Paulo",
"idade":22,
"visto":"201803072000"
}
}
É para evitar as situações mostradas acima que existe a regra “.validate”. Podemos evitar o problema do exemplo 1 utilizando as regras a seguir:
"utilizadores":{
"$uid":{
".write":"auth!=null && $uid == auth.uid",
".read":"auth!=null && $uid == auth.uid",
".validate":"newData.child('visto').isString()"
}
}
Neste exemplo, newData.child(‘visto’).isString()
vai garantir que o atributo visto
que seja sempre uma String. Se não for enviado uma String, então o utilizador não será guardado na database.
E para evitar o problema do exemplo 2, podemos utilizar as regras:
"utilizadores":{
"$uid":{
".write":"auth!=null && $uid == auth.uid",
".read":"auth!=null && $uid == auth.uid", .validate":"newData.hasChildren(['nome','idade','visto','email'])"
}
}
Esta regra obriga que o nosso objeto utilizador tenha os 4 campos(nome, idade, visto e email) para que possa ser armazenado na database.
Índices (.indexOn)
Se você já utilizou o método orderByChild()
quando criava uma Query para ler dados do Firebase, provavelmente já se deparou com o aviso(warning):
Using an unspecified index. Consider adding “.indexOn” … to your security and Firebase Database rules for better performance
Isso acontece porque, por padrão, o Firebase ordena os dados de acordo com as suas chaves. Se você quiser ordenar por um outro atributo/child, você deve especificar qual é esse atributo/child para que a Realtime Database possa ordenar os dados assim na própria base de dados, antes de enviar para a sua aplicação (Android, Web ou iOS). Isso porque ordenar os dados na aplicação pode ter custos elevados em termos de performance (uso de memória e bateria).
Por exemplo, se quisermos ordenar os nossos utilizadores pelo nome, utilizamos a regra .indexOn com o nome:
"utilizadores":{
"$uid":{
".write":"auth!=null && $uid == auth.uid",
".read":"auth!=null && $uid == auth.uid",
".indexOn":"nome"
}
}
Se houver uma outra tela na nossa aplicação onde precisamos de ordenar os utilizadores com base em um outro atributo (como o email por exemplo). Podemos especificar isso passando um array para o indexOn:
"utilizadores":{
"$uid":{
".write":"auth!=null && $uid == auth.uid",
".read":"auth!=null && $uid == auth.uid",
".indexOn": ["nome","email"]
}
}
Bónus: Regras baseadas em queries
A documentação especifica claramente que: “Regras não são filtros”. O que significa que não tem como criar regras para mostrar apenas um certo grupo de dados e outros não (filtragem básica).
Mas em Janeiro de 2018, o Firebase anunciou as regras baseadas em queries. Estas regras servem para limitar os tipos de queries que podem ser executadas na nossa base de dados. E de certa forma, fazem algo parecido com uma filtragem que antes não era possível fazer.
Por exemplo, supondo que temos 2 tipos de utilizadores na database. Queremos permitir que um dos utilizadores possa ler todo o nó mensagens, enquanto o outro só lê as primeiras 25 mensagens marcadas como activas.
Para tal, podemos limitar os tipos de queries que podem ser executados nesse nó:
{
"mensagens":{
".read":"query.orderByKey || (query.orderByChild == 'activa' && query.limitToFirst == 25)",
".write":"auth!=null"
}
}
Com estas regras, só podem ser executadas as seguintes queries na database:
getReference(“mensagens”).orderByKey(); //egetReference(“mensagens”).orderByChild(“activa”).limitToFirst(10);
Qualquer outra query resultará em PERMISSION_DENIED
.
E esta foi a última regra que eu tinha para mostrar nesta série de Regras de Segurança da Realtime Database. Agora você pode manter a sua base de dados segura. No próximo artigo explicarei como utilizar o Simulador de Regras da Realtime Database.
Caso tenha alguma dúvida ou sugestão, pode me contactar pelo email rosariofernandes51@gmail.com ou pelo Telegram. Será um prazer conversar com você. 🙂
*- Lazaro Anibal é um amigo que teve um problema com as regras de validate e inspirou-me a escrever este post. Thanks dude 😃