我一直在与Firebase安全性进行一段时间的斗争,而且我认为这种情况并不是很独特(但在文档中也没有涉及)。
想象一下,我有一棵树test_tree
,它位于我的Firebase数据库的根目录下。从那里开始,我在test_tree
下面有三个名为requiredString1
,requiredString2
和optionalString1
的孩子。
我希望Firebase的经过身份验证的用户能够写入test_tree
,并要求同时包含两个requiredString
子项,同时允许optionalString1
成为可选项。还有一个警告让我循环 - 虽然optionalString1
是可选的,但不应该允许删除。
因此,考虑到这些要求,我提出了以下安全规则:
"rules" {
"test_tree": {
//Define overall write rules
".write": "
auth !== null &&
newData.exists() //this is done to ensure that a deletion of this tree cannot occur
",
".validate": "
newData.hasChildren(['requiredString1', 'requiredString2'])
",
//Define rules for each child
"requiredString1":{
".validate": "
newData.isString()
"
},
"requiredString2":{
".validate": "
newData.isString()
"
},
"optionalString1":{
".validate": "
newData.isString()
"
},
//And finally, ensure no other miscellaneous children can be written
"$other": {
".validate": false
}
},
//Also, ensure lockdown on all other root trees
"$other": {
".read": false,
".write": false,
".validate": false
}
}
我开始组建一个测试套件来测试我的规则,但 optional-yet-not-delete 规则导致了问题。
根据上述规则,我在两次测试中失败了:
允许使用以下有效内容写入test_tree
(这需要失败)。
{
requiredString1: "string1",
requiredString2: "string2",
optionalString1: null
}
允许写入test_tree/optionalString1
,有效负载为null
(这需要失败)。
我试图通过验证规则变得棘手,例如:
"rules": {
...
".validate": "
//Ensure that required values are present
newData.hasChildren(['requiredString1', 'requiredString2']) &&
(
//IF optionalString is included, ensure that it's not null
(
newData.hasChild('optionalString1') &&
newData.hasChild('optionalString1').val() !== null
)
||
//But also allow it to be non-present
!newData.hasChild('optionalString1')
)
"
...
}
但不幸的是,这会导致与以前相同的错误。
我也尝试了其他一些规则结构,包括将整个规则集实际移动到每个子位置(并删除父.write
位置的.validate
和test_tree
规则) ,但随后写入父位置(之前有效)将失败。
这里有些帮助吗?同样,我认为允许数据是可选的,但仍然可以防止删除,这是一种常见的需求。
编辑1:
我花了一些时间思考我的问题,我认为这个要求有点用词不当。基本上,我要求的是数据是可选的当且仅当它不存在于Firebase数据库中时。如果Firebase中存在 ,则基本上需要它。
然而,一旦我意识到这是真正的要求,它就更容易描述用例。希望稍后会清楚,但基本上用例是保护与Firebase连接的开发人员。
想象一下,有一个项目的几个描述符的树,树看起来如下:
items: {
item_ID1: {
name: "Item Name",
descriptorA: "A descriptor",
descriptorB: "Another descriptor"
}
}
基本上,我认为descriptorA
和descriptorB
是可选的,name
是必需的。如果需要更改name
,而descriptorA
和descriptorB
保持不变,我想保护正在编写此数据接口的开发人员能够意外地进行爆炸descriptorA
和descriptorB
使用.set({item_ID1: {name: "New Name"}})
。
我认为这实际上可以通过以下规则来实现:
"rules": {
"items": {
".write": "
auth !== null &&
newData.exists()
",
".validate": "
newData.hasChild('name') &&
(
!data.hasChild('descriptorA') ||
(data.hasChild('descriptorA') && newData.hasChild('descriptorA'))
) &&
(
!data.hasChild('descriptorB') ||
(data.hasChild('descriptorB') && newData.hasChild('descriptorB'))
)
",
"name": {
".validate": "newData.isString()"
",
"descriptorA": {
".validate": "newData.isString()"
",
"descriptorB": {
".validate": "newData.isString()"
",
"$others": {
".validate": false
}
}
}
答案 0 :(得分:2)
第一个问题是(如你所说)如果没有新数据,则不会执行.validate
规则。因此,您需要在.write
规则中检测条件。
第二个问题(正如你所说)是"规则级联",所以如果你允许在更高级别的节点上进行操作,你就不能把它带到更低的级别。因此,您需要在JSON结构中检测更高级别的条件。
因此,解决方案是在JSON树中使用更高的.write
规则。
"test_tree": {
".write": "newData.exists() && (newData.hasChild('optionalString') || !data.hasChild('optionalString'))",
".validate": "newData.hasChildren(['requiredString'])",
"requiredString": {
".validate": "newData.isString()"
},
"optionalString":{
".validate": "newData.isString() || !data.exists()"
},
"$other": {
".validate": false
}
我已经简化了您的数据结构,只包含一个必需属性和一个可选属性。
答案 1 :(得分:0)
以上编辑1中对此进行了描述。
我花了一些时间思考我的问题,我认为这个要求有点用词不当。基本上,我要求的是,当且仅当它在Firebase数据库中不存在时,数据才是可选的。如果Firebase中存在 ,则基本上需要它。
然而,一旦我意识到这是真正的要求,它就更容易描述用例。希望它能在一瞬间变得清晰,但基本上用例是保护与Firebase接口的开发人员。
想象一下,有一个项目的几个描述符的树,树看起来如下:
items: {
item_ID1: {
name: "Item Name",
descriptorA: "A descriptor",
descriptorB: "Another descriptor"
}
}
基本上,我认为descriptorA
和descriptorB
是可选的,name
是必需的。如果需要更改name
,而descriptorA
和descriptorB
保持不变,我想保护正在编写此数据接口的开发人员能够意外地进行爆炸descriptorA
和descriptorB
使用.set({item_ID1: {name: "New Name"}})
。
我认为这实际上可以通过以下规则来实现:
"rules": {
"items": {
".write": "
auth !== null &&
newData.exists()
",
".validate": "
newData.hasChild('name') &&
(
!data.hasChild('descriptorA') ||
(data.hasChild('descriptorA') && newData.hasChild('descriptorA'))
) &&
(
!data.hasChild('descriptorB') ||
(data.hasChild('descriptorB') && newData.hasChild('descriptorB'))
)
",
"name": {
".validate": "newData.isString()"
",
"descriptorA": {
".validate": "newData.isString()"
",
"descriptorB": {
".validate": "newData.isString()"
",
"$others": {
".validate": false
}
}
}