【问题标题】:Firestore: How to query a map of roles inside a subcollection of documents?Firestore:如何查询文档子集中的角色映射?
【发布时间】:2021-09-04 16:27:36
【问题描述】:

我有以下子集合结构:

/projects/{projectId}/documents/{documentId}

每个文档都有一个access 映射,用于检查每个文档的安全规则:

{
  title: "A Great Story",
  content: "Once upon a time ...",
  access: {
    alice: true,
    bob: false,
    david: true,
    jane: false
    // ...
  }
}

如何设置安全规则:

match /{path=**}/documents/{documentId} {
      allow list, get: if resource.data.access[request.auth.uid] == true;
}

当我想查询 alice 可以跨所有项目访问的所有文档时:

db.collectionGroup("documents").where(`access.${uid}`, "==", true);

我收到一条错误消息,告诉我需要为 access.aliceaccess.david 等创建集合组索引。

如何克服集合组查询的索引问题?

【问题讨论】:

    标签: firebase indexing google-cloud-firestore firebase-security user-roles


    【解决方案1】:

    作为索引的快速概述,有两种类型 - 复合索引(用于将多个字段连接在一起)或单字段索引。默认情况下,对于普通集合查询,会为文档的每个字段自动创建单字段索引,而对于集合组查询,则会禁用单字段索引。如果字段的值是一个数组,则该数组的值将被添加到 Array Contains 索引中。如果字段的值是原始值(字符串、数字、时间戳等),则文档将添加到该字段的 AscendingDescending 索引中。

    要启用对集合组查询的access 字段的查询,您必须创建索引。虽然您可以单击查询时收到的错误消息中的链接,但这只会为您正在查询的用户添加索引(例如access.aliceaccess.bob 等)。相反,您应该使用 Firebase Console 告诉 Cloud Firestore 为 access 创建索引(这将为它的每个子项创建索引)。打开控制台后,使用“添加豁免”按钮(考虑使用“豁免”来表示“覆盖默认设置”)定义新的索引规则:

    在第一个对话框中,使用以下设置:

    Collection ID:      "documents"
    Field Path:         "access"
    
    Collection:         Checked ✔
    Collection group:   Checked ✔
    

    在第二个对话框中,使用以下设置:

    Collection Scope Collection Group Scope
    Ascending Enabled Enabled
    Descending Enabled Enabled
    Array Contains Enabled Enabled

    在您的安全规则中,您还应该在进行相等之前检查目标用户是否在access 映射中。访问缺少的属性时会抛出“对象上的属性未定义”。拒绝访问的错误,如果您稍后将语句与|| 组合在一起,这将成为一个问题。

    要解决此问题,您可以使用:

    match /{path=**}/documents/{documentId} {
      allow list, get: if request.auth.uid in resource.data.access
                       && resource.data.access[request.auth.uid] == true;
    }
    

    或在未找到所需键时提供备用值:

    match /{path=**}/documents/{documentId} {
      allow list, get: if resource.data.access.get(request.auth.uid, false) == true;
    }
    

    作为违反规则的示例,假设您希望“员工”用户能够阅读文档,即使他们不在该文档的 access 映射中。

    这些规则总是会出错并失败(如果工作人员的用户 ID 不在 access 映射中):

    match /{path=**}/documents/{documentId} {
      allow list, get: if resource.data.access[request.auth.uid] == true
                       || get(/databases/$(database)/documents/users/$(request.auth.uid)).data.get("staff", false) == true;
    }
    

    然而,这些规则会起作用:

    match /{path=**}/documents/{documentId} {
      allow list, get: if resource.data.access.get(request.auth.uid, false) == true
                       || get(/databases/$(database)/documents/users/$(request.auth.uid)).data.get("staff", false) == true;
    }
    

    附带说明,除非您需要查询用户无权访问的文档,否则从访问映射中简单地省略该用户会更简单。

    {
      title: "A Great Story",
      content: "Once upon a time ...",
      access: {
        alice: true,
        david: true,
        // ...
      }
    }
    

    【讨论】:

    • 感谢您的详细解答!一个后续问题是,如果我在上面的查询中添加orderBy("createdDate") 会怎样?该索引是否适用于复合查询?
    • 注意:-stackoverflow.com/questions/50980243/…-stackoverflow.com/questions/65767799/… 显然我必须将访问映射去特征化为一个数组才能使用orderBy()
    • @Hernantz 添加异常只是告诉 Firestore 在 access 下构建单字段索引。正如您所确定的,为orderBy 使用不同的字段需要一个复合索引(将两个或多个单字段索引合并在一起)。您还注意到,为了只构建一个复合索引而不需要为access 下的每个字段创建一个复合索引,您可以使用数组字段。因此,在access 下添加一个名为accessMembers 的用户ID/用户名数组。使用这种结构,您甚至可以使用access 为给定的帖子分配角色(编辑、贡献者等)。
    【解决方案2】:

    如@Jim 所述,您必须为其创建一个索引。 如果您不想深入了解它,您可以捕获错误,它将有一个直接链接来创建一个,如下所示:

    
    db.collectionGroup("documents").where(`access.${uid}`, "==", true).get().then((snap) => {
      //
    }).catch((e) => console.log(e))
    

    文档中提到的其他方法包括:

    在使用集合组查询之前,您必须创建一个索引 支持您的集合组查询。您可以通过创建索引 错误消息、控制台或 Firebase CLI。

    【讨论】:

      【解决方案3】:

      正如https://cloud.google.com/firestore/docs/query-data/queries#collection-group-query 中所述,“在使用集合组查询之前,您必须创建一个支持您的集合组查询的索引。您可以通过错误消息、控制台或 Firebase CLI 创建索引。”

      在这种情况下,您需要在访问映射 (https://cloud.google.com/firestore/docs/query-data/indexing#add_a_single-field_index_exemption) 上启用 CollectionGroup 查询

      【讨论】:

        猜你喜欢
        • 2020-05-08
        • 1970-01-01
        • 2020-07-26
        • 1970-01-01
        • 2021-09-25
        • 2019-04-14
        • 2021-04-08
        • 1970-01-01
        • 2019-09-25
        相关资源
        最近更新 更多