Докато работя върху приложение за управление на паркиране на съоръжения https://parkalot.io изпитвам някои предупреждения относно правилата за сигурност на Firebase, свързани с числата

Правилата за сигурност на Firebase са написани с доста ограничено подмножество от методи, подобни на JavaScript, както и няколко глобални променливи: now, auth, root.

Приложение

Това изглеждаше достатъчно, за да се приложи известно валидиране от страна на сървъра. Нашата тема е просто приложение, което казва, че ще съхранява информация за настроението на потребителя всеки ден. Да приемем, че искаме да създадем този вид структура, където всеки дъщерен възел на възел days представлява определен ден, в който ще запазим някои данни. Денят ще бъде представен като число, което означава „ден след епоха“. За по-голяма простота нека забравим сега за съществуването на часови зони.

Ограничения

  1. не позволявайте на потребителя да запълва настроението му за един ден в бъдеще.
  2. не позволявайте на потребителя да се оплаква в петък, трябва да бъде (настроение: „чудесно“)
//code that we use to write to database
var daySinceEpoch = getCurrentEpochDay();
return firebase
    .child('days')
    .child(daySinceEpoch)
    .child('mood')
    .set('awful');

Нека напишем някои правила! С Bolt Compiler от екипа на firebase може да бъде толкова просто, колкото:

// 86400000 - number of milliseconds in a day
path /days/{$epochDay}/{$mood} {
    write() { $epochDay < (now / 86400000) }
    read() { true }
}

Изпълнението на firebase-bolt rules.bolt ни дава:

{
  "rules": {
    ".read": “false”,
    ".write": “false”,
    "days": {
      "$epochDay": {
        ".read": "true",
        ".write": "$epochDay < now / 86400000"
      }
    }
  }
}

Така че сега нека бягаме.. и да видим как се проваля. В документацията на firebase е ясно посочено, че не сме имали шанс от самото начало:

Разбити мечти

И все пак$key === newData.val()+’’ни дава известна надежда. Ако можем да преобразуваме число към низ, защо не преведем $epochDayкъм число с подобен оператор + (унарен плюс). За съжаление, въпреки че firebase не се оплаква веднага, когато поставим правила в конзолата, той се проваля по-късно, при всяка отделна операция за запис. Дори и с правила, написани по-долу (в JS винаги ще бъдат оценени като true), ние се проваляме във всяка операция за запис. Вероятно в правилата на firebase не можем да конвертираме низ в число по този начин.

“$epochDay”: {
“.read”: “true”,
“.write”: “1 + ‘1’ ‹ 3” // винаги трябва да е вярно.
}

В такава ситуация обикновено вероятно бихме преосмислили структурата на данните си. Друг подход е да добавите братски възел към настроение, който дублира информация за деня, но като числова стойност. В такъв случай потвърждаваме равенството им като низове и валидираме стойността на {ден: xxxxxx} спрямо правилата за сигурност (вижте структурата по-долу).

Време е за забавление

Но този път за забавление (и само за забавление)нека създадем наш собствен начин за кодиране на числа, за да ги вкараме нелегално в правилата на Firebase. Точно както в детската градина, нека присвоим стойности на буквите. Нека a = 1, b = 10, c = 100 и т.н., така че 151 = cbbbbba. Нещо подобно на това, което са използвали около VI век пр.н.е. в древен Рим. Или изчакайте, нека вземем назаем буквите от римляните, тъй като те са много по-стилни. Сега i= 1, x= 10, c = 100, m= 1000, X = 10000, C = 100000, M = 1000000. Моля, обърнете внимание, че буквите за огромни числа (поне според възприемането на римляните) са главни! Обърнете внимание също, че не използваме „субтрактивна нотация“. Използвайте JS фрагмента по-долу, за да преобразувате десетично число в нашите псевдо римски цифри.

function toFirebaseNumber(number) {
    var strNum = "" + number;
    var digits = ['i', 'x', 'c', 'm', 'X' , 'C', 'M'];
    var result = "";
    for (var i = 0; i < strNum.length; i++) {
        var digit = +strNum[strNum.length - 1 - i];
        for (;digit > 0; digit--) {
            result = digits[i] + result;
        }
    }
    return result;
}

Сега от страна на правилата на firebase става малко по-сложно. Искаме да преброим колко пъти всяка буква се появява в низа и да пресъздадем отново числото. Единственият метод на низ в правилата на firebase, който връща число, е .length(), но можем да живеем с това.

// count occurrences of digit
count(digit, str) { str.length - str.replace(digit,'').length }
// parse our notation back to number
parseRoman(str) { count('i',str) 
                + count('x',str) * 10 
                + count('c',str) * 100 
                + count('m',str) * 1000 
                + count('X',str) * 10000
                + count('C',str) * 100000 }
// is a proper roman numerical
isRoman(str) {  str.matches(/^C{0,9}X{0,9}m{0,9}c{0,9}x{0,9}i{0,9}+$/)
}
// rules, % 7 != 1 because 1 january 1970 was thursday
path /days/{$epochDay}/{$mood} {
    write() { isRoman($epochDay)
              && parseInt($epochDay) < now / 86400000
              && (parseInt($epochDay) % 7 != 1 || $mood === 'great')}
    read() { true }
}

Така че сега сме сигурни, че нашите потребители няма да влязат в настроенията си от бъдещето и че се чувстват страхотно в петък;). Този експеримент също ни показва, че firebase е доста производителна, когато става въпрос за обработка на сложни правила за сигурност. Не забелязах наказание и както валидираните, така и невалидираните записи също около 150 ms. Поне докато не го убием с някой наистина ефектен RegExp. Аве!