限制可写入路径的记录数(参考安全规则中的其他路径)

IT技术 javascript firebase firebase-security
2021-01-25 16:58:43

假设我的 Firebase 集合如下所示:

{
  "max":5
  "things":{}
}

我将如何使用max安全规则中的值来限制 的数量things

{
  "rules": {
    "things": {
      ".validate": "newData.val().length <= max"
    }
  }
}
2个回答

使用现有属性是通过rootparent 完成的,非常简单。

{
  "rules": {
    "things": {
      // assuming value is being stored as an integer
      ".validate": "newData.val() <= root.child('max')"
    }
  }
}

但是,确定记录数量并执行此操作比简单地编写安全规则要复杂一些:

  • 由于.length对象上没有,我们需要存储存在多少条记录
  • 我们需要以安全/实时的方式更新该号码
  • 我们需要知道相对于该计数器添加的记录数

一种天真的方法

假设限制很小(例如 5 条记录),一个穷人的方法是简单地在安全规则中枚举它们:

{
  "rules": {
    "things": {
      ".write": "newData.hasChildren()", // is an object
      "thing1": { ".validate": true },
      "thing2": { ".validate": true },
      "thing3": { ".validate": true },
      "thing4": { ".validate": true },
      "thing5": { ".validate": true },
      "$other": { ".validate": false
    }
  }
}

一个真实的例子

像这样的数据结构有效:

/max/<number>
/things_counter/<number>
/things/$record_id/{...data...}

因此,每次添加记录时,计数器必须递增。

var fb = new Firebase(URL);
fb.child('thing_counter').transaction(function(curr) {
   // security rules will fail this if it exceeds max
   // we could also compare to max here and return undefined to cancel the trxn
   return (curr||0)+1;
}, function(err, success, snap) {
   // if the counter updates successfully, then write the record
   if( err ) { throw err; }
   else if( success ) {
      var ref = fb.child('things').push({hello: 'world'}, function(err) {
         if( err ) { throw err; }
         console.log('created '+ref.name());
      });
   }
});

每次删除记录时,计数器必须递减。

var recordId = 'thing123';
var fb = new Firebase(URL);
fb.child('thing_counter').transaction(function(curr) {
   if( curr === 0 ) { return undefined; } // cancel if no records exist
   return (curr||0)-1;
}, function(err, success, snap) {
   // if the counter updates successfully, then write the record
   if( err ) { throw err; }
   else if( success ) {
      var ref = fb.child('things/'+recordId).remove(function(err) {
         if( err ) { throw err; }
         console.log('removed '+recordId);
      });
   }
});

现在进入安全规则:

{
  "rules": {
    "max": { ".write": false },

    "thing_counter": {
      ".write": "newData.exists()", // no deletes
      ".validate": "newData.isNumber() && newData.val() >= 0 && newData.val() <= root.child('max').val()"
    },

    "things": {
      ".write": "root.child('thing_counter').val() < root.child('max').val()"
    }
  }
}

请注意,这不会强制用户在更新记录之前写入 thing_counter,因此虽然适合限制记录数量,但不适合执行游戏规则或防止作弊。

其他资源和想法

如果您需要游戏级别的安全性,请查看此 fiddle,其中详细介绍了如何使用增量 ID 创建记录,包括强制执行计数器所需的安全规则。您可以将其与上述规则结合起来,对增量 id 强制执行最大值,并确保在写入记录之前更新计数器。

此外,请确保您没有过度考虑这一点,并且有一个合法的用例来限制记录数量,而不仅仅是为了满足健康的担忧。简单地在您的数据结构上强制执行穷人的配额是非常复杂的。

是的,如果$id想要摆脱这种模式,我认为这就是我必须做的。
2021-03-23 16:58:43
我试图想象如果$id不是与当前counter匹配的整数,它甚至可能如何强制执行如果.write是在多个记录共享的对象属性上,或者每个记录$id都是随机的怎么办?你有什么想法不需要$id增加counter吗?
2021-03-28 16:58:43
这是我想到的完全客户端方法的唯一想法。您总是可以在大约 20 行代码中启动一个节点进程来监视路径、计算记录、强制执行最大值等。
2021-04-05 16:58:43
嗨,丹,是的,这是两种不同的策略,一种用于创建唯一的增量记录,另一种用于强制执行最大值。将两者结合起来需要一些心机,我在这里没有这样做。
2021-04-11 16:58:43
这是非常彻底的 - 感谢您花时间带我完成这个。很有帮助。关于$id >= 'rec'+root.child('incid/counter').val()哪里$id等于counter在您的小提琴中找到值的快速问题如果用户删除了一条$idsay 记录,3我们可能会剩下1,2,4,5这些规则会在哪一点失效,对吗?我将如何处理这个问题?
2021-04-13 16:58:43

虽然我认为仍然没有可用的规则来做这样的事情,但这里有一个示例云函数可以做到这一点:

https://github.com/firebase/functions-samples/tree/master/limit-children

谢谢贡萨洛。这是一个真正严格而强大的解决方案。这是因为人们可以将逻辑和限制他/她的用户限制在客户端,但是使用 Cloud Functions 可以很容易地抓住任何试图绕过您在客户端提供的规则的人。查看 firebase.google.com/docs/functions
2021-04-05 16:58:43