包含数组的查询的 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 安全规则的主要内容,如果未能解决你的问题,请参考以下文章