【问题标题】:Firebase Firestore: custom admin accessFirebase Firestore:自定义管理员访问权限
【发布时间】:2017-11-07 09:31:36
【问题描述】:

在 Firebase Firestore 中,我试图只允许(自定义分配的)管理员写入/更新/删除资源,为此我制定了以下安全规则:

service cloud.firestore {
  match /databases/{database}/documents {
    match /resources {
      allow read;
      allow write, update, delete: if get(/users/$(request.auth.uid).isAdmin);
    }
    match /resources/{resource} {
      allow read;
      allow write, update, delete: if get(/users/$(request.auth.uid).isAdmin);
    }
  }
}

我正在使用 users 集合中标记为管理员的用户登录:

NfwIQAjfNdS85yDvd5yPVDyMTUj2 是从身份验证窗格获取的 UID:

但是,由于某种原因(更新: 原因已确定;请参阅答案),我在写入 资源 时遇到 PERMISSION_DENIED 错误绝对确定我已使用管理员用户登录后收集。

也许可以从 Firestore 查看请求日志?然后我可以看看request.auth.uid 的样子,以使其与我的集合和规则相匹配。

【问题讨论】:

标签: firebase google-cloud-firestore admin rules


【解决方案1】:

在写我的问题时,我成功了!我犯了两个错误,如果我正确阅读文档,这两个错误都是可以避免的。

首先,所有对service-defined function get 的调用都需要在路径前加上/databases/$(database)/documents/。所以这条规则:

allow write: if get(/users/$(request.auth.uid)).isAdmin;

变成这样:

allow write: if get(/databases/$(database)/documents/users/$(request.auth.uid)).isAdmin;

它很长,我知道,但就是这样。不过,我不确定为什么 Firestore 不能自己做到这一点,因为相同的路径前缀在所有对 get 的调用中都将保持不变,但这可能是为了一些尚未准备好的未来功能然而,像跨数据库查询什么的。

第二,get 函数将返回一个resource,而您需要调用.data 以获取其中包含的实际数据。因此,不要这样做:

get(/path/to/user/).isAdmin

你需要这样做:

get(/path/to/user/).data.isAdmin

现在我只希望我能够将该逻辑提取到user-defined function:

function isAdmin() {
  return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.isAdmin;
}

但是这样做会再次导致 PERMISSION_DENIED,并且在不知道函数中实际发生了什么的情况下,我不确定现在是否会花更多时间来解决这个问题。

更新: @Hareesh pointed out 必须在匹配器的范围内定义函数,因此可以将函数放在默认的顶级匹配器中,如下所示:

service cloud.firestore {
  match /databases/{database}/documents {
    function isAdmin() {
      return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.isAdmin == true;
    }

    // ...
  }
}

【讨论】:

  • 对于遵循此方法的任何人,请注意,如果您向用户提供写入权限以写入他们自己的用户文档/users/${userId},精明的敏锐用户可以将属性 isAdmin 设置为 True,并且将被视为管理员。为防止这种情况发生,您将创建一个安全规则以拒绝使用 isAdmin 向用户 doc 写入请求,或者将该 isAdmin 属性保存在用户没有写入权限的单独的 firestore 文档中。
  • 确实如此。您绝对不希望用户能够将自己设置为管理员。 ?
  • 虽然这是一个很好的方法并且显示在 firebase 文档中,但是每次安全规则 evals 时都需要一个读取数据库。自定义声明更适合“基于角色”的访问,从而简化了安全规则。见这里firebase.google.com/docs/auth/admin/custom-claims
【解决方案2】:

我注意到的几点

match /resources 指向一个集合,该规则对其文档没有影响。这里我引用doc

集合规则不适用于该集合中的文档。在集合级别而不是文档级别编写安全规则是不寻常的(并且可能是错误)。

因此您不必为集合编写规则

然后在规则allow write, update, delete: 中,您可以说allow write: 或特别是allow create, update, delete: 三个选项中的任何一个或组合它们。

试试这个

service cloud.firestore {
    match /databases/{database}/documents {
      match /resources/{resource} {

        function isAdmin() {
            return get(/databases/$(database)/documents/users/$(request.auth.uid)).isAdmin ||
            get(/databases/$(database)/documents/users/$(request.auth.uid)).data.isAdmin;
        }

        allow read;
        allow create, update, delete: if isAdmin();
    }
  }
}

【讨论】:

  • 哦,太好了!我没有注意到关于集合级别规则的部分。谢谢!另外,我之前实际上有create, update, delete,然后我只是尝试了不同的东西以使其工作,但你是对的,那应该只是write。将尝试将功能放入匹配器中,但在我看来,这违背了目的。 :-)
  • 是的,您只使用函数进行多个验证,例如 here
  • 因此将函数放在顶级匹配器(/databases/{database}/documents)中可以正常工作!那么就没有问题了,真的。谢谢@Hareesh,我会更新我的答案。 :-)
猜你喜欢
  • 1970-01-01
  • 2018-09-16
  • 1970-01-01
  • 2021-10-28
  • 2013-12-21
  • 2021-01-29
  • 2021-02-28
  • 1970-01-01
  • 2018-11-14
相关资源
最近更新 更多