Firestore规则 - 只允许更新文档的某些字段

7

我的目标

我希望只允许用户更新其他用户文档中的特定字段。


我的用户文档

/* BEFORE */
{
  id: 'uid1',
  profile: { /* a map of personal info */ },
  connectedUsers: {
    uid2: true,
    uid3: true,
  }
}

/* AFTER */
{
  id: 'uid1',
  profile: { /* a map of personal info */ },
  connectedUsers: {
    uid2: true,
    uid3: true,
    uid4: true, // <--- added.
  }
}

请求

const selfUserId = 'uid4';

db.runTransaction(function(transaction) {

    return transaction.update(userDocRef).then(function(userDoc) {

        if (!userDoc.exists) { throw "Document does not exist!"; }

        transaction.update(userDocRef, 'connectedUsers.${selfUserId}', true);
    });
}

我对规则工作原理的理解:

  • request.resource.data 是在更改后整个目标文档

  • 对于 update 操作,以上内容仍然有效。我不太明白文档中所说的:

对于仅修改文档字段子集的更新操作,request.resource 变量将包含待处理文档状态

参考链接


我的规则: (见下面的更新)

function existingData() { return resource.data }
function expectedData() { return request.resource.data }
  • 检查更新后是否添加了请求者的uid
function isAddingRequester() {
  return expectedData().connectedUsers[requesterId()] != null
}
  • 检查更新后是否只添加了 10 个项目到connectedUsers。如果请求者已经在列表中,则为0
function isAddingOneAtMost() {
  return expectedData().connectedUsers.size() == existingData().connectedUsers.size() + 1
  || expectedData().connectedUsers.size() == existingData().connectedUsers.size()
}
  • 检查更新后用户文档的所有其他字段是否未更改。
function isNotChangingOtherFields() {
  return expectedData().id == existingData().id
  && expectedData().profile == existingData().profile
}

我的问题

  • 我对Firestore规则的理解正确吗?上面提到的文档中的“pending document state”是什么意思?

  • 我的规则实现反映了我的意图吗?在搜索并学习模拟器可能存在错误后,我感到困惑。

  • 在我的isNotChangingOtherFields函数中,我能否使用==运算符直接比较profile对象?


更新-2018/01/17 3PM

已删除existingData()expectedData()

function isAddingRequester() {
  return request.resource.data.connectedUsers[requesterId()] != null
}

function isAddingOneAtMost() {
  return (request.resource.data.connectedUsers.size() == resource.data.connectedUsers.size() + 1)
  || (request.resource.data.connectedUsers.size() == resource.data.connectedUsers.size()) // NOTE: if the requester is already in the list.
}

function isNotChangingOtherFields() {
  return request.resource.data.profile == resource.data.profile
  && request.resource.data.id == resource.data.id
}

function isNotAddingOtherFields() {
  return request.resource.data.size() == resource.data.size()
}

调试结果

有趣的是,在模拟器和生产环境中,结果并不相同

// PASSED in simulator & production:      
allow update: if isAddingRequester();

// PASSED in simulator but NOT production:
allow update: if isNotChangingOtherFields();

// PASSED in simulator but NOT production:
allow update: if isNotAddingOtherFields();

// FAILED in both simulator AND production:
allow update: if isAddingOneAtMost();

// NOTE: inserted 2 mock data before update.
// PASSED in simulator:
allow update: if resource.data.connectedUsers.size() == 2;

// FAILED in simulator:
allow update: if request.resource.data.connectedUsers.size() == 3; 
// PASSED in simulator:
allow update: if request.resource.data.connectedUsers.size() == 1; 

问题

如果request.resource是更新后的文档,为什么request.resource.data.connectedUsers.size()为1,而不是3(2个现有+1个新添加)?

相关发现(来自模拟器)

如果我有一个函数:

expectedData() { return request.resource.data }

我得到了一些意想不到的结果:


// PASSED:
allow update: if request.resource.data.id == expectedData().id;

// FAILED if the order is changed.
allow update: if expectedData().id == request.resource.data.id;


2
一个帖子中有太多问题。 "我对规则如何工作的理解:request.resource.data是更改后的整个目标文档。对于更新操作,以上仍然成立。"那是正确的。文档也是这样说的(使用“待处理”来表示您所说的“更改后”),尽管我承认它可能不太清晰。 - Frank van Puffelen
你的规则中的expectedData()是什么?另外:代码有什么问题吗?它拒绝了你想要允许的写入吗?还是允许了你想要拒绝的写入? - Frank van Puffelen
你是否考虑过将整个规则分解成每个位,然后独立地测试每个位,而不是一次性尝试调试整个规则? - Doug Stevenson
@FrankvanPuffelen 谢谢您的解释。我已经在我的帖子底部添加了一些调试更新。 - czphilip
如果您关心控制台模拟和实际产品之间的差异,您可能希望开始使用模拟器进行工作,该模拟器允许您对实际评估规则的代码运行本地测试。这是未来测试规则的首选方式(已知某些方面控制台存在问题)。https://firebase.google.com/docs/firestore/security/test-rules-emulator - Doug Stevenson
显示剩余2条评论
2个回答

10

看起来有一个非常有用的MapDiff类型,可以简化您的规则。以下是文档中的示例代码:

// Compare two Map objects and return whether the key "a" has been
// affected; that is, key "a" was added or removed, or its value was updated.
request.resource.data.diff(resource.data).affectedKeys().hasOnly(["a"]);

https://firebase.google.com/docs/reference/rules/rules.MapDiff


0
在Firestore中,request.resource并不代表文档在更新后的状态(对于创建操作是这样的,因为所有字段都在那里指定了)。它代表传入的数据,即你想要更改的文档数据,可能还带有一些其他字段...
要获取在所涉及字段更新后的文档之后的状态,你需要使用getAfter(/databases/$(database)/documents/path-to-doc).data 所以你应该期望数据被更改为:
function expectedData(path) {
  return getAfter(/databases/$(database)/documents/$(path));
}

Path 表示您实际控制的路径,而 database 变量默认在规则顶部定义为数据库名称...


1
request.resource.data 表示成功写入操作后的文档。它将包含现有资源上存在的字段,即使这些字段在传入的资源中缺失。 - nicoqh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接