Consultar dados com segurança

Esta página se baseia nos conceitos de Estruturação de regras de segurança e Gravação de condições para regras de segurança para explicar como as regras de segurança do Cloud Firestore interagem com consultas. Ele analisa mais de perto como as regras de segurança afetam as consultas que você pode escrever e descreve como garantir que suas consultas usem as mesmas restrições que suas 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 .

Regras não são filtros

Ao escrever consultas para recuperar documentos, lembre-se de que as regras de segurança não são filtros – as consultas são tudo ou nada. Para economizar tempo e recursos, o Cloud Firestore avalia uma consulta em relação ao conjunto de resultados potencial, em vez dos valores reais dos campos de todos os seus documentos. Se uma consulta puder retornar documentos que o cliente não tem permissão para ler, toda a solicitação falhará.

Consultas e regras de segurança

Como demonstram os exemplos abaixo, você deve escrever suas consultas para atender às restrições de suas regras de segurança.

Proteja e consulte 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 :

/histórias/{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 o author e os campos published para uso no controle de acesso. Estes exemplos pressupõem 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.

A regra de segurança a seguir usa as variáveis request.auth e resource.data para restringir o acesso de leitura e gravação de cada story ao seu 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;
    }
  }
}

Suponha que seu aplicativo inclua uma página que mostra ao usuário uma lista de documentos story de sua autoria. Você pode esperar usar a consulta a seguir para preencher esta página. No entanto, esta consulta 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 usuário atual seja realmente o autor de cada documento story . A razão para esse comportamento é que quando o Cloud Firestore aplica suas regras de segurança, ele avalia a consulta em relação ao conjunto de resultados potencial , e não às propriedades reais dos documentos em seu banco de dados. Se uma consulta puder incluir documentos que violem suas regras de segurança, a consulta falhará.

Por outro lado, a consulta a seguir é 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 em um 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 usuário 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 deve incluir as mesmas restrições das 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, esta consulta satisfaz as regras de segurança e tem permissão para ler dados.

OR consultas

Ao avaliar uma consulta OR lógica ( or , in ou array-contains-any ) em relação a um conjunto de regras, o Cloud Firestore avalia cada valor de comparação separadamente. Cada valor de comparação deve atender às 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])
    )

Avaliando restrições em consultas

Suas regras de segurança também podem aceitar ou negar consultas com base em suas restrições. A variável request.query contém as limit , offset e orderBy de uma consulta. Por exemplo, as 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 colocadas em consultas. Este exemplo expande o conjunto de regras stories anteriores com as seguintes alterações:

  • O conjunto de regras separa a regra de leitura em regras para get e list .
  • A regra get restringe a recuperação de documentos únicos a documentos públicos ou documentos de autoria do usuário.
  • A regra da list aplica as mesmas restrições que get , mas para consultas. Ele também verifica o limite de consultas e, em seguida, nega qualquer consulta sem limite ou com limite superior a 10.
  • O conjunto de regras define uma função authorOrPublished() para evitar 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 de grupo de coleta e regras de segurança

Por padrão, as consultas têm como escopo uma única coleção e recuperam resultados somente dessa coleção. Com consultas de grupo de coleções , você pode recuperar resultados de um grupo de coleções que consiste em todas as coleções com o mesmo ID. Esta seção descreve como proteger suas consultas de grupo de coleta usando regras de segurança.

Proteja e consulte documentos com base em grupos de coleta

Nas suas regras de segurança, você deve permitir explicitamente consultas de grupos de coleções escrevendo uma regra para o grupo de coleções:

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

Por exemplo, considere um fórum organizado em documentos forum contendo subcoleções posts :

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

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

Neste aplicativo, tornamos as postagens editáveis ​​por seus proprietários e legíveis por 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 != null;
      // Only the post author can write
      allow write: if request.auth != null && request.auth.uid == resource.data.author;
    }
  }
}

Qualquer usuário autenticado pode recuperar as postagens de qualquer fórum:

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

Mas e se você quiser mostrar ao usuário atual suas postagens em todos os fóruns? Você pode usar uma consulta de grupo de coleções para recuperar resultados de todas as coleções posts :

var user = firebase.auth().currentUser;

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

Nas suas regras de segurança, você deve permitir esta consulta escrevendo uma regra de leitura ou lista para o grupo de coleta 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;

    }
  }
}

Observe, entretanto, que essas regras serão aplicadas a todas as coleções com posts de ID, independentemente da hierarquia. Por exemplo, estas regras se aplicam a todas as seguintes coleções posts :

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

Consultas seguras de grupos de coleta com base em um campo

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

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

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

Podemos então escrever regras para o grupo de coleta posts com base no status published e no 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 Web, Apple 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 de 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 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 coleta e no caminho do documento

Em alguns casos, talvez você queira restringir as consultas do grupo de coleções com base no caminho do documento. Para criar essas restrições, você pode usar as mesmas técnicas para proteger e consultar documentos com base em um campo.

Considere um aplicativo que monitora as transações de cada usuário entre diversas bolsas de valores e criptomoedas:

/usuários/{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",
}

Observe o campo user . Embora saibamos qual usuário possui um documento transaction pelo caminho do documento, duplicamos essas informações em cada documento transaction porque nos permite fazer duas coisas:

  • Escreva consultas de grupo de coleções restritas a documentos que incluam um /users/{userid} específico no 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 coleta transactions para que um usuário não possa recuperar os documentos transaction de outro usuário.

Aplicamos esta restrição em 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óximos passos