包含数组的查询的 Firestore 安全规则

Posted

技术标签:

【中文标题】包含数组的查询的 Firestore 安全规则【英文标题】:Firestore Security Rules for Query with Array Contains 【发布时间】:2019-11-25 14:00:56 【问题描述】:

我有一个 Flutter 应用程序,用户可以在其中发布帖子并将帖子标记为属于某个组。帖子存储在一个全局集合中,每个都有一个Post.groupId 字段:

/posts/postId

根据我的 Firestore 安全规则和查询,用户只有在帖子被标记的组(即帖子的 groupId 字段)中才能阅读帖子。批准的组用户存储在:

/groups/groupId/users/userId

我可以查询来自特定用户组的帖子,例如:

_firestore.collection('posts').where('groupId', isEqualTo: 'groupA')...

以上一切正常。

我正在尝试改进一个帖子可以标记在多个组中而不是一个组中,因此我将单个 Post.groupId 字段替换为 Post.groupIds 数组。如果用户是来自Post.groupIds 的任何组的成员,他/她应该能够阅读帖子。我尝试从我的 Flutter 应用程序中使用以下查询读取所有标记为特定组的帖子:

_firestore.collection('posts').where('groupIds', arrayContains: 'groupA')...

我不断收到以下异常 Missing or insufficient permissions 与这些安全规则:

match /posts/postId 
    allow read: if canActiveUserReadAnyGroupId(resource.data.groupIds);


function isSignedIn() 
    return request.auth != null;


function getActiveUserId() 
    return request.auth.uid;


function isActiveUserGroupMember(groupId) 
    return isSignedIn() &&
            exists(/databases/$(database)/documents/groups/$(groupId)/users/$(getActiveUserId()));


function canActiveUserReadAnyGroupId(groupIds) 
    return groupIds != null && (
            (groupIds.size() >= 1 && isActiveUserGroupMember(groupIds[0])) ||
            (groupIds.size() >= 2 && isActiveUserGroupMember(groupIds[1])) ||
            (groupIds.size() >= 3 && isActiveUserGroupMember(groupIds[2])) ||
            (groupIds.size() >= 4 && isActiveUserGroupMember(groupIds[3])) ||
            (groupIds.size() >= 5 && isActiveUserGroupMember(groupIds[4]))
            );

使用这些安全规则,我可以阅读单个帖子,但无法进行上述查询。是否有允许我进行此查询的安全规则?

更新 1

为完整性添加了isSignedIn()getActiveUserId() 安全规则功能。

更新 2

这是我尝试在本地使用 Firestore 模拟器执行此查询时收到的错误:

     FirebaseError: 
Function not found error: Name: [size]. for 'list' @ L215

第 215 行对应此规则中的 allow read 行:

match /posts/postId 
    allow read: if canActiveUserReadAnyGroupId(resource.data.groupIds);

【问题讨论】:

当您运行查询时,它只查找groupIds 包含groupA 的文档,然后返回该文档。无需检查整个 groupIds 数组,因为您只是在寻找 groupA。因此您只需要检查isSignedIn() && exists(/databases/$(database)/documents/groups/$(groupId)/users/$(getActiveUserId())) @Nathan,根据 OP 的问题,用户访问帖子的方式是他们是否可以访问任何组。检查所有组似乎是必要的 @DanFein 我的错,你是对的。 您可能会超过 firebase 对单文档请求和查询请求的 exists() 和 get() 调用的最大数量(只有 10 个)。超过限制会导致权限被拒绝错误。 另请注意,有一个规则模拟器可让您在本地测试规则,并提供更详细的消息以帮助您了解发生了什么。 cloud.google.com/firestore/docs/security/test-rules-emulator 【参考方案1】:

根据blog post,如果您可以维护给定帖子的成员 ID 索引(基于组分配),那么您可以保护帖子读取访问权限,将成员 ID 存储在数组数据类型中并与成员匹配规则集中带有“array-contains”子句的 ID。在您的 Firebase 规则中如下所示:

service cloud.firestore 
  match /databases/database/documents 
    match /posts/postId 
     allow read: if request.auth.uid in resource.data.members
     allow write: if request.auth.uid == resource.data.owner
    
  

【讨论】:

【参考方案2】:

目前,Firestore 似乎不支持此场景的安全规则(感谢您帮助追踪 Doug Stevenson)。我想出了一种机制来解决这个限制,并想分享一下,以防其他人正在处理这个问题。它需要额外的查询,但让我不必为了绕过安全规则而使用 Admin SDK 创建 Web API。

帖子存储如下(简化):

/posts/postId
- userId
- timestamp
- groupIds[]
- message
- photo

现在我添加了一个额外的帖子引用集合,它只存储指针信息:

/postRefs/postId
- userId
- timestamp
- groupIds[]

posts 集合将具有安全规则,这些规则会执行所有验证,以确保用户至少位于帖子被标记的组之一中。 Firestore 能够正确处理简单的 get 请求,但目前还不能处理 list 请求。

由于postRefs 集合仅存储 ID,而不存储可能在帖子中的敏感信息,因此可以放宽其安全规则,以便我仅验证用户是否已登录。因此,用户将执行帖子查询postRefs 集合以检索有序的 postId 列表,以便从 posts 集合中延迟加载。

客户向/从普通的posts 集合添加/删除帖子,然后有一个云函数将 ID 信息复制到 postRefs 集合。

【讨论】:

【参考方案3】:

如果我不得不猜测,我会说 groupIds 实际上不是 List 类型的对象,这意味着文档中的字段也不是数组。如果是字符串,则此代码不起作用,因为字符串在规则语言中没有称为 size() 的方法。

如果您不能 100% 确定字段的类型,则需要检查规则中的类型并确定如何处理它。您可以使用is 运算符来检查类型。例如,groupIds is list 将是布尔值 true,如果您实际使用的是一个。

在您的规则中,您可以使用debug() 函数将某些表达式的值转储到日志中。它将返回相同的值。因此,您可以说 debug(groupIds) != null 来打印该值并检查它是否为空。

【讨论】:

我确实验证了groupIds 是数据库文档中的一个数组。但是,我在规则中添加了 debug(groupIds) != null 调用,它会打印以下内容。 constraint_value simple_constraints comparator: LIST_CONTAINS value string_value: "groupA" 嗯,实际上,规则的工作方式,它不能为单独评估的每个文档提供一个值(它不会读取每个可能的文档 - 这不会扩展)。相反,它将提供查询外观的描述,以便规则可以更普遍地检查查询的有效性。 知道了,那么有没有办法将string_value 从约束中提取出来,以便我验证用户对该组的访问权限?我尝试了以下方法,但它给出了一个错误:groupIds.constraint_value.simple_constraints.value.string_value。这是我现在得到的错误:Type error. Received: [constraint] Expected: [map,path]. for 'list' @ L219 该值的行为类似于列表,但不完全是。您可以在这里做的是检查客户端给出的约束中是否存在某些内容。您应该可以说"groupA" in resource.data.groupId 来检查客户端发送的内容。我现在知道的就这么多了。我被告知resource.data.groupId[0] 不会。 我明白了。我认为你正在尝试做的事情目前无法使用安全规则。至少不是用于查询。单个文档获取应该没问题,因为您可以从正在获取的文档中访问整个列表。

以上是关于包含数组的查询的 Firestore 安全规则的主要内容,如果未能解决你的问题,请参考以下文章

Firebase Firestore - 基于“where”查询参数的安全规则

Firestore:查询和安全 (Swift)

Firestore安全规则奇怪的行为

FireStore - 如何绕过数组“不包含”查询

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

Firestore 安全规则 - 允许基于其父文档数据读取子集合