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

时间:2019-01-17 04:26:18

标签: google-cloud-firestore firebase-security-rules

我的目标

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


我的用户文档

/* 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变量将包含待处理   操作后的文档状态

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是什么意思?

  • 我的规则执行是否反映了我的意图?我四处搜寻后感到困惑,得知模拟器可能有错误。

  • 在我的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 个答案:

答案 0 :(得分:5)

似乎有一个非常有用的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

答案 1 :(得分:0)

Firestore 中,request.resource并不代表更新后的文档(嗯,对于 create ,它代表文档,因为所有字段都在此指定)。它代表传入的数据,您想要在文档中更改的数据,可能还有一些其他字段...

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

因此,预期数据应更改为:

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

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