Ir para o console

Consultar dados com segurança

Nesta página, você aprende como as regras de segurança do Cloud Firestore interagem com as consultas com base nos conceitos de Como estruturar regras de segurança e Como gravar condições para regras de segurança. Vamos examinar em detalhes como as regras de segurança afetam as consultas que você pode escrever. Além disso, vamos saber como garantir que suas consultas usem as mesmas restrições que suas regras de segurança. Por último, também veremos como escrever regras de segurança para permitir ou negar consultas com base nas propriedades dela, como limit e orderBy.

Regras não são filtros

Ao escrever consultas para recuperar documentos, lembre-se de que regras de segurança não são filtros. As consultas podem falhar ou serem bem-sucedidas. Para economizar tempo e recursos, o Cloud Firestore avalia uma consulta com relação ao potencial conjunto de resultados em vez dos valores de campo reais de todos os seus documentos. Se for possível que uma consulta retorne documentos para os quais o cliente não tem permissão de leitura, toda a solicitação falhará.

Consultas e regras de segurança

Como demonstrado nos exemplos abaixo, você precisa escrever consultas que atendam às restrições das suas regras de segurança.

Proteger e consultar documentos com base em auth.uid

O exemplo a seguir demonstra como escrever uma consulta para recuperar documentos protegidos por uma regra de segurança. Considere um banco de dados que contém uma coleção de documentos story:

/stories/{storyid}

{
  title: "A Great Story",
  content: "Once upon a time...",
  author: "some_auth_id",
  published: false
}

Além dos campos title e content, cada documento armazena os campos author e published para usar para controle de acesso. Após analisar os exemplos, é possível concluir que o aplicativo usa o Firebase Authentication para definir o campo author como o UID do usuário que criou o documento. O Firebase Authentication também preenche a variável request.auth nas regras de segurança.

Na regra de segurança a seguir, são usadas as variáveis request.auth e resource.data para restringir o acesso de leitura e gravação de cada story ao respectivo autor:

service cloud.firestore {
  match /databases/{database}/documents {
    match /stories/{storyid} {
      // Only the authenticated user who authored the document can read or write
      allow read, write: if request.auth.uid == resource.data.author;
    }
  }
}

Suponha que o aplicativo inclua uma página que mostra ao usuário uma lista de documentos story de autoria própria. Você pode pensar que é possível usar a seguinte consulta para preencher esta página. No entanto, a consulta falhará, porque ela não inclui as mesmas restrições que suas regras de segurança:

Inválido: as restrições da consulta não correspondem às das regras de segurança.

// This query will fail
db.collection("stories").get()

A consulta falhará mesmo se o usuário atual realmente for o autor de todos os documentos story. O motivo desse comportamento é que, quando o Cloud Firestore aplica suas regras de segurança, ele avalia a consulta em relação ao conjunto de resultados potenciais dela, e não em relação às propriedades reais dos documentos no seu banco de dados. Se uma consulta puder potencialmente incluir documentos que violam as regras de segurança, ela falhará.

Em contraste, a seguinte consulta é bem-sucedida, porque inclui a mesma restrição no campo author que as regras de segurança:

Válido: as restrições da consulta correspondem às restrições das regras de segurança.

var user = firebase.auth().currentUser;

db.collection("stories").where("author", "==", user.uid).get()

Proteger e consultar documentos com base em um campo

Para demonstrar melhor a interação entre consultas e regras, as regras de segurança abaixo expandem o acesso de leitura para a coleção de stories para permitir que qualquer usuário leia documentos de story quando o campo published estiver definido como true.

service cloud.firestore {
  match /databases/{database}/documents {
    match /stories/{storyid} {
      // Anyone can read a published story; only story authors can read unpublished stories
      allow read: if resource.data.published == true || request.auth.uid == resource.data.author;
      // Only story authors can write
      allow write: if request.auth.uid == resource.data.author;
    }
  }
}

A consulta a páginas publicadas precisa incluir as mesmas restrições que as regras de segurança:

db.collection("stories").where("published", "==", true).get()

A restrição de consulta .where("published", "==", true) garante que resource.data.published seja true para qualquer resultado. Portanto, essa consulta satisfaz as regras de segurança e pode ler os dados.

Como avaliar restrições em consultas

Suas regras de segurança também podem aceitar ou negar consultas com base nas próprias restrições. A variável request.query contém as propriedades limit, offset e orderBy de uma consulta. Por exemplo, suas regras de segurança podem negar qualquer consulta que não limite o número máximo de documentos recuperados a um determinado intervalo:

allow list: if request.query.limit <= 10;

O conjunto de regras a seguir demonstra como escrever regras de segurança que avaliam restrições incluídas em consultas. Este exemplo expande o conjunto de regras stories anterior com as seguintes alterações:

  • O conjunto de regras separa a regra de leitura em regras get e list.
  • A regra get restringe a recuperação de documentos únicos para documentos públicos ou documentos de autoria do usuário.
  • A regra list aplica as mesmas restrições que get, mas para consultas. Ela também verifica o limite da consulta e nega qualquer consulta sem limite ou com um limite superior a 10.
  • O conjunto de regras define uma função authorOrPublished() para evitar a duplicação de código.
service cloud.firestore {

  match /databases/{database}/documents {

    match /stories/{storyid} {

      // Returns `true` if the requested story is 'published'
      // or the user authored the story
      function authorOrPublished() {
        return resource.data.published == true || request.auth.uid == resource.data.author;
      }

      // Deny any query not limited to 10 or fewer documents
      // Anyone can query published stories
      // Authors can query their unpublished stories
      allow list: if request.query.limit <= 10 &&
                     authorOrPublished();

      // Anyone can retrieve a published story
      // Only a story's author can retrieve an unpublished story
      allow get: if authorOrPublished();

      // Only a story's author can write to a story
      allow write: if request.auth.uid == resource.data.author;
    }

  }
}

Regras de segurança e consultas do grupo de coleções

Por padrão, as consultas têm escopo delimitado a uma única coleção e recuperam os resultados apenas dela. Com as consultas do grupo de coleções, você recupera os resultados de um grupo que inclui todas as coleções com o mesmo ID. Veja nesta seção como proteger suas consultas do grupo de coleções por meio das regras de segurança.

Proteger e consultar documentos com base em grupos de coleções

Nas suas regras de segurança, você precisa permitir explicitamente as consultas do grupo de coleções. Para isso, grave uma regra para esse grupo:

  1. Garanta que rules_version = '2'; seja a primeira linha do seu conjunto de regras. As consultas do grupo de coleções exigem o comportamento do novo caractere curinga recursivo {name=**} das regras de segurança da versão 2.
  2. Escreva uma regra para seu grupo de coleções usando match /{path=**}/[COLLECTION_ID]/{doc}.

Por exemplo, pense em um fórum organizado em documentos de forum que contêm as subcoleções de posts:

/forums/{forumid}/posts/{postid}

{
  author: "some_auth_id",
  authorname: "some_username",
  content: "I just read a great story.",
}

Nesse aplicativo, tornamos as postagens editáveis pelos proprietários e legíveis para os usuários autenticados:

service cloud.firestore {
  match /databases/{database}/documents {
    match /forums/{forumid}/posts/{post} {
      // Only authenticated users can read
      allow read: if request.auth.uid != null;
      // Only the post author can write
      allow write: if request.auth.uid == resource.data.author;
    }
  }
}

Todos os usuários autenticados podem recuperar as postagens de qualquer fórum:

db.collection("forums/technology/posts").get()

E se você quiser mostrar ao usuário atual as postagens dele em todos os fóruns? Use uma consulta do grupo de coleções para recuperar os resultados de todas as coleções de posts:

var user = firebase.auth().currentUser;

db.collectionGroup("posts").where("author", "==", user.uid).get()

Nas suas regras de segurança, você precisa permitir essa consulta ao escrever uma regra de leitura ou uma lista de regras para o grupo de coleções de posts:

rules_version = '2';
service cloud.firestore {

  match /databases/{database}/documents {
    // Authenticated users can query the posts collection group
    // Applies to collection queries, collection group queries, and
    // single document retrievals
    match /{path=**}/posts/{post} {
      allow read: if request.auth.uid != null;
    }
    match /forums/{forumid}/posts/{postid} {
      // Only a post's author can write to a post
      allow write: if request.auth.uid == resource.data.author;

    }
  }
}

Essas regras serão aplicadas a todas as coleções com o ID de posts, independentemente da hierarquia. Por exemplo, elas se aplicarão a todas as seguintes coleções de posts:

  • /posts/{postid}
  • /forums/{forumid}/posts/{postid}
  • /forums/{forumid}/subforum/{subforumid}/posts/{postid}

Proteger consultas do grupo de coleções com base em um campo

Assim como as consultas de uma única coleção, as consultas do grupo de coleções também precisam atender às restrições definidas nas suas regras de segurança. Por exemplo, podemos adicionar um campo published a cada postagem do fórum, como fizemos no exemplo de stories acima:

/forums/{forumid}/posts/{postid}

{
  author: "some_auth_id",
  authorname: "some_username",
  content: "I just read a great story.",
  published: false
}

Nesse momento, podemos escrever regras para o grupo de coleções de posts com base no status published e no autor (author) da postagem:

rules_version = '2';
service cloud.firestore {

  match /databases/{database}/documents {

    // Returns `true` if the requested post is 'published'
    // or the user authored the post
    function authorOrPublished() {
      return resource.data.published == true || request.auth.uid == resource.data.author;
    }

    match /{path=**}/posts/{post} {

      // Anyone can query published posts
      // Authors can query their unpublished posts
      allow list: if authorOrPublished();

      // Anyone can retrieve a published post
      // Authors can retrieve an unpublished post
      allow get: if authorOrPublished();
    }

    match /forums/{forumid}/posts/{postid} {
      // Only a post's author can write to a post
      allow write: if request.auth.uid == resource.data.author;
    }
  }
}

Com essas regras, os clientes da Web e para iOS e Android podem fazer as seguintes consultas:

  • Qualquer pessoa pode recuperar postagens publicadas em um fórum:

    db.collection("forums/technology/posts").where('published', '==', true).get()
    
  • Qualquer pessoa pode recuperar as postagens publicadas por um autor em todos os fóruns:

    db.collectionGroup("posts").where("author", "==", "some_auth_id").where('published', '==', true).get()
    
  • Os autores podem recuperar todas as postagens que publicaram e cancelaram a publicação em todos os fóruns:

    var user = firebase.auth().currentUser;
    
    db.collectionGroup("posts").where("author", "==", user.uid).get()
    

Proteger e consultar documentos com base no grupo de coleções e no caminho do documento

Em alguns casos, é recomendado restringir as consultas do grupo de coleções com base no caminho do documento. Se quiser criar essas restrições, use as mesmas técnicas adotadas para proteger e consultar documentos com base em um campo.

Pense em um aplicativo que rastreia as transações de cada usuário em várias criptomoedas e bolsas de valores:

/users/{userid}/exchange/{exchangeid}/transactions/{transaction}

{
  amount: 100,
  exchange: 'some_exchange_name',
  timestamp: April 1, 2019 at 12:00:00 PM UTC-7,
  user: "some_auth_id",
}

Veja o campo user. Sabemos a que usuário pertence um documento transaction do caminho de documento, mas duplicamos essas informações em cada documento transaction, já que assim podemos realizar duas ações:

  • Gravar consultas do grupo de coleções restritas a documentos que incluam um /users/{userid} específico no caminho do documento. Exemplo:

    var user = firebase.auth().currentUser;
    // Return current user's last five transactions across all exchanges
    db.collectionGroup("transactions").where("user", "==", user).orderBy('timestamp').limit(5)
    
  • Aplicar essa restrição a todas as consultas no grupo de coleções de transactions para que um usuário não consiga recuperar documentos transaction de outro.

Aplicamos essa restrição nas nossas regras de segurança e incluímos validação de dados para o campo user:

rules_version = '2';
service cloud.firestore {

  match /databases/{database}/documents {

    match /{path=**}/transactions/{transaction} {
      // Authenticated users can retrieve only their own transactions
      allow read: if resource.data.user == request.auth.uid;
    }

    match /users/{userid}/exchange/{exchangeid}/transactions/{transaction} {
      // Authenticated users can write to their own transactions subcollections
      // Writes must populate the user field with the correct auth id
      allow write: if userid == request.auth.uid && request.data.user == request.auth.uid
    }
  }
}

Próximas etapas