Firestore安全规则:使用Set.hasAny()

时间:2020-10-27 18:23:38

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

我有一个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);

其中jJXLKq7p9wWSNLsHcVIn是组织的ID(并在查询中使用),但我不知道如何从请求“上下文”中检索ID。

任何帮助将不胜感激!

2 个答案:

答案 0 :(得分:1)

好的。首先,感谢@Doug Stevenson在另一篇文章中提到debug()!我不知道它的存在,而且它震撼了!

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

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

LIST_CONTAINS强迫我看一下List: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次)。

对此有何想法?

我衷心希望这是第一种情况^^

答案 1 :(得分:0)

我测试了一些规则,这些规则与您使用Firestore“ Rules Playground”的尝试非常相似,并且似乎可以正常工作。 您不需要get()当前的userId,因为您已经在resource对象中拥有了它。 我不确定它会产生更多的读取操作,因为我们仅将get()用于request.auth.uid

    function getUser(userId) {
        return get(/databases/$(database)/documents/users/$(userId));
    }

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

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

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

    match /users/{userId} {
        allow read: if isAdmin() || hasMemberManagerRelationship();
    }

您在哪里得到FirebaseError: Null value error