Ограничить количество записей, которые могут быть записаны в путь (ссылки на другие пути в правилах безопасности)

Допустим, моя коллекция Firebase выглядит так:

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

Как мне использовать значение max в моих правилах безопасности, чтобы ограничить количество things?

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

person Dan Kanze    schedule 25.03.2014    source источник


Ответы (2)


Использование существующих свойств осуществляется с помощью root или parent, и это довольно просто.

{
  "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 перед обновлением записи, поэтому, хотя он подходит для ограничения количества записей, он не подходит для обеспечения соблюдения правил игры или предотвращения читов.

Другие ресурсы и мысли

Если вам нужна безопасность на уровне игры, ознакомьтесь с этой скрипкой, в которой подробно описано, как создавать записи с инкрементными идентификаторами, включая правила безопасности, необходимые для установить счетчик. Вы можете комбинировать это с приведенными выше правилами, чтобы обеспечить максимальное значение для инкрементных идентификаторов и убедиться, что счетчик обновляется до записи записи.

Кроме того, убедитесь, что вы не слишком задумываетесь об этом и существует законный вариант использования для ограничения количества записей, а не просто для удовлетворения здоровой дозы беспокойства. Очень сложно просто установить квоту для бедняков в ваших структурах данных.

person Kato    schedule 25.03.2014
comment
Это было очень тщательно - спасибо, что нашли время провести меня через это. Очень полезно. Быстрый вопрос о $id >= 'rec'+root.child('incid/counter').val(), где $id равно значению counter, найденному в вашей скрипке. Если пользователь удаляет $id запись 3, мы можем остаться с 1,2,4,5, в какой момент эти правила не сработают, верно? Как бы я с этим справился? - person Dan Kanze; 26.03.2014
comment
Привет, Дэн, да, это две разные стратегии: одна предназначена для создания уникальных инкрементных записей, другая - для обеспечения максимального. Чтобы объединить эти две вещи, нужно немного придумать, чего я здесь не делал. - person Kato; 26.03.2014
comment
Я пытаюсь представить, как можно было бы применить и то, и другое, если бы $id не было целым числом, которое соответствует текущему значению counter. Что, если .write находится в свойстве объекта, которое разделяют несколько записей, или если каждое $id было случайным? У вас есть идеи, которые не требуют увеличения $id на counter? - person Dan Kanze; 26.03.2014
comment
Это единственная идея полностью клиентского подхода, которую я предлагаю. Вы всегда можете развернуть процесс узла примерно в 20 строках кода, чтобы отслеживать путь, подсчитывать записи, обеспечивать максимальное значение и т. Д. - person Kato; 26.03.2014
comment
Да, если $id хочет вырваться из этого шаблона, я думаю, это то, что мне нужно сделать. - person Dan Kanze; 26.03.2014
comment
@Kato Что-то вроде root.child ('somePath'). NumChildren () было бы отличным дополнением к структуре безопасности, если бы это могло быть быстро. Я работаю над приложением, в котором количество вещей, которые может иметь пользователь, зависит от того, сколько они заплатили. Я обрабатываю это в некотором защищенном коде на стороне сервера, но теперь у меня есть логика безопасности / проверки в нескольких местах, а не централизованное хранение в области правил безопасности Firebase. - person Mike Pugh; 27.03.2014
comment
@Kato Возвращаясь к этому чуть позже, мне интересно, попадал ли .numChildren() когда-либо в API безопасности или находится в разработке? - person Dan Kanze; 27.01.2015
comment
Получение детей без загрузки всех записей по-прежнему является очень нетривиальной проблемой в распределенных вычислениях в реальном времени. Доступен вызов REST API (shallow = true), который дает аналогичный результат, но все равно требует небольшого снижения производительности, если на пути есть миллионы дочерних элементов, а для SDK ничего не доступно. - person Kato; 27.01.2015
comment
Как всегда потрясающие ответы @Kato. Мне было интересно, можете ли вы порекомендовать подход, чтобы на моем пути было не более 12 элементов .ref (connections/${myUid}/connection) - person Nikos; 11.07.2020

Хотя я думаю, что до сих пор нет доступного правила для таких вещей, здесь есть образец облачной функции, которая делает это:

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

person Gonzalo    schedule 07.06.2017
comment
Спасибо, Гонсало. Это действительно строгое и действенное решение. Это потому, что можно ввести логику и ограничить своих пользователей ограничениями на стороне клиента, однако с помощью облачных функций так легко поймать любого, кто пытается обойти правила, которые вы предоставили на стороне клиента. Ознакомьтесь с firebase.google.com/docs/functions - person katmanco; 27.08.2017