Firebase允许在父节点和子节点上进行写入而不删除

时间:2015-10-06 18:19:57

标签: firebase firebase-security

我一直在与Firebase安全性进行一段时间的斗争,而且我认为这种情况并不是很独特(但在文档中也没有涉及)。

想象一下,我有一棵树test_tree,它位于我的Firebase数据库的根目录下。从那里开始,我在test_tree下面有三个名为requiredString1requiredString2optionalString1的孩子。

我希望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 规则导致了问题。

根据上述规则,我在两次测试中失败了:

  1. 允许使用以下有效内容写入test_tree(这需要失败)。

    {
      requiredString1: "string1",
      requiredString2: "string2",
      optionalString1: null
    }
    
  2. 允许写入test_tree/optionalString1,有效负载为null(这需要失败)。

  3. 我试图通过验证规则变得棘手,例如:

    "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位置的.validatetest_tree规则) ,但随后写入父位置(之前有效)将失败。

    这里有些帮助吗?同样,我认为允许数据是可选的,但仍然可以防止删除,这是一种常见的需求。

    编辑1:

    我花了一些时间思考我的问题,我认为这个要求有点用词不当。基本上,我要求的是数据是可选的当且仅当它不存在于Firebase数据库中时。如果Firebase中存在 ,则基本上需要它。

    然而,一旦我意识到这是真正的要求,它就更容易描述用例。希望稍后会清楚,但基本上用例是保护与Firebase连接的开发人员。

    想象一下,有一个项目的几个描述符的树,树看起来如下:

    items: {
      item_ID1: {
        name: "Item Name",
        descriptorA: "A descriptor",
        descriptorB: "Another descriptor"
      }
    }
    

    基本上,我认为descriptorAdescriptorB是可选的,name是必需的。如果需要更改name,而descriptorAdescriptorB保持不变,我想保护正在编写此数据接口的开发人员能够意外地进行爆炸descriptorAdescriptorB使用.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
        }
      }
    }
    

2 个答案:

答案 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"
  }
}

基本上,我认为descriptorAdescriptorB是可选的,name是必需的。如果需要更改name,而descriptorAdescriptorB保持不变,我想保护正在编写此数据接口的开发人员能够意外地进行爆炸descriptorAdescriptorB使用.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
    }
  }
}