首页 > 解决方案 > Firestore 安全规则:使用 Set.hasAny() 时出现空值错误

问题描述

我有一个user包含两个字段memberOfmanagerOf(即,一个组织的集合;两者都是文档 ID 数组)的集合。

我想限制经理仅列出属于他们管理的组织的成员的用户。

在 JS 中,它会是这样的:

const memberOf = [1, 2, 3, 4, 5]
const managerOf = [6, 7, 1, 9, 0]

console.log(memberOf.some(el => managerOf.includes(el))) //  returns true

这是我到目前为止所拥有的:

function isSignedIn() {
    return request.auth.uid != null
}

function isAdmin() {
    return isSignedIn() && 'admin' in request.auth.token && request.auth.token.admin
}

match /users/{userId} {
    allow get: if isSignedIn() && (request.auth.uid == userId || isAdmin());
    allow list: if isAdmin() || ???; //  how can I express the above condition?
    allow write: if isAdmin();
}

这就是查询:

const unsubscribe = db.collection('users')
    .where('memberOf', 'array-contains', organisationId)
    .orderBy('email', 'asc')
    .onSnapshot(snap => {
        console.log(`Received query snapshot of size ${snap.size}`)
        var docs = []
        snap.forEach(doc => docs.push({ ...doc.data(), id: doc.id }))
        actions.setMembers(docs)
    }, error => console.error(error))

首先,我想organisationId在安全规则中使用来自请求的,但它不可用,因为它不是写操作(https://firebase.google.com/docs/reference/rules/rules.firestore.Request#resource

我想过:

function hasMemberManagerRelationship(userId) {
    return isSignedIn() && get(/databases/$(database)/documents/users/$(userId)).data.memberOf in get(/databases/$(database)/documents/users/$(request.auth.uid)).data.managerOf
}

match /users/{userId} {
    allow get: if isSignedIn() && (request.auth.uid == userId || isAdmin());
    allow list: if isAdmin() || hasMemberManagerRelationship(userId);
    allow write: if isAdmin();
}

或者

function hasMemberManagerRelationship(userId) {
    return isSignedIn() && get(/databases/$(database)/documents/users/$(userId)).data.memberOf.toSet().hasAny(get(/databases/$(database)/documents/users/$(request.auth.uid)).data.managerOf.toSet())
}

https://firebase.google.com/docs/reference/rules/rules.Set#hasAny

但它不工作,我有错误FirebaseError: Null value error. for 'list' @ L27。最重要的是,这可能会产生大量额外的读取操作(未在计费方面进行优化)。

我可以执行以下操作:

allow list: if isAdmin() || (isManagerOf('jJXLKq7p9wWSNLsHcVIn') && 'jJXLKq7p9wWSNLsHcVIn' in resource.data.memberOf);

组织的 id在哪里jJXLKq7p9wWSNLsHcVIn(并在查询中使用),但我不知道如何从请求“上下文”中检索 id..

任何帮助,将不胜感激!

标签: firebasegoogle-cloud-firestorefirebase-security

解决方案


好的。首先,感谢@Doug Stevensondebug()在另一篇文章中提到!我不知道它的存在,它摇摆不定!

debug(resource.data.memberOf)调试日志中的结果是:

constraint_value {
  simple_constraints {
    comparator: LIST_CONTAINS
    value {
      string_value: "jJXLKq7p9wWSNLsHcVIn"
    }
  }
}

LIST_CONTAINS强迫我看看列表:https ://firebase.google.com/docs/reference/rules/rules.List#hasAny

toSet()不适用于列表,但列表已经具有该hasAny()功能。 (事实上​​,它确实存在,但在我的情况下不起作用)

最后,这条规则有效:

function hasMemberManagerRelationship() {
    return isSignedIn() && resource.data.memberOf.hasAny(getUser(request.auth.uid).data.managerOf)
}

现在我只是想知道是否getUser(request.auth.uid).data.managerOf以某种方式缓存(1 读取多个用户条目)或每次重新运行(100 个用户,100 次额外读取)。

对此有什么想法吗?

我真诚地希望这是第一个案例^^


推荐阅读