【问题标题】:Firestore rules - Allow only update certain field of a documentFirestore 规则 - 只允许更新文档的特定字段
【发布时间】:2019-01-17 04:26:18
【问题描述】:

我的目标

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


我的用户文档

/* 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.daraentire目标文档after的变化。

  • 对于update 操作,上述内容仍然适用。我不太明白文档的含义:

对于只修改文档子集的更新操作 字段,request.resource 变量将包含 pending 操作后的文档状态

ref


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

function existingData() { return resource.data }
function expectedData() { return request.resource.data }
  • 检查更新后是否添加了请求者的uid
function isAddingRequester() {
  return expectedData().connectedUsers[requesterId()] != null
}
  • 检查更新后是否只有10 项添加到connectedUsers0 如果请求者已经在列表中。
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 上面引用的文档是什么意思?

  • 我的规则实施是否反映了我的意图?搜索了一下,发现模拟器可能有bug,一头雾水。

  • 在我的isNotChangingOtherFields 函数中,我能否直接将profile 对象与== 运算符进行比较?


更新 - 2018 年 1 月 17 日下午 3 点

删除了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()而不是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;

【问题讨论】:

  • 一篇文章中有太多问题。 “我对规则如何工作的理解:request.resource.dara 是更改后的整个目标文档。对于更新操作,上述内容保持不变。”那是正确的。文档也是这么说的(使用“待定”表示您所谓的“更改后”),尽管我承认它可能更清楚。
  • 您的规则中的expectedData() 是什么?另外:代码有什么问题?它是否拒绝您要允许的写入?还是允许您想要拒绝的写入?
  • 您是否考虑过分解总体规则的每一部分并单独测试,完全相互独立,而不是尝试一次调试整个事情?
  • @FrankvanPuffelen 感谢您的解释。我在帖子底部添加了一些调试更新。
  • @DougStevenson 感谢您的建议g,我已经进行了一些调试并在我的帖子中进行了更新。

标签: google-cloud-firestore firebase-security


【解决方案1】:

看起来有一个非常有用的 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

【讨论】:

    【解决方案2】:

    Firestore 中,request.resource 不代表更新后的文档(好吧,对于 create 它确实如此,因为在那里指定了所有字段)。它代表传入的数据,您希望在文档中更改的数据,可能还有一些额外的字段...

    要在相关字段更新后获取文档,您需要使用getAfter(/databases/$(database)/documents/path-to-doc).data

    所以预期的数据应该改为:

    function expectedData(path) {
      return getAfter(/databases/$(database)/documents/$(path));
    }
    

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

    【讨论】:

    • request.resource.data 表示文档成功写入操作之后。它将包含现有资源中存在的字段,即使传入资源中缺少这些字段。
    猜你喜欢
    • 2020-08-17
    • 2019-03-12
    • 2020-05-31
    • 1970-01-01
    • 1970-01-01
    • 2020-01-28
    • 1970-01-01
    • 1970-01-01
    • 2021-07-08
    相关资源
    最近更新 更多