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