【问题标题】:Firestore Security Rules for Query with Array Contains包含数组的查询的 Firestore 安全规则
【发布时间】: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

标签: firebase flutter google-cloud-firestore firebase-security


【解决方案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] 不会。
      • 我明白了。我认为你正在尝试做的事情目前无法使用安全规则。至少不是用于查询。单个文档获取应该没问题,因为您可以从正在获取的文档中访问整个列表。
      猜你喜欢
      • 2021-03-25
      • 2020-12-28
      • 1970-01-01
      • 2020-11-07
      • 1970-01-01
      • 1970-01-01
      • 2018-08-04
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多