You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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未定义有关。


解决方案

  1. 修复自定义函数的空值判断
    自定义函数未处理嵌套对象不存在的情况,导致规则评估时触发未定义错误。修改函数如下:
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);
}
  1. 修复管理员权限函数的空值判断
    普通用户的auth token中没有isBoss字段,直接访问会导致未定义错误。修改isBoss函数:
function isBoss() {
  return request.auth != null && request.auth.token.isBoss == true;
}
  1. 测试用例初始化嵌套对象
    在测试的初始化阶段给文档添加空的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 07:54:58