首页 > 解决方案 > 基于角色的访问的 Firestore 安全规则

问题描述

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

users在地图下有一个包含组成员身份的集合,该集合roles具有两个级别的组权限:“成员”和“管理员”

示例用户文档:

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

我想让user文档的所有者能够:

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

示例单元测试:

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集合结构的建议,同时实现使组管理员能够管理组成员身份的相同目标。

标签: firebasegoogle-cloud-firestorefirebase-security

解决方案


推荐阅读