基于角色访问的Firestore安全规则

时间:2020-05-23 19:11:06

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

我正在尝试在Firestore数据库中实现基于角色的安全性,但是无法获取做出访问决策所需的必要数据。

我有一个users集合,其中包含roles映射下的组成员身份,并在两个级别上描述了组权限:“成员”和“管理员”

用户文档示例:

{
    name: "User1",
    roles: { group1: "admin", group2: "member"... }
}

我希望允许user文档的所有者能够:

  • 更新除roles映射以外的所有字段

此外,我希望作为组管理员的用户能够:

  • 修改其他用户的roles映射,但仅修改为其具有管理员角色的组

单元测试示例:

it("[users] group admin can update user's roles if admin of destination group", async () => {
    const admin = adminApp({ uid: "admin" });
    const alice = authedApp({ uid: "alice" });

    // Create user document for alice and make 'admin' of group1
    await firebase.assertSucceeds(
        admin.collection("users")
        .doc("alice")
        .set({ name: "Alice", roles: { group1: "admin" } })
    );

    // Create user document for bob but without any group membership
    await firebase.assertSucceeds(
        admin.collection("users")
        .doc("bob")
        .set({ name: "Bob" })
    );

    // Alice should be able to grant bob membership to group1
    // as she is an 'admin' of group1
    await firebase.assertSucceeds(
        alice.collection("users")
        .doc("bob")
        .update({ 'roles.group1': "member" })
    );

    // Alice should NOT be able to grant bob membership to group2
    // as she is not an 'admin' of group2
    await firebase.assertFails(
        alice.collection("users")
        .doc("bob")
        .update({ 'roles.group2': "member" })
    );
});

还有我到目前为止的规则:

match /databases/{database}/documents {
    function isSignedIn() {
        return request.auth != null;
    }

    function isOwner(rsc) {
        return isSignedIn() && request.auth.uid == rsc.id;
    }

    function notUpdating(field) {
        return !(field in request.resource.data)
            || resource.data[field] == request.resource.data[field]
    }

    function requestorIsAdminOfGroup(groupId) {
        return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.roles[groupId] == "admin"
    }

    function adminUpdateUsersRole() {
        // Get document before update
        let before = resource.data;

        // Get potential document after update 
        let after = getAfter(request.path).data;

        // Compare to find top level document keys with changes
        let affectedKeys = after.diff(before).affectedKeys();

        // Find keys in roles that have changes (groupIds)
        let affectedGroups = after.roles.diff(before.roles).affectedKeys();

        // What I'm trying to do here:
        // - Ensure that only the 'roles' field is changing by checking
        //   affected key name and size
        // - Ensure that a single groupId is being modified and that the
        //   requesting user is an 'admin' of that group
        //
        // Problem: how do I get the key name of the modified key from the 'roles' map
        // in order to pass to requestorIsAdminOfGroup?
        return affectedKeys.size() == 1 && affectedKeys.hasOnly(["roles"]) &&
            affectedGroups.size() == 1 && requestorIsAdminOfGroup(groupId)

    }

    match /users/{userId} {
        allow update: if (isOwner(resource) && notUpdating("roles")) || adminUpdateUsersRole();
    }
}

我的问题在上方adminUpdateUsersRole()

中以注释突出显示

如何在roles映射中获取表示groupId的更改密钥,以验证请求者是该组的管理员?

如果这不可能,那么我也乐于接受一些建议,这些建议可以更改users集合的结构,同时实现使组管理员能够管理组成员身份的相同目标。

0 个答案:

没有答案