在 Firestore 安全规则“列表”操作中使用变量

Posted

技术标签:

【中文标题】在 Firestore 安全规则“列表”操作中使用变量【英文标题】:Using Variables in a Firestore Security Rules "List" operation 【发布时间】:2019-01-31 05:11:35 【问题描述】:

我正在尝试根据子集合中文档字段的值设置允许访问集合的安全规则。

这在按 id 检索单个文档时按预期工作,这是一个 get 操作。但是,在查询 main_collectionlist 操作)时,会失败并出现“权限被拒绝”错误。由于集合中只有一个文档,所以这不是我对某些正在查询的文档没有权限的情况,例如this question。

我的数据库结构如下所示。它包含列出的集合 (main_collection),它有一个文档 (some_doc),它有一个子集合 (sub_collection),它有一个文档 (another_doc)。

/main_collection/some_doc/sub_collection/another_doc

another_doc 有一个字符串字段someFieldValue

对于这个例子,我的查询是整个集合,即单个文档。在我的实际应用程序中,它只查询它期望有权访问的文档,但这里的最终结果是相同的,因为我无法从客户端库中过滤文档的子集合。

firestore.collection('main_collection').get()

这些是我的安全规则。

service cloud.firestore 
  match /databases/database/documents 
    match /main_collection/mainColDoc 
      // This operation works
      allow get: if subCollectionDocumentHasField('someFieldValue');
      // This operation fails with permission denied
      allow list: if subCollectionDocumentHasField('someFieldValue');

      // This checks for the existence of a field on the subcollection's document
      function subCollectionDocumentHasField(fieldName) 
        return get(/databases/$(database)/documents/main_collection/$(mainColDoc)/sub_collection/another_doc).data.keys().hasAny([fieldName]);
        //return get(/databases/$(database)/documents/main_collection/some_doc/sub_collection/another_doc).data.keys().hasAny([fieldName]);
      
    
  

subCollectionDocumentHasField 函数检查文档another_doc 上是否存在someFieldValue 字段。在此函数中,如果我将$(mainColDoc) 变量替换为硬编码的文档ID some_doc,则list 操作成功。由于$(database) 路径变量可以在这种情况下使用,我希望其他人也可以。

这是错误还是预期行为?

【问题讨论】:

【参考方案1】:

这实际上是预期的行为,您不能使用 Firebase 的规则来过滤查询结果


一个典型的场景是有消息的集合,其中每条消息都指向它的创建者。

您不能简单地添加一条规则,即只允许对创建者是经过身份验证的用户的邮件进行阅读,从而自动过滤当前经过身份验证的用户的邮件。

唯一的方法是在客户端使用过滤器(或通过云功能)进行查询。


文档对此非常清楚:

在编写查询以检索文档时,请记住安全性规则不是过滤器——查询是全部或全部。为节省您的时间和资源,Cloud Firestore 根据您的所有文档的潜在结果集而不是实际字段值评估查询。如果查询可能返回客户端无权读取的文档,则整个请求将失败。

From Firebase's documentation

【讨论】:

感谢收看。虽然您引用的功能可能是一个因素,但它并没有解决 为什么 用硬编码的文档 ID 替换 $(mainColDoc) 变量允许 list 成功;也就是说,安全规则按我的预期工作,根据子集合的文档字段的存在允许/拒绝对文档的访问。由于我在两种情况下都使用相同的查询(现在在问题中),如果您引用的内容有问题,我希望查询无论如何都会失败,因为“潜在结果集”“可能包含违反……安全规则”。 使用此规则条件get(/databases/.../.../.../some_doc/.../another_doc).data.keys().hasAny([fieldName]),其中some_doc 是硬编码的。该条件仅每个查询检查一次,因为它不是每个文档的条件。它评估似乎在集合中的another_doc,但它可能是您数据库中的任何其他文档。 有了这个其他规则条件get(/databases/.../.../.../$(mainColDoc)/.../another_doc).data.keys().hasAny([fieldName])。规则更加动态,因为它应该针对每个 $(mainColDoc)possible 值的每个嵌套文档进行测试。 每文档条件不适用于查询,因为它消耗太多资源。这就是您无法使用规则过滤查询的原因。 从表面上看,您对每个查询评估的解释是有道理的。您对此有官方参考吗?我之所以问,是因为 Firestore 团队的另一个 SO 答案 ***.com/a/49440901 建议使用函数 parentDoc() 来查询 read 上的父文档数据,其中包括 listparentDoc() 函数 is 在其路径中使用了一个变量。诚然,这与我正在尝试的方向相反(即查询父文档的子文档),但在 parentDoc() 的上下文中,它仍然需要查询每个文档才能工作。 @cokeman19,这是我从上述链接 (documentation) 和我对 Firestore 的个人经验中得到的理解。我认为 firestore 规则很懒惰,因为它们不会遍历列表的每个项目来检查规则(即使列表为空)。从您指出的另一个线程来看,规则是allow read, write: if get(/databases/../../$(performanceId)).data.owner = request.auth.uid;list 将起作用,因为在查询(嵌套)场景时只有一个 $(performanceId) 需要检查。【参考方案2】:

我向 Google 开了一张票,并有效地确认了 @José 从使用情况中推断出的内容,即“每次查询只检查一次安全规则”。

为了澄清,虽然list 操作上的安全规则通常查询文档的内容(以避免潜在的性能不佳),但至少有一个条件 em>将查询文档的内容。这是安全规则保证只返回一个文档的情况。当满足这个保证时,会查询单个文档的内容,因为可以保持高性能;与get 操作相同。

因此,在我的问题的链接示例中,list 操作的规则是引用父文档,这一保证得到满足,并且将查询父文档的内容。

此外,在我的示例中,list 操作的规则引用了硬编码的文档 ID,此保证得到满足,并且将查询硬编码文档的内容。

为了明确说明,对于 list 操作,在 Firestore 无法保证其规则仅查询单个文档的任何情况下,访问将被设计为自动拒绝。

【讨论】:

【参考方案3】:

重申其他答案所说的内容,但表述方式略有不同:查询必须与安全规则一致,在查看任何查询文档之前,否则将失败并获得许可拒绝。

例如,如果子集合中的所有文档发生都与安全规则匹配(例如,您的创建和列出规则都要求 owner 字段为“X”),查询仍然必须符合安全规则(例如,查询还必须过滤 owner 是“X”),否则它将失败并出现权限被拒绝错误,与子集合的实际内容无关。

【讨论】:

以上是关于在 Firestore 安全规则“列表”操作中使用变量的主要内容,如果未能解决你的问题,请参考以下文章

Firestore安全规则奇怪的行为

无法为 Android 应用程序的特定经过身份验证的用户 (uid) 编写***集合(列表权限)的 Firestore 安全规则

在 Cloud Firestore 中如何制作一个,只创建一次安全规则

在不使用 Firebase 身份验证的情况下设置 Firestore 安全规则

Firestore:删除文档和安全规则

匿名身份验证用户的这个 Firebase/Firestore 安全规则是不是安全?