Многие приложения для совместной работы позволяют пользователям читать и записывать различные данные на основе заданного набора разрешений. Например, в приложении для редактирования документов пользователи могут захотеть разрешить нескольким пользователям читать и записывать свои документы, блокируя при этом нежелательный доступ.
Решение: Управление доступом на основе ролей.
Вы можете использовать модель данных Cloud Firestore, а также настраиваемые правила безопасности для реализации управления доступом на основе ролей в вашем приложении.
Предположим, вы разрабатываете приложение для совместного написания текстов, в котором пользователи могут создавать «истории» и «комментарии» со следующими требованиями к безопасности:
- У каждой истории есть один владелец, и ею можно поделиться с «авторами», «комментаторами» и «читателями».
- Читатели могут видеть только статьи и комментарии. Они не могут ничего редактировать.
- Комментаторы имеют полный доступ ко всей информации, что и читатели, и могут также оставлять комментарии к статье.
- Авторы имеют полный доступ ко всей информации, доступный комментаторам, и могут также редактировать содержание статей.
- Владельцы могут редактировать любую часть истории, а также контролировать доступ других пользователей.
Структура данных
Предположим, в вашем приложении есть коллекция stories , где каждый документ представляет собой отдельную историю. У каждой истории также есть подколлекция comments , где каждый документ является комментарием к этой истории.
Для отслеживания ролей доступа добавьте поле roles , представляющее собой сопоставление идентификаторов пользователей с ролями:
/stories/{storyid}
{
title: "A Great Story",
content: "Once upon a time ...",
roles: {
alice: "owner",
bob: "reader",
david: "writer",
jane: "commenter"
// ...
}
}
Комментарии содержат всего два поля: идентификатор пользователя автора и некоторое содержимое:
/stories/{storyid}/comments/{commentid}
{
user: "alice",
content: "I think this is a great story!"
}
Правила
Теперь, когда роли пользователей записаны в базе данных, вам необходимо написать правила безопасности для их проверки. Эти правила предполагают, что приложение использует Firebase Auth, поэтому переменная request.auth.uid содержит идентификатор пользователя.
Шаг 1 : Начните с базового файла правил, который включает пустые правила для историй и комментариев:
service cloud.firestore {
match /databases/{database}/documents {
match /stories/{story} {
// TODO: Story rules go here...
match /comments/{comment} {
// TODO: Comment rules go here...
}
}
}
}
Шаг 2 : Добавьте простое правило write , которое предоставляет владельцам полный контроль над историями. Определенные функции помогают определять роли пользователей и проверять допустимость новых документов:
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} {
// ...
}
}
}
}
Шаг 3 : Напишите правила, позволяющие пользователю любой роли читать истории и комментарии. Использование функций, определенных на предыдущем шаге, делает правила лаконичными и читаемыми:
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']);
}
}
}
}
Шаг 4 : Разрешите авторам статей, комментаторам и владельцам оставлять комментарии. Обратите внимание, что это правило также проверяет, совпадает ли owner комментария с пользователем, запрашивающим комментарий, что предотвращает перезапись комментариев друг друга:
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;
}
}
}
}
Шаг 5 : Предоставьте авторам возможность редактировать содержимое истории, но не изменять роли в истории или какие-либо другие свойства документа. Для этого необходимо разделить правило write историй на отдельные правила для create , update и delete поскольку авторы могут только обновлять истории:
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;
}
}
}
}
Ограничения
Представленное выше решение демонстрирует защиту пользовательских данных с помощью правил безопасности, однако следует учитывать следующие ограничения:
- Детализация : В приведенном выше примере несколько ролей (автор и владелец) имеют доступ на запись к одному и тому же документу, но с разными ограничениями. Управление такими документами может стать сложным, и в этом случае лучше разделить один документ на несколько документов, каждый из которых принадлежит одной роли.
- Большие группы : Если вам необходимо обмениваться данными с очень большими или сложными группами, рассмотрите систему, в которой роли хранятся в отдельной коллекции, а не в качестве поля в целевом документе.