Escrevendo condições para regras de segurança do Cloud Firestore

Este guia se baseia no guia de estruturação de regras de segurança para mostrar como adicionar condições às regras de segurança do Cloud Firestore. Se você não estiver familiarizado com os conceitos básicos das regras de segurança do Cloud Firestore, consulte o guia de primeiros passos .

O principal alicerce das regras de segurança do Cloud Firestore é a condição. Uma condição é uma expressão booleana que determina se uma determinada operação deve ser permitida ou negada. Use regras de segurança para escrever condições que verifiquem a autenticação do usuário, validem dados recebidos ou até mesmo acessem outras partes do seu banco de dados.

Autenticação

Um dos padrões de regras de segurança mais comuns é controlar o acesso com base no estado de autenticação do usuário. Por exemplo, seu aplicativo pode permitir que apenas usuários conectados gravem dados:

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to access documents in the "cities" collection
    // only if they are authenticated.
    match /cities/{city} {
      allow read, write: if request.auth != null;
    }
  }
}

Outro padrão comum é garantir que os usuários só possam ler e gravar seus próprios dados:

service cloud.firestore {
  match /databases/{database}/documents {
    // Make sure the uid of the requesting user matches name of the user
    // document. The wildcard expression {userId} makes the userId variable
    // available in rules.
    match /users/{userId} {
      allow read, update, delete: if request.auth != null && request.auth.uid == userId;
      allow create: if request.auth != null;
    }
  }
}

Se seu aplicativo usar o Firebase Authentication ou o Google Cloud Identity Platform , a variável request.auth conterá as informações de autenticação do cliente que está solicitando os dados. Para obter mais informações sobre request.auth , consulte a documentação de referência .

Data de validade

Muitos aplicativos armazenam informações de controle de acesso como campos em documentos no banco de dados. As regras de segurança do Cloud Firestore podem permitir ou negar acesso dinamicamente com base nos dados do documento:

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to read data if the document has the 'visibility'
    // field set to 'public'
    match /cities/{city} {
      allow read: if resource.data.visibility == 'public';
    }
  }
}

A variável resource refere-se ao documento solicitado e resource.data é um mapa de todos os campos e valores armazenados no documento. Para obter mais informações sobre a variável resource , consulte a documentação de referência .

Ao gravar dados, você pode querer comparar os dados recebidos com os dados existentes. Neste caso, se o seu conjunto de regras permitir a gravação pendente, a variável request.resource conterá o estado futuro do documento. Para operações update que modificam apenas um subconjunto dos campos do documento, a variável request.resource conterá o estado do documento pendente após a operação. Você pode verificar os valores dos campos em request.resource para evitar atualizações de dados indesejadas ou inconsistentes:

service cloud.firestore {
  match /databases/{database}/documents {
    // Make sure all cities have a positive population and
    // the name is not changed
    match /cities/{city} {
      allow update: if request.resource.data.population > 0
                    && request.resource.data.name == resource.data.name;
    }
  }
}

Acesse outros documentos

Usando as funções get() e exists() , suas regras de segurança podem avaliar as solicitações recebidas em relação a outros documentos no banco de dados. As funções get() e exists() esperam caminhos de documentos totalmente especificados. Ao usar variáveis ​​para construir caminhos para get() e exists() , você precisa escapar explicitamente das variáveis ​​usando a sintaxe $(variable) .

No exemplo abaixo, a variável database é capturada pela instrução match /databases/{database}/documents e usada para formar o caminho:

service cloud.firestore {
  match /databases/{database}/documents {
    match /cities/{city} {
      // Make sure a 'users' document exists for the requesting user before
      // allowing any writes to the 'cities' collection
      allow create: if request.auth != null && exists(/databases/$(database)/documents/users/$(request.auth.uid))

      // Allow the user to delete cities if their user document has the
      // 'admin' field set to 'true'
      allow delete: if request.auth != null && get(/databases/$(database)/documents/users/$(request.auth.uid)).data.admin == true
    }
  }
}

Para gravações, você pode usar a função getAfter() para acessar o estado de um documento após a conclusão de uma transação ou lote de gravações, mas antes da transação ou lote ser confirmado. Assim como get() , a função getAfter() utiliza um caminho de documento totalmente especificado. Você pode usar getAfter() para definir conjuntos de gravações que devem ocorrer juntas como uma transação ou lote.

Acessar limites de chamadas

Há um limite de chamadas de acesso a documentos por avaliação de conjunto de regras:

  • 10 para solicitações de documento único e solicitações de consulta.
  • 20 para leituras, transações e gravações em lote de vários documentos. O limite anterior de 10 também se aplica a cada operação.

    Por exemplo, imagine que você cria uma solicitação de gravação em lote com três operações de gravação e que suas regras de segurança usam duas chamadas de acesso a documentos para validar cada gravação. Nesse caso, cada gravação utiliza 2 de suas 10 chamadas de acesso e a solicitação de gravação em lote utiliza 6 de suas 20 chamadas de acesso.

Exceder qualquer um dos limites resulta em um erro de permissão negada. Algumas chamadas de acesso a documentos podem ser armazenadas em cache e as chamadas armazenadas em cache não contam para os limites.

Para obter uma explicação detalhada de como esses limites afetam as transações e gravações em lote, consulte o guia para proteger operações atômicas .

Acesse chamadas e preços

O uso dessas funções executa uma operação de leitura em seu banco de dados, o que significa que você será cobrado pela leitura de documentos mesmo que suas regras rejeitem a solicitação. Consulte Preços do Cloud Firestore para obter informações de faturamento mais específicas.

Funções personalizadas

À medida que suas regras de segurança se tornam mais complexas, você pode querer agrupar conjuntos de condições em funções que podem ser reutilizadas em seu conjunto de regras. As regras de segurança suportam funções personalizadas. A sintaxe para funções personalizadas é um pouco parecida com JavaScript, mas as funções de regras de segurança são escritas em uma linguagem específica de domínio que tem algumas limitações importantes:

  • As funções podem conter apenas uma única instrução return . Eles não podem conter nenhuma lógica adicional. Por exemplo, eles não podem executar loops ou chamar serviços externos.
  • As funções podem acessar automaticamente funções e variáveis ​​do escopo em que estão definidas. Por exemplo, uma função definida no escopo service cloud.firestore tem acesso à variável resource e funções integradas, como get() e exists() .
  • As funções podem chamar outras funções, mas não podem recorrer. A profundidade total da pilha de chamadas é limitada a 10.
  • Na versão de regras v2 , as funções podem definir variáveis ​​usando a palavra-chave let . As funções podem ter até 10 ligações let, mas devem terminar com uma instrução return.

Uma função é definida com a palavra-chave function e aceita zero ou mais argumentos. Por exemplo, você pode querer combinar os dois tipos de condições usadas nos exemplos acima em uma única função:

service cloud.firestore {
  match /databases/{database}/documents {
    // True if the user is signed in or the requested data is 'public'
    function signedInOrPublic() {
      return request.auth.uid != null || resource.data.visibility == 'public';
    }

    match /cities/{city} {
      allow read, write: if signedInOrPublic();
    }

    match /users/{user} {
      allow read, write: if signedInOrPublic();
    }
  }
}

O uso de funções em suas regras de segurança torna-as mais fáceis de manter à medida que a complexidade de suas regras aumenta.

Regras não são filtros

Depois de proteger seus dados e começar a escrever consultas, lembre-se de que as regras de segurança não são filtros. Você não pode escrever uma consulta para todos os documentos de uma coleção e esperar que o Cloud Firestore retorne apenas os documentos que o cliente atual tem permissão para acessar.

Por exemplo, considere a seguinte regra de segurança:

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to read data if the document has the 'visibility'
    // field set to 'public'
    match /cities/{city} {
      allow read: if resource.data.visibility == 'public';
    }
  }
}

Negado : esta regra rejeita a seguinte consulta porque o conjunto de resultados pode incluir documentos onde visibility não é public :

Rede
db.collection("cities").get()
    .then(function(querySnapshot) {
        querySnapshot.forEach(function(doc) {
            console.log(doc.id, " => ", doc.data());
    });
});

Permitido : esta regra permite a seguinte consulta porque a cláusula where("visibility", "==", "public") garante que o conjunto de resultados satisfaça a condição da regra:

Rede
db.collection("cities").where("visibility", "==", "public").get()
    .then(function(querySnapshot) {
        querySnapshot.forEach(function(doc) {
            console.log(doc.id, " => ", doc.data());
        });
    });

As regras de segurança do Cloud Firestore avaliam cada consulta em relação ao seu resultado potencial e falham na solicitação se ela retornar um documento que o cliente não tem permissão para ler. As consultas devem seguir as restrições definidas pelas suas regras de segurança. Para obter mais informações sobre regras e consultas de segurança, consulte consulta de dados com segurança .

Próximos passos