Firebase安全规则会阻止读取单个文档,但会在完整集合查询中返回文档

时间:2019-11-21 21:29:57

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

我在Firestore中有一个播放器文档集合。我想将其中一些文档标记为私有,这样就不会对其提出质疑。我的数据的JSON转储如下:

[
  {
    "id": "H0ycPIqXB5pX5VmdYlmY",
    "name": "Tim",
  },
  {
    "id": "VICMGdutgIN7PUjG571h",
    "name": "Zoe",
  },
  {
    "id": "query-blocker",
    "name": "Don't look here",
    "private": true
  },
  {
    "id": "zYkhO5f7gYPe2VgqQQXe",
    "name": "Bob"
  }
]

现在应用此安全规则,旨在保护标有private的任何文档:

match /players/{document=**} {
    allow read: if !('private' in resource.data);
}

结果:

  • 用于读取包含字段private的单个文档的查询正确返回了权限被拒绝错误。
  • 查询以读取集合中的所有文档成功返回了集合中的所有文档,包括标记为private的所有文档。

似乎所有文档的查询也应该失败(我知道security rules are not filters)。在这里我有误会吗?

以下是使用模拟器的问题的有效示例:https://github.com/Keltin42/firebase-it-rulestest

这是您可以从命令行运行的简化示例:

'use strict';

const firebase = require('firebase');
require('firebase/firestore');

firebase.initializeApp({
    apiKey: 'your api key here',
    projectId: 'your project id here'
});
const db = firebase.firestore();

async function doTest() {
    const playersCollection = db.collection('players');
    await playersCollection.add({ name: 'Sue' });
    await playersCollection.add({ name: 'Bob' });
    await playersCollection.doc('good').set({ name: 'Fred' });
    await playersCollection.doc('query-blocker').set({ name: 'Tim', private: true });

    // Read a good document.
    await playersCollection.doc('good').get().then(doc => {
        console.log('The good document: ', JSON.stringify(doc.data()));
    });

    // Read all the documents
    await playersCollection.get().then(querySnapshot => {
        console.log('All documents: ');
        querySnapshot.forEach(doc => {
            console.log('\t', doc.id, ' => ', doc.data());
        });
    });

    // Read the query-block document
    await playersCollection.doc('query-blocker').get().then(doc => {
        console.log('The query-blocker document: ', JSON.stringify(doc.data()));
    }).catch(error => {
        console.error('Error retrieving query-blocker document: ', error);
    });
}

doTest();

具有安全规则:

service cloud.firestore {
  match /databases/{database}/documents {
    match /players/{document=**} {
      allow write;
      allow read: if !('private' in resource.data);
    }
  }
}

1 个答案:

答案 0 :(得分:0)

您在此处创建了一个非常复杂的方案,该方案隐藏在建议的解决方案后面(请参阅what is the XY problem)。我们可以从您的用例中看出来的合理答案是简化您的数据和所需的查询。还要认识到security rules are not filters,并且您不能对集合执行get(),并期望它代表您过滤结果。

首先为所有没有值的现有记录设置private: false(您不能查询缺少的东西或缺少的值)。

然后像这样设置您的规则:

match /players/{playerId} {
   allow read: if resource.data.private == false;
}

您的查询是这样的:

playersCollection.where("private", "==", false).get()

执行查询时,查询必须匹配规则才能成功(数据实际上未检查为this can't scale)。所以您的目标是匹配这两个。

通常来说,避免使用glob(例如document=**),因为它们会带来潜在的安全问题(在安全性方面通常不建议使用隐式允许;最好使用显式允许)。