Consultar dados em segurança

Esta página baseia-se nos conceitos em Estruturar regras de segurança e Escrever condições para regras de segurança para explicar como as regras de segurança do Firestore interagem com as consultas. Analisa mais detalhadamente a forma como as regras de segurança afetam as consultas que pode escrever e descreve como garantir que as consultas usam as mesmas restrições que as regras de segurança. Esta página também descreve como escrever regras de segurança para permitir ou negar consultas com base em propriedades de consulta, como limit e orderBy.

As regras não são filtros

Quando escrever consultas para obter documentos, tenha em atenção que as regras de segurança não são filtros. As consultas são tudo ou nada. Para poupar tempo e recursos, o Firestore avalia uma consulta em relação ao respetivo conjunto de resultados potencial em vez dos valores de campo reais de todos os seus documentos. Se uma consulta puder devolver documentos que o cliente não tem autorização para ler, todo o pedido falha.

Consultas e regras de segurança

Conforme demonstrado nos exemplos abaixo, tem de escrever as suas consultas de forma a ajustarem-se às restrições das suas regras de segurança.

Proteja e consulte documentos com base em auth.uid

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

/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 no controlo de acesso. Estes exemplos pressupõem que a app usa a Firebase Authentication para definir o campo author para o UID do utilizador que criou o documento. O Firebase Authentication também preenche a variável request.auth nas regras de segurança.

A seguinte regra de segurança usa as variáveis request.auth e resource.data para restringir o acesso de leitura e escrita de cada story ao respetivo 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 != null && request.auth.uid == resource.data.author;
    }
  }
}

Suponhamos que a sua app inclui uma página que mostra ao utilizador uma lista de story documentos que criou. Pode esperar usar a seguinte consulta para preencher esta página. No entanto, esta consulta vai falhar porque não inclui as mesmas restrições que as suas regras de segurança:

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

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

A consulta falha mesmo que o utilizador atual seja efetivamente o autor de todos os documentos story. A razão para este comportamento é que, quando o Firestore aplica as suas regras de segurança, avalia a consulta em função do conjunto de resultados potenciais e não das propriedades reais dos documentos na sua base de dados. Se uma consulta puder potencialmente incluir documentos que violem as suas regras de segurança, a consulta falha.

Por outro lado, 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 de consulta correspondem às restrições das regras de segurança

var user = firebase.auth().currentUser;

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

Proteja e consulte documentos com base num campo

Para demonstrar ainda mais a interação entre consultas e regras, as regras de segurança abaixo expandem o acesso de leitura para a coleção stories para permitir que qualquer utilizador leia documentos story onde o campo published está 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 != null && request.auth.uid == resource.data.author);
      // Only story authors can write
      allow write: if request.auth != null && request.auth.uid == resource.data.author;
    }
  }
}

A consulta de páginas publicadas tem de 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 é true para qualquer resultado. Por conseguinte, esta consulta satisfaz as regras de segurança e tem autorização para ler dados.

OR consultas

Quando avalia uma consulta lógica (or, in ou array-contains-any) em relação a um conjunto de regras, o Firestore avalia cada valor de comparação separadamente.OR Cada valor de comparação tem de cumprir as restrições da regra de segurança. Por exemplo, para a seguinte regra:

match /mydocuments/{doc} {
  allow read: if resource.data.x > 5;
}

Inválido: a consulta não garante que x > 5 para todos os documentos potenciais

// These queries will fail
query(db.collection("mydocuments"),
      or(where("x", "==", 1),
         where("x", "==", 6)
      )
    )

query(db.collection("mydocuments"),
      where("x", "in", [1, 3, 6, 42, 99])
    )

Válido: a consulta garante que x > 5 para todos os documentos potenciais

query(db.collection("mydocuments"),
      or(where("x", "==", 6),
         where("x", "==", 42)
      )
    )

query(db.collection("mydocuments"),
      where("x", "in", [6, 42, 99, 105, 200])
    )

Avaliação das restrições nas consultas

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

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

O conjunto de regras seguinte demonstra como escrever regras de segurança que avaliam as restrições aplicadas às consultas. Este exemplo expande o conjunto de regras anterior com as seguintes alterações:stories

  • O conjunto de regras separa a regra de leitura em regras para get e list.
  • A regra get restringe a obtenção de documentos individuais a documentos públicos ou documentos criados pelo utilizador.
  • A regra list aplica as mesmas restrições que get, mas para consultas. Também verifica o limite de consultas e, em seguida, rejeita qualquer consulta sem um 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;
    }

  }
}

Consultas e regras de segurança do grupo de recolha

Por predefinição, as consultas são limitadas a uma única coleção e obtêm resultados apenas dessa coleção. Com as consultas de grupos de coleções, pode obter resultados de um grupo de coleções composto por todas as coleções com o mesmo ID. Esta secção descreve como proteger as consultas de grupos de recolha através de regras de segurança.

Proteja e consulte documentos com base em grupos de coleções

Nas regras de segurança, tem de permitir explicitamente as consultas de grupos de recolha escrevendo uma regra para o grupo de recolha:

  1. Certifique-se de que rules_version = '2'; é a primeira linha do conjunto de regras. As consultas de grupos de coleções requerem o comportamento novo de carateres universais recursivos {name=**} da versão 2 das regras de segurança.
  2. Escreva uma regra para o seu grupo de recolha através do match /{path=**}/[COLLECTION_ID]/{doc}.

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

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

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

Nesta aplicação, tornamos as publicações editáveis pelos respetivos proprietários e legíveis por utilizadores autenticados:

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

Qualquer utilizador autenticado pode obter as publicações de qualquer fórum:

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

Mas e se quiser mostrar ao utilizador atual as respetivas publicações em todos os fóruns? Pode usar uma consulta de grupo de coleções para obter resultados de todas as coleções posts:

var user = firebase.auth().currentUser;

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

Nas regras de segurança, tem de permitir esta consulta escrevendo uma regra de leitura ou de lista para o grupo de coleções 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 != null;
    }
    match /forums/{forumid}/posts/{postid} {
      // Only a post's author can write to a post
      allow write: if request.auth != null && request.auth.uid == resource.data.author;

    }
  }
}

No entanto, tenha em atenção que estas regras aplicam-se a todas as coleções com o ID posts, independentemente da hierarquia. Por exemplo, estas regras aplicam-se a todas as seguintes coleções:posts

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

Proteja as consultas de grupos de recolha com base num campo

Tal como as consultas de recolha única, as consultas de grupos de recolhas também têm de cumprir as restrições definidas pelas suas regras de segurança. Por exemplo, podemos adicionar um campo published a cada publicação no fórum, tal como fizemos no exemplo stories acima:

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

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

Em seguida, podemos escrever regras para o grupo de recolha posts com base no estado published e na publicação author:

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 estas regras, os clientes Web, Apple e Android podem fazer as seguintes consultas:

  • Qualquer pessoa pode aceder a publicações publicadas num fórum:

    db.collection("forums/technology/posts").where('published', '==', true).get()
    
  • Qualquer pessoa pode aceder às publicações publicadas de um autor em todos os fóruns:

    db.collectionGroup("posts").where("author", "==", "some_auth_id").where('published', '==', true).get()
    
  • Os autores podem aceder a todas as suas publicações publicadas e não publicadas em todos os fóruns:

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

Proteja e consulte documentos com base no grupo de coleções e no caminho do documento

Em alguns casos, pode querer restringir as consultas de grupos de recolha com base no caminho do documento. Para criar estas restrições, pode usar as mesmas técnicas para proteger e consultar documentos com base num campo.

Considere uma aplicação que acompanha as transações de cada utilizador entre várias bolsas de valores e criptomoedas:

/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",
}

Repare no campo user. Embora saibamos que utilizador é proprietário de um transaction documento a partir do caminho do documento, duplicamos estas informações em cada documento transactionporque nos permite fazer duas coisas:

  • Escrever consultas de grupos de coleções restritas a documentos que incluam um /users/{userid} específico no respetivo caminho do documento. Por 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)
    
  • Aplique esta restrição a todas as consultas no grupo de recolha transactions para que um utilizador não possa obter os documentos transaction de outro utilizador.

Aplicamos esta restrição nas nossas regras de segurança e incluímos a 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
    }
  }
}

Passos seguintes