Firestore安全规则:嵌套对象MapDiff权限控制异常排查
Firestore安全规则:限制嵌套对象特定字段更新问题
核心问题
配置Firestore安全规则时,顶层字段的更新限制可以正常工作:
allow update: if request.auth.uid == userID && request.resource.data.diff(resource.data).affectedKeys().hasOnly(["key1", "key2"]);
但尝试限制嵌套对象someData内的字段更新时,以下两种逻辑均无效:
allow update: if request.auth.uid == userID && request.resource.data.diff(resource.data).affectedKeys().hasOnly("someData", "otherData") && (!("someData" in request.resource.data.keys()) || (request.resource.data["someData"].diff(resource.data["someData"]) .affectedKeys().hasOnly(["key1", "key2"]));
或者:
allow update: if request.auth.uid == userID && request.resource.data.diff(resource.data).affectedKeys().hasOnly("someData", "otherData") && (!("someData" in request.resource.data.keys()) || (request.resource.data.someData.diff(resource.data.someData) .affectedKeys().hasOnly(["key1", "key2"]));
场景补充(出租车预订应用)
原规则正常运行,添加管理员权限后出现问题:管理员拥有自定义声明isBoss: true,规则允许其读写所有文档:
match /{document=**} { allow read, write: if isBoss(); } function isBoss() { return request.auth.token.isBoss == true; }
管理员在用户的driversData中添加了用户无权修改的字段(如approvedDriver: true)后,用户无法再更新driversData——因为请求对象会包含所有字段,触发规则限制。
为此修改规则,使用自定义函数替换原有的request.resource.data.driversData.keys().hasOnly(allowedFields):
(editOnlyFieldsNested("driversData", ["licensingAuthority", "amountOfVehicles", "DVLACheckCode", "DVLALicence", "PHBadge", "PHDriversLicence"]))
函数定义:
function editOnlyFieldsNested(nestedObj, allowedFields) { return request.resource.data[nestedObj].diff(resource.data[nestedObj]).affectedKeys().hasOnly(allowedFields); }
测试失败情况
使用Mocha测试环境+Firestore模拟器测试时,测试用例如下:
it("Can update a user document only with allowed fields", async () => { await testEnv.withSecurityRulesDisabled((context) => { return context.firestore().collection("users").doc(myId).set({ name: "User Abc", email: "abs@ruxus.taxi", createdAt: serverTimestamp(), }); }); const testDoc = myUser.firestore().collection("users").doc(myId); await assertSucceeds( testDoc.update({ name: "New Name", phoneNumber: "after", driversData: { amountOfVehicles: 1, licensingAuthority: "Bristol" }, lastUpdate: serverTimestamp(), }) ); });
旧规则(无MapDiff)测试通过,新规则下必须去掉driversData属性才能通过,否则报错:
FirebaseError: 7 PERMISSION_DENIED: Property isBoss is undefined on object. for 'update' @ L8, evaluation error at L17:24 for 'update' @ L17, Property isBoss is undefined on object. for 'update' @ L8, Property driversData is undefined on object. for 'update' @ L17
推测问题与driversData未定义有关。
解决方案
- 修复自定义函数的空值判断
自定义函数未处理嵌套对象不存在的情况,导致规则评估时触发未定义错误。修改函数如下:
function editOnlyFieldsNested(nestedObj, allowedFields) { // 原文档和请求都没有嵌套对象,允许操作 if (!(nestedObj in resource.data) && !(nestedObj in request.resource.data)) { return true; } // 原文档无嵌套对象,检查新对象的字段是否全在允许列表内 if (!(nestedObj in resource.data)) { return request.resource.data[nestedObj].keys().hasOnly(allowedFields); } // 请求中无嵌套对象,不修改原字段,允许操作 if (!(nestedObj in request.resource.data)) { return true; } // 对比嵌套对象的差异字段,仅允许修改指定字段 return request.resource.data[nestedObj].diff(resource.data[nestedObj]).affectedKeys().hasOnly(allowedFields); }
- 修复管理员权限函数的空值判断
普通用户的auth token中没有isBoss字段,直接访问会导致未定义错误。修改isBoss函数:
function isBoss() { return request.auth != null && request.auth.token.isBoss == true; }
- 测试用例初始化嵌套对象
在测试的初始化阶段给文档添加空的driversData,避免更新时原文档无该字段的情况:
await testEnv.withSecurityRulesDisabled((context) => { return context.firestore().collection("users").doc(myId).set({ name: "User Abc", email: "abs@ruxus.taxi", createdAt: serverTimestamp(), driversData: {} // 初始化空嵌套对象 }); });
内容的提问来源于stack exchange,提问作者waway
相关产品推荐
相关产品推荐

