Catch up on everything announced at Firebase Summit, and learn how Firebase can help you accelerate app development and run your app with confidence. Learn More

Acesso seguro a dados para usuários e grupos

Mantenha tudo organizado com as coleções Salve e categorize o conteúdo com base nas suas preferências.

Muitos aplicativos colaborativos permitem que os usuários leiam e gravem diferentes partes de dados com base em um conjunto de permissões. Em um aplicativo de edição de documentos, por exemplo, os usuários podem permitir que alguns usuários leiam e escrevam seus documentos enquanto bloqueiam o acesso indesejado.

Solução: controle de acesso baseado em função

Você pode aproveitar o modelo de dados do Cloud Firestore, bem como as regras de segurança personalizadas para implementar o controle de acesso baseado em função em seu aplicativo.

Suponha que você esteja criando um aplicativo de escrita colaborativa no qual os usuários possam criar "histórias" e "comentários" com os seguintes requisitos de segurança:

  • Cada história tem um dono e pode ser compartilhada com "escritores", "comentaristas" e "leitores".
  • Os leitores só podem ver histórias e comentários. Eles não podem editar nada.
  • Os comentadores têm todo o acesso dos leitores e também podem adicionar comentários a uma história.
  • Os escritores têm todo o acesso dos comentadores e também podem editar o conteúdo da história.
  • Os proprietários podem editar qualquer parte de uma história, bem como controlar o acesso de outros usuários.

Estrutura de dados

Suponha que seu aplicativo tenha uma coleção de stories em que cada documento representa uma história. Cada história também tem uma subcoleção de comments onde cada documento é um comentário sobre aquela história.

Para acompanhar as funções de acesso, adicione um campo de roles que é um mapa de IDs de usuário para funções:

/stories/{storyid}

{
  title: "A Great Story",
  content: "Once upon a time ...",
  roles: {
    alice: "owner",
    bob: "reader",
    david: "writer",
    jane: "commenter"
    // ...
  }
}

Os comentários contêm apenas dois campos, o ID de usuário do autor e algum conteúdo:

/stories/{storyid}/comments/{commentid}

{
  user: "alice",
  content: "I think this is a great story!"
}

Regras

Agora que você tem as funções dos usuários registradas no banco de dados, você precisa escrever Regras de Segurança para validá-las. Essas regras pressupõem que o aplicativo usa o Firebase Auth para que a variável request.auth.uid seja o ID do usuário.

Etapa 1 : comece com um arquivo de regras básicas, que inclui regras vazias para histórias e comentários:

service cloud.firestore {
   match /databases/{database}/documents {
     match /stories/{story} {
         // TODO: Story rules go here...

         match /comments/{comment} {
            // TODO: Comment rules go here...
         }
     }
   }
}

Etapa 2 : adicione uma regra de write simples que dê aos proprietários controle total sobre as histórias. As funções definidas ajudam a determinar as funções de um usuário e se novos documentos são válidos:

service cloud.firestore {
   match /databases/{database}/documents {
     match /stories/{story} {
        function isSignedIn() {
          return request.auth != null;
        }

        function getRole(rsc) {
          // Read from the "roles" map in the resource (rsc).
          return rsc.data.roles[request.auth.uid];
        }

        function isOneOfRoles(rsc, array) {
          // Determine if the user is one of an array of roles
          return isSignedIn() && (getRole(rsc) in array);
        }

        function isValidNewStory() {
          // Valid if story does not exist and the new story has the correct owner.
          return resource == null && isOneOfRoles(request.resource, ['owner']);
        }

        // Owners can read, write, and delete stories
        allow write: if isValidNewStory() || isOneOfRoles(resource, ['owner']);

         match /comments/{comment} {
            // ...
         }
     }
   }
}

Etapa 3 : Escreva regras que permitam que um usuário de qualquer função leia histórias e comentários. O uso das funções definidas na etapa anterior mantém as regras concisas e legíveis:

service cloud.firestore {
   match /databases/{database}/documents {
     match /stories/{story} {
        function isSignedIn() {
          return request.auth != null;
        }

        function getRole(rsc) {
          return rsc.data.roles[request.auth.uid];
        }

        function isOneOfRoles(rsc, array) {
          return isSignedIn() && (getRole(rsc) in array);
        }

        function isValidNewStory() {
          return resource == null
            && request.resource.data.roles[request.auth.uid] == 'owner';
        }

        allow write: if isValidNewStory() || isOneOfRoles(resource, ['owner']);

        // Any role can read stories.
        allow read: if isOneOfRoles(resource, ['owner', 'writer', 'commenter', 'reader']);

        match /comments/{comment} {
          // Any role can read comments.
          allow read: if isOneOfRoles(get(/databases/$(database)/documents/stories/$(story)),
                                      ['owner', 'writer', 'commenter', 'reader']);
        }
     }
   }
}

Etapa 4 : permitir que escritores de histórias, comentadores e proprietários postem comentários. Observe que essa regra também valida se o owner do comentário corresponde ao usuário solicitante, o que impede que os usuários substituam os comentários uns dos outros:

service cloud.firestore {
   match /databases/{database}/documents {
     match /stories/{story} {
        function isSignedIn() {
          return request.auth != null;
        }

        function getRole(rsc) {
          return rsc.data.roles[request.auth.uid];
        }

        function isOneOfRoles(rsc, array) {
          return isSignedIn() && (getRole(rsc) in array);
        }

        function isValidNewStory() {
          return resource == null
            && request.resource.data.roles[request.auth.uid] == 'owner';
        }

        allow write: if isValidNewStory() || isOneOfRoles(resource, ['owner'])
        allow read: if isOneOfRoles(resource, ['owner', 'writer', 'commenter', 'reader']);

        match /comments/{comment} {
          allow read: if isOneOfRoles(get(/databases/$(database)/documents/stories/$(story)),
                                      ['owner', 'writer', 'commenter', 'reader']);

          // Owners, writers, and commenters can create comments. The
          // user id in the comment document must match the requesting
          // user's id.
          //
          // Note: we have to use get() here to retrieve the story
          // document so that we can check the user's role.
          allow create: if isOneOfRoles(get(/databases/$(database)/documents/stories/$(story)),
                                        ['owner', 'writer', 'commenter'])
                        && request.resource.data.user == request.auth.uid;
        }
     }
   }
}

Etapa 5 : Dê aos escritores a capacidade de editar o conteúdo da história, mas não de editar as funções da história ou alterar quaisquer outras propriedades do documento. Isso requer a divisão da regra de write de histórias em regras separadas para create , update e delete , pois os escritores só podem atualizar histórias:

service cloud.firestore {
   match /databases/{database}/documents {
     match /stories/{story} {
        function isSignedIn() {
          return request.auth != null;
        }

        function getRole(rsc) {
          return rsc.data.roles[request.auth.uid];
        }

        function isOneOfRoles(rsc, array) {
          return isSignedIn() && (getRole(rsc) in array);
        }

        function isValidNewStory() {
          return request.resource.data.roles[request.auth.uid] == 'owner';
        }

        function onlyContentChanged() {
          // Ensure that title and roles are unchanged and that no new
          // fields are added to the document.
          return request.resource.data.title == resource.data.title
            && request.resource.data.roles == resource.data.roles
            && request.resource.data.keys() == resource.data.keys();
        }

        // Split writing into creation, deletion, and updating. Only an
        // owner can create or delete a story but a writer can update
        // story content.
        allow create: if isValidNewStory();
        allow delete: if isOneOfRoles(resource, ['owner']);
        allow update: if isOneOfRoles(resource, ['owner'])
                      || (isOneOfRoles(resource, ['writer']) && onlyContentChanged());
        allow read: if isOneOfRoles(resource, ['owner', 'writer', 'commenter', 'reader']);

        match /comments/{comment} {
          allow read: if isOneOfRoles(get(/databases/$(database)/documents/stories/$(story)),
                                      ['owner', 'writer', 'commenter', 'reader']);
          allow create: if isOneOfRoles(get(/databases/$(database)/documents/stories/$(story)),
                                        ['owner', 'writer', 'commenter'])
                        && request.resource.data.user == request.auth.uid;
        }
     }
   }
}

Limitações

A solução mostrada acima demonstra como proteger os dados do usuário usando regras de segurança, mas você deve estar ciente das seguintes limitações:

  • Granularidade : No exemplo acima, várias funções (escritor e proprietário) têm acesso de gravação ao mesmo documento, mas com limitações diferentes. Isso pode se tornar difícil de gerenciar com documentos mais complexos e pode ser melhor dividir documentos únicos em vários documentos, cada um pertencente a uma única função.
  • Grupos Grandes : Se você precisar compartilhar com grupos muito grandes ou complexos, considere um sistema em que as funções sejam armazenadas em sua própria coleção em vez de um campo no documento de destino.