Как управлять доступом к определенным полям

В этой статье на основе концепций, описанных в разделах Структурирование правил безопасности и Написание условий для правил безопасности, объясняется, как использовать Cloud Firestore Security Rules для создания правил, которые позволяют клиентам выполнять операции с одними полями в документе, но не с другими.

Иногда вам может потребоваться контролировать изменения в документе не на уровне документа, а на уровне поля.

Например, вы можете разрешить клиенту создавать или изменять документ, но запретить ему редактировать определенные поля в этом документе. или сделать так, чтобы любой документ, создаваемый клиентом, содержал определенный набор полей. В этом руководстве рассказывается, как выполнять некоторые из этих задач с помощью Cloud Firestore Security Rules.

Чтобы получить помощь от ИИ, скопируйте запрос ниже и вставьте его в свой ИИ-агент для программирования (например, Gemini в Android Studio, Antigravity, Claude Code, Codex или Cursor). Вы можете настроить запрос и повторить его с агентом. Подробные инструкции по установке вы найдете в статье Навыки агента Firebase.

Help me use Firebase in my app.
1. Make sure that **Firebase agent skills** are installed and updated. For Claude Code, Codex, or Cursor agents, help me install the Firebase plugin. For other agents, run `npx skills add firebase/agent-skills`. Always try to use **Firebase agent skills** for Firebase-related tasks.
2. Help me write, validate, and deploy Firebase Security Rules to protect my app's Firestore data.

Как разрешить доступ только для чтения к определенным полям

Чтение в правилах выполняется на уровне документа. Вы либо извлекаете весь документ, либо ничего не извлекаете. Частично извлечь документ невозможно. Невозможно использовать только правила безопасности, чтобы запретить пользователям читать определенные поля в документе.

Если в документе есть поля, которые вы хотите скрыть от некоторых пользователей, лучше всего поместить их в отдельный документ. Например, вы можете создать документ в подколлекции private следующим образом:

/employees/{emp_id}

  name: "Alice Hamilton",
  department: 461,
  start_date: <timestamp>

/employees/{emp_id}/private/finances

    salary: 80000,
    bonus_mult: 1.25,
    perf_review: 4.2

Затем вы можете добавить правила безопасности с разными уровнями доступа для двух коллекций. В этом примере мы используем пользовательские утверждения аутентификации, чтобы указать, что только пользователи с пользовательским утверждением аутентификации role, равным Finance, могут просматривать финансовую информацию сотрудника.

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow any logged in user to view the public employee data
    match /employees/{emp_id} {
      allow read: if request.resource.auth != null
      // Allow only users with the custom auth claim of "Finance" to view
      // the employee's financial data
      match /private/finances {
        allow read: if request.resource.auth &&
          request.resource.auth.token.role == 'Finance'
      }
    }
  }
}

Ограничение полей при создании документа

Правила не имеют схемы, то есть на уровне базы данных нет ограничений на то, какие поля содержит документ. Такая гибкость упрощает разработку, но иногда вам может потребоваться, чтобы клиенты могли создавать только документы, содержащие определенные поля или не содержащие другие поля.

Чтобы создать такие правила, изучите метод keys объекта request.resource.data. Это список всех полей, которые клиент пытается записать в этот новый документ. Объединив этот набор полей с такими функциями, как hasOnly() или hasAny(), вы можете добавить логику, которая ограничивает типы документов, которые пользователь может добавить в правила.

Как сделать поля обязательными в новых документах

Допустим, вы хотите, чтобы все документы, созданные в коллекции restaurant, содержали как минимум поля name, location и city. Для этого можно вызвать hasAll() для списка ключей в новом документе.

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to create a document only if that document contains a name
    // location, and city field
    match /restaurant/{restId} {
      allow create: if request.resource.data.keys().hasAll(['name', 'location', 'city']);
    }
  }
}

Это позволяет создавать рестораны и с другими полями, но гарантирует, что все документы, созданные клиентом, содержат как минимум эти три поля.

Запрет определенных полей в новых документах

Аналогичным образом можно запретить клиентам создавать документы, содержащие определенные поля, используя hasAny() со списком запрещенных полей. Этот метод возвращает значение True, если документ содержит любое из этих полей, поэтому вам, вероятно, потребуется отрицание результата, чтобы запретить определенные поля.

Например, в следующем примере клиентам не разрешено создавать документ, содержащий поле average_score или rating_count, поскольку эти поля будут добавлены вызовом сервера позже.

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to create a document only if that document does *not*
    // contain an average_score or rating_count field.
    match /restaurant/{restId} {
      allow create: if (!request.resource.data.keys().hasAny(
        ['average_score', 'rating_count']));
    }
  }
}

Создание белого списка полей для новых документов

Вместо того чтобы запрещать определенные поля в новых документах, вы можете создать список только тех полей, которые явно разрешены в новых документах. Затем вы можете использовать функцию hasOnly(), чтобы убедиться, что все новые созданные документы содержат только эти поля (или их подмножество) и никакие другие.

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to create a document only if that document doesn't contain
    // any fields besides the ones listed below.
    match /restaurant/{restId} {
      allow create: if (request.resource.data.keys().hasOnly(
        ['name', 'location', 'city', 'address', 'hours', 'cuisine']));
    }
  }
}

Сочетание обязательных и необязательных полей

Вы можете комбинировать операции hasAll и hasOnly в правилах безопасности, чтобы сделать некоторые поля обязательными, а другие – необязательными. Например, в этом примере требуется, чтобы все новые документы содержали поля name, location и city, а также необязательно поля address, hours и cuisine.

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to create a document only if that document has a name,
    // location, and city field, and optionally address, hours, or cuisine field
    match /restaurant/{restId} {
      allow create: if (request.resource.data.keys().hasAll(['name', 'location', 'city'])) &&
       (request.resource.data.keys().hasOnly(
           ['name', 'location', 'city', 'address', 'hours', 'cuisine']));
    }
  }
}

В реальном сценарии вы можете перенести эту логику во вспомогательную функцию, чтобы не дублировать код и упростить объединение необязательных и обязательных полей в один список, например:

service cloud.firestore {
  match /databases/{database}/documents {
    function verifyFields(required, optional) {
      let allAllowedFields = required.concat(optional);
      return request.resource.data.keys().hasAll(required) &&
        request.resource.data.keys().hasOnly(allAllowedFields);
    }
    match /restaurant/{restId} {
      allow create: if verifyFields(['name', 'location', 'city'],
        ['address', 'hours', 'cuisine']);
    }
  }
}

Ограничение полей при обновлении

В целях безопасности клиентам обычно разрешается изменять только некоторые поля. Для этого недостаточно просто посмотреть на список request.resource.data.keys(), описанный в предыдущем разделе, поскольку он представляет собой полный документ в том виде, в каком он будет выглядеть после обновления, и поэтому будет включать поля, которые клиент не изменял.

Однако если вы используете функцию diff(), вы можете сравнить request.resource.data с объектом resource.data, который представляет документ в базе данных до обновления. При этом создается объект mapDiff, содержащий все различия между двумя картами.

Вызвав метод affectedKeys() для этого mapDiff, вы можете получить набор полей, которые были изменены при редактировании. Затем вы можете использовать такие функции, как hasOnly() или hasAny(), чтобы убедиться, что этот набор содержит (или не содержит) определенные элементы.

Запрет на изменение некоторых полей

Используя метод hasAny() для набора, созданного с помощью affectedKeys(), и затем отрицая результат, вы можете отклонить любой запрос клиента, который пытается изменить поля, которые вы не хотите изменять.

Например, вы можете разрешить клиентам обновлять информацию о ресторане, но запретить им изменять среднюю оценку или количество отзывов.

service cloud.firestore {
  match /databases/{database}/documents {
    match /restaurant/{restId} {
      // Allow the client to update a document only if that document doesn't
      // change the average_score or rating_count fields
      allow update: if (!request.resource.data.diff(resource.data).affectedKeys()
        .hasAny(['average_score', 'rating_count']));
    }
  }
}

Разрешить изменять только определенные поля.

Вместо того чтобы указывать поля, которые не нужно изменять, вы можете использовать функцию hasOnly(), чтобы указать список полей, которые нужно изменить. Обычно это считается более безопасным, поскольку запись в любые новые поля документа по умолчанию запрещена, пока вы явно не разрешите это в своих правилах безопасности.

Например, вместо того чтобы запрещать поля average_score и rating_count, вы можете создать правила безопасности, которые позволят клиентам изменять только поля name, location, city, address, hours и cuisine.

service cloud.firestore {
  match /databases/{database}/documents {
    match /restaurant/{restId} {
    // Allow a client to update only these 6 fields in a document
      allow update: if (request.resource.data.diff(resource.data).affectedKeys()
        .hasOnly(['name', 'location', 'city', 'address', 'hours', 'cuisine']));
    }
  }
}

Это означает, что если в какой-то будущей итерации вашего приложения документы ресторана будут включать поле telephone, попытки отредактировать это поле будут неудачными, пока вы не вернетесь и не добавите это поле в список hasOnly() в своих правилах безопасности.

Принудительное применение типов полей

Ещё одно следствие отсутствия схемы у правил заключается в том, что на уровне базы данных не контролируется, какие типы данных могут храниться в определенных полях. Однако вы можете принудительно задать это в правилах безопасности с помощью оператора is.

Например, следующее правило безопасности требует, чтобы поле score отзыва было целым числом, поля headline, content и author_name – строками, а поле review_date – временной меткой.

service cloud.firestore {
  match /databases/{database}/documents {
    match /restaurant/{restId} {
      // Restaurant rules go here...
      match /review/{reviewId} {
        allow create: if (request.resource.data.score is int &&
          request.resource.data.headline is string &&
          request.resource.data.content is string &&
          request.resource.data.author_name is string &&
          request.resource.data.review_date is timestamp
        );
      }
    }
  }
}

Допустимые типы данных для оператора is: bool, bytes, float, int, list, latlng, number, path, map, string и timestamp. Оператор is также поддерживает типы данных constraint, duration, set и map_diff, но поскольку они генерируются самим языком правил безопасности, а не клиентами, вы редко будете использовать их в большинстве практических приложений.

Типы данных list и map не поддерживают обобщения и аргументы типов. Другими словами, вы можете использовать правила безопасности, чтобы обеспечить наличие в определенном поле списка или карты, но вы не можете обеспечить наличие в поле списка всех целых чисел или всех строк.

Аналогичным образом вы можете использовать правила безопасности, чтобы принудительно применять значения типов для определенных записей в списке или карте (используя скобки или имена ключей соответственно), но не существует ярлыка для принудительного применения типов данных всех элементов в карте или списке одновременно.

Например, следующие правила гарантируют, что поле tags в документе содержит список, а первая запись является строкой. Оно также гарантирует, что поле product содержит карту, которая, в свою очередь, содержит название товара, являющееся строкой, и количество, являющееся целым числом.

service cloud.firestore {
  match /databases/{database}/documents {
  match /orders/{orderId} {
    allow create: if request.resource.data.tags is list &&
      request.resource.data.tags[0] is string &&
      request.resource.data.product is map &&
      request.resource.data.product.name is string &&
      request.resource.data.product.quantity is int
      }
    }
  }
}

Типы полей необходимо применять как при создании, так и при обновлении документа. Поэтому вы можете создать вспомогательную функцию, которую можно вызывать как в разделе создания, так и в разделе обновления правил безопасности.

service cloud.firestore {
  match /databases/{database}/documents {

  function reviewFieldsAreValidTypes(docData) {
     return docData.score is int &&
          docData.headline is string &&
          docData.content is string &&
          docData.author_name is string &&
          docData.review_date is timestamp;
  }

   match /restaurant/{restId} {
      // Restaurant rules go here...
      match /review/{reviewId} {
        allow create: if reviewFieldsAreValidTypes(request.resource.data) &&
          // Other rules may go here
        allow update: if reviewFieldsAreValidTypes(request.resource.data) &&
          // Other rules may go here
      }
    }
  }
}

Принудительное применение типов для необязательных полей

Важно помнить, что вызов request.resource.data.foo для документа, в котором не существует foo, приводит к ошибке, и поэтому любое правило безопасности, выполняющее этот вызов, отклонит запрос. Для этого используйте метод get в request.resource.data. Метод get позволяет указать аргумент по умолчанию для поля, которое вы извлекаете из карты, если это поле не существует.

Например, если документы, которые нужно проверить, также содержат необязательное поле photo_url и необязательное поле tags, и вы хотите убедиться, что они являются строками и списками соответственно, вы можете сделать это, переписав функцию reviewFieldsAreValidTypes следующим образом:

  function reviewFieldsAreValidTypes(docData) {
     return docData.score is int &&
          docData.headline is string &&
          docData.content is string &&
          docData.author_name is string &&
          docData.review_date is timestamp &&
          docData.get('photo_url', '') is string &&
          docData.get('tags', []) is list;
  }

Это отклоняет документы, в которых tags существует, но не является списком, и разрешает документы, которые не содержат поле tags (или photo_url).

Частичная запись не допускается.

И последнее, что нужно сказать о Cloud Firestore Security Rules: они либо позволяют клиенту внести изменение в документ, либо отклоняют всю правку. Вы не можете создавать правила безопасности, которые принимают записи в одни поля вашего документа и отклоняют другие в рамках одной и той же операции.