Является ли способ, с помощью которого быстро запускается протокол Firebase, считается безопасным?

Я хочу создать поле приращения для статей.

Я имею в виду эту ссылку: https://firebase.google.com/docs/database/android/save-data#save_data_as_transactions

В примере есть код для поля инкремента:

if (p.stars.containsKey(getUid())) {
    // Unstar the post and remove self from stars
    p.starCount = p.starCount - 1;
    p.stars.remove(getUid());
} else {
    // Star the post and add self to stars
    p.starCount = p.starCount + 1;
    p.stars.put(getUid(), true);
}

Но как я могу быть уверен, что пользователь уже любил/не писал статью?

В этом примере пользователь (хакер) может также очистить всю карту "Звезды", как это, и он все равно сохранит:

p.stars = new HashMap<>();

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

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

Любая помощь, предложения?

Ответ 1

Правила безопасности могут выполнять несколько действий:

  • убедитесь, что пользователь может добавлять/удалять свои собственные uid в stars node

    "stars": {
      "$uid": {
        ".write": "$uid == auth.uid"
      }
    }
    
  • убедитесь, что пользователь может изменить только starCount, когда он добавляет свой собственный uid в stars node или удаляет его оттуда

  • убедитесь, что пользователь может только увеличивать/уменьшать starCount на 1

Даже с этим может оказаться сложным иметь правило безопасности, гарантирующее, что значение starCount равно числу uids в stars node. Я рекомендую вам попробовать и поделиться своим результатом.

То, как я видел большинство разработчиков, имеет дело с этим:

  • сделайте начальный подсчет на клиенте (если размер stars node не слишком велик, это разумно).
  • есть надежный процесс, запущенный на сервере, который объединяет stars в starCount. Он может использовать события child_added/child_removed для увеличения/уменьшения.

Обновление: с рабочим примером

Я написал рабочий пример системы голосования. Структура данных:

votes: {
  uid1: true,
  uid2: true,
},
voteCount: 2

Когда пользователь голосует, приложение отправляет обновление с несколькими местоположениями:

{
  "/votes/uid3": true,
  "voteCount": 3
}

И затем, чтобы удалить свой голос:

{
  "/votes/uid3": null,
  "voteCount": 2
}

Это означает, что приложение должно явно прочитать текущее значение для voteCount, с помощью:

function vote(auth) {
  ref.child('voteCount').once('value', function(voteCount) {
    var updates = {};
    updates['votes/'+auth.uid] = true;
    updates.voteCount = voteCount.val() + 1;
    ref.update(updates);
  });  
}

Это, по сути, транзакция с несколькими местоположениями, но затем встроенная в код приложения и правила безопасности вместо Firebase SDK и самого сервера.

Правила безопасности делают несколько вещей:

  • убедитесь, что значение countCount может увеличиваться или уменьшаться на 1
  • убедитесь, что пользователь может добавлять или удалять только собственные голоса.
  • убедитесь, что увеличение счета сопровождается голосованием.
  • убедитесь, что уменьшение счета сопровождается "unvote"
  • обеспечить, чтобы голосование сопровождалось увеличением количества.

Обратите внимание, что правил нет:

  • убедитесь, что "unvote" сопровождается уменьшением количества (может быть сделано с помощью правила .write)
  • повторение неудачных голосов /unvotes (для обработки одновременного голосования/опроса)

Правила:

"votes": {
    "$uid": {
      ".write": "auth.uid == $uid",
      ".validate": "(!data.exists() && newData.val() == true &&
                      newData.parent().parent().child('voteCount').val() == data.parent().parent().child('voteCount').val() + 1
                    )"
    }
},
"voteCount": {
    ".validate": "(newData.val() == data.val() + 1 && 
                   newData.parent().child('votes').child(auth.uid).val() == true && 
                   !data.parent().child('votes').child(auth.uid).exists()
                  ) || 
                  (newData.val() == data.val() - 1 && 
                   !newData.parent().child('votes').child(auth.uid).exists() && 
                   data.parent().child('votes').child(auth.uid).val() == true
                  )",
    ".write": "auth != null"
}

jsbin с некоторым кодом для проверки этого: http://jsbin.com/yaxexe/edit?js,console