Catch up on everthing we announced at this year's Firebase Summit. Learn more

Условия использования в правилах базы данных реального времени

Это руководство опирается на выучить основные Правила Firebase безопасности языка руководства , чтобы показать , как добавить условия для Вашего Firebase реального времени базы данных правил безопасности.

Основной строительный блок в режиме реального времени базы данных правил безопасности является условием. Условие - это логическое выражение, которое определяет, следует ли разрешить или запретить конкретную операцию. Для основных правил, используя true и false литералы в условиях работы втирает хорошо. Но язык правил безопасности баз данных в реальном времени дает вам возможность писать более сложные условия, которые могут:

  • Проверить аутентификацию пользователя
  • Сравните существующие данные с новыми данными
  • Доступ и сравнение различных частей вашей базы данных
  • Проверить входящие данные
  • Используйте структуру входящих запросов для логики безопасности

Использование $ Variables для захвата сегментов пути

Вы можете захватить участки пути для чтения или записи с помощью объявления переменного захвата с $ префикса. Он служит подстановочным знаком и сохраняет значение этого ключа для использования в условиях правил:

{
  "rules": {
    "rooms": {
      // this rule applies to any child of /rooms/, the key for each room id
      // is stored inside $room_id variable for reference
      "$room_id": {
        "topic": {
          // the room's topic can be changed if the room id has "public" in it
          ".write": "$room_id.contains('public')"
        }
      }
    }
  }
}

Динамический $ переменные также могут быть использованы параллельно с постоянными именами путей. В этом примере мы используем $other переменные объявить .validate правила , что гарантирует , что widget не имеет другие детей title и color . Любая запись, которая приведет к созданию дополнительных дочерних элементов, завершится ошибкой.

{
  "rules": {
    "widget": {
      // a widget can have a title or color attribute
      "title": { ".validate": true },
      "color": { ".validate": true },

      // but no other child paths are allowed
      // in this case, $other means any key excluding "title" and "color"
      "$other": { ".validate": false }
    }
  }
}

Аутентификация

Один из наиболее распространенных шаблонов правил безопасности - это управление доступом на основе состояния аутентификации пользователя. Например, вашему приложению может потребоваться разрешить запись данных только вошедшим в систему пользователям.

Если ваше приложение использует Firebase аутентификации request.auth переменная содержит сведения о проверке подлинности для клиента , запрашивающего данные. Для получения дополнительной информации о request.auth см справочную документацию .

Firebase Authentication интегрируется с базой данных Firebase Realtime, чтобы вы могли контролировать доступ к данным для каждого пользователя с помощью условий. После проверки подлинности пользователя, то auth переменная в режиме реального времени базы данных правил Правила безопасности будут заполнены информацией пользователя. Эта информация включает в свой уникальный идентификатор ( uid ), а также связанные данные учетной записи, такие как идентификатор Facebook или адрес электронной почты, а также другую информацию. Если вы реализуете настраиваемый поставщик аутентификации, вы можете добавить свои собственные поля в полезные данные аутентификации пользователя.

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

auth Variable

Предопределены auth переменная в правилах равна нулю , прежде чем происходит проверка подлинности.

После аутентификации пользователя с Firebase Authentication он будет содержать следующие атрибуты:

провайдер Используемый метод аутентификации («пароль», «анонимный», «facebook», «github», «google» или «twitter»).
uid Уникальный идентификатор пользователя, гарантированно уникальный для всех провайдеров.
жетон Содержимое токена Firebase Auth ID. Смотрите справочную документацию по auth.token для более подробной информации.

Ниже приведен пример правила , которое использует auth переменную , чтобы гарантировать , что каждый пользователь может писать только к пользователю конкретного пути:

{
  "rules": {
    "users": {
      "$user_id": {
        // grants write access to the owner of this user account
        // whose uid must exactly match the key ($user_id)
        ".write": "$user_id === auth.uid"
      }
    }
  }
}

Структурирование вашей базы данных для поддержки условий аутентификации

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

{
  "rules": {
    "users": {
      "$uid": {
        ".read": "auth != null && auth.uid == $uid"
      }
    }
  }
}

Работа с настраиваемыми утверждениями аутентификации

Для приложений, требующих таможенного контроля доступа для различных пользователей, Firebase Authentication позволяет разработчикам набор требований к пользователю Firebase . Эти претензии в подъезде auth.token переменного в правилах. Ниже приведен пример правил , которые делают использование hasEmergencyTowel таможенной претензии:

{
  "rules": {
    "frood": {
      // A towel is about the most massively useful thing an interstellar
      // hitchhiker can have
      ".read": "auth.token.hasEmergencyTowel === true"
    }
  }
}

Разработчики , создающие свои собственные маркеры пользовательские аутентификации могут при необходимости добавлять требования к этим лексем. Эти претензии avaialble на auth.token переменной в правилах.

Существующие данные против новых данных

Предопределенные data переменным используются для обращения к данным до операции записи происходит. С другой стороны , newData переменная содержит новые данные , которые будут существовать , если операция записи прошла успешно. newData представляет собой объединенный результат новых данных, записываемых и существующих данных.

Чтобы проиллюстрировать, это правило позволит нам создавать новые записи или удалять существующие, но не вносить изменения в существующие ненулевые данные:

// we can write as long as old data or new data does not exist
// in other words, if this is a delete or a create, but not an update
".write": "!data.exists() || !newData.exists()"

Ссылки на данные в других путях

Любые данные могут использоваться как критерий для правил. Использование предопределенного переменного root , data и newData , мы можем получить доступ к любому пути, он будет существовать до или после события записи.

Рассмотрим следующий пример, который позволяет операции записи до тех пор , как значение из /allow_writes/ узел true , родительский узел не имеет readOnly установлен флаг, и есть ребенок с именем foo в недавно записанных данных:

".write": "root.child('allow_writes').val() === true &&
          !data.parent().child('readOnly').exists() &&
          newData.child('foo').exists()"

Проверка данных

Обеспечение структуры данных и проверка формата и содержания данных должны быть выполнен с использованием .validate правил, которые выполняются только после того, как .write правила удается предоставить доступ. Ниже приведен пример .validate определение правила , которое позволяет только даты в формате YYYY-MM-DD между годами 1900-2099, который проверяется с помощью регулярного выражения.

".validate": "newData.isString() &&
              newData.val().matches(/^(19|20)[0-9][0-9][-\\/. ](0[1-9]|1[012])[-\\/. ](0[1-9]|[12][0-9]|3[01])$/)"

В .validate правил являются единственным типом правила безопасности , которые не каскада. Если какое-либо правило проверки не выполняется для какой-либо дочерней записи, вся операция записи будет отклонена. Кроме того, определения Validate игнорируются при удалении данных (то есть, когда новое значение записывается в null ).

Это может показаться банальным, но на самом деле это важные функции для написания мощных правил безопасности баз данных Firebase в реальном времени. Учтите следующие правила:

{
  "rules": {
    // write is allowed for all paths
    ".write": true,
    "widget": {
      // a valid widget must have attributes "color" and "size"
      // allows deleting widgets (since .validate is not applied to delete rules)
      ".validate": "newData.hasChildren(['color', 'size'])",
      "size": {
        // the value of "size" must be a number between 0 and 99
        ".validate": "newData.isNumber() &&
                      newData.val() >= 0 &&
                      newData.val() <= 99"
      },
      "color": {
        // the value of "color" must exist as a key in our mythical
        // /valid_colors/ index
        ".validate": "root.child('valid_colors/' + newData.val()).exists()"
      }
    }
  }
}

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

JavaScript
var ref = db.ref("/widget");

// PERMISSION_DENIED: does not have children color and size
ref.set('foo');

// PERMISSION DENIED: does not have child color
ref.set({size: 22});

// PERMISSION_DENIED: size is not a number
ref.set({ size: 'foo', color: 'red' });

// SUCCESS (assuming 'blue' appears in our colors list)
ref.set({ size: 21, color: 'blue'});

// If the record already exists and has a color, this will
// succeed, otherwise it will fail since newData.hasChildren(['color', 'size'])
// will fail to validate
ref.child('size').set(99);
Цель-C
Примечание: Этот Firebase продукт не доступен на цели клипа App.
FIRDatabaseReference *ref = [[[FIRDatabase database] reference] child: @"widget"];

// PERMISSION_DENIED: does not have children color and size
[ref setValue: @"foo"];

// PERMISSION DENIED: does not have child color
[ref setValue: @{ @"size": @"foo" }];

// PERMISSION_DENIED: size is not a number
[ref setValue: @{ @"size": @"foo", @"color": @"red" }];

// SUCCESS (assuming 'blue' appears in our colors list)
[ref setValue: @{ @"size": @21, @"color": @"blue" }];

// If the record already exists and has a color, this will
// succeed, otherwise it will fail since newData.hasChildren(['color', 'size'])
// will fail to validate
[[ref child:@"size"] setValue: @99];
Быстрый
Примечание: Этот Firebase продукт не доступен на цели клипа App.
var ref = FIRDatabase.database().reference().child("widget")

// PERMISSION_DENIED: does not have children color and size
ref.setValue("foo")

// PERMISSION DENIED: does not have child color
ref.setValue(["size": "foo"])

// PERMISSION_DENIED: size is not a number
ref.setValue(["size": "foo", "color": "red"])

// SUCCESS (assuming 'blue' appears in our colors list)
ref.setValue(["size": 21, "color": "blue"])

// If the record already exists and has a color, this will
// succeed, otherwise it will fail since newData.hasChildren(['color', 'size'])
// will fail to validate
ref.child("size").setValue(99);
Джава
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference ref = database.getReference("widget");

// PERMISSION_DENIED: does not have children color and size
ref.setValue("foo");

// PERMISSION DENIED: does not have child color
ref.child("size").setValue(22);

// PERMISSION_DENIED: size is not a number
Map<String,Object> map = new HashMap<String, Object>();
map.put("size","foo");
map.put("color","red");
ref.setValue(map);

// SUCCESS (assuming 'blue' appears in our colors list)
map = new HashMap<String, Object>();
map.put("size", 21);
map.put("color","blue");
ref.setValue(map);

// If the record already exists and has a color, this will
// succeed, otherwise it will fail since newData.hasChildren(['color', 'size'])
// will fail to validate
ref.child("size").setValue(99);
ОТДЫХАТЬ
# PERMISSION_DENIED: does not have children color and size
curl -X PUT -d 'foo' \
https://docs-examples.firebaseio.com/rest/securing-data/example.json

# PERMISSION DENIED: does not have child color
curl -X PUT -d '{"size": 22}' \
https://docs-examples.firebaseio.com/rest/securing-data/example.json

# PERMISSION_DENIED: size is not a number
curl -X PUT -d '{"size": "foo", "color": "red"}' \
https://docs-examples.firebaseio.com/rest/securing-data/example.json

# SUCCESS (assuming 'blue' appears in our colors list)
curl -X PUT -d '{"size": 21, "color": "blue"}' \
https://docs-examples.firebaseio.com/rest/securing-data/example.json

# If the record already exists and has a color, this will
# succeed, otherwise it will fail since newData.hasChildren(['color', 'size'])
# will fail to validate
curl -X PUT -d '99' \
https://docs-examples.firebaseio.com/rest/securing-data/example/size.json

Теперь давайте посмотрим на ту же структуру, но с использованием .write правил вместо .validate :

{
  "rules": {
    // this variant will NOT allow deleting records (since .write would be disallowed)
    "widget": {
      // a widget must have 'color' and 'size' in order to be written to this path
      ".write": "newData.hasChildren(['color', 'size'])",
      "size": {
        // the value of "size" must be a number between 0 and 99, ONLY IF WE WRITE DIRECTLY TO SIZE
        ".write": "newData.isNumber() && newData.val() >= 0 && newData.val() <= 99"
      },
      "color": {
        // the value of "color" must exist as a key in our mythical valid_colors/ index
        // BUT ONLY IF WE WRITE DIRECTLY TO COLOR
        ".write": "root.child('valid_colors/'+newData.val()).exists()"
      }
    }
  }
}

В этом варианте любая из следующих операций завершится успешно:

JavaScript
var ref = new Firebase(URL + "/widget");

// ALLOWED? Even though size is invalid, widget has children color and size,
// so write is allowed and the .write rule under color is ignored
ref.set({size: 99999, color: 'red'});

// ALLOWED? Works even if widget does not exist, allowing us to create a widget
// which is invalid and does not have a valid color.
// (allowed by the write rule under "color")
ref.child('size').set(99);
Цель-C
Примечание: Этот Firebase продукт не доступен на цели клипа App.
Firebase *ref = [[Firebase alloc] initWithUrl:URL];

// ALLOWED? Even though size is invalid, widget has children color and size,
// so write is allowed and the .write rule under color is ignored
[ref setValue: @{ @"size": @9999, @"color": @"red" }];

// ALLOWED? Works even if widget does not exist, allowing us to create a widget
// which is invalid and does not have a valid color.
// (allowed by the write rule under "color")
[[ref childByAppendingPath:@"size"] setValue: @99];
Быстрый
Примечание: Этот Firebase продукт не доступен на цели клипа App.
var ref = Firebase(url:URL)

// ALLOWED? Even though size is invalid, widget has children color and size,
// so write is allowed and the .write rule under color is ignored
ref.setValue(["size": 9999, "color": "red"])

// ALLOWED? Works even if widget does not exist, allowing us to create a widget
// which is invalid and does not have a valid color.
// (allowed by the write rule under "color")
ref.childByAppendingPath("size").setValue(99)
Джава
Firebase ref = new Firebase(URL + "/widget");

// ALLOWED? Even though size is invalid, widget has children color and size,
// so write is allowed and the .write rule under color is ignored
Map<String,Object> map = new HashMap<String, Object>();
map.put("size", 99999);
map.put("color", "red");
ref.setValue(map);

// ALLOWED? Works even if widget does not exist, allowing us to create a widget
// which is invalid and does not have a valid color.
// (allowed by the write rule under "color")
ref.child("size").setValue(99);
ОТДЫХАТЬ
# ALLOWED? Even though size is invalid, widget has children color and size,
# so write is allowed and the .write rule under color is ignored
curl -X PUT -d '{size: 99999, color: "red"}' \
https://docs-examples.firebaseio.com/rest/securing-data/example.json

# ALLOWED? Works even if widget does not exist, allowing us to create a widget
# which is invalid and does not have a valid color.
# (allowed by the write rule under "color")
curl -X PUT -d '99' \
https://docs-examples.firebaseio.com/rest/securing-data/example/size.json

Это иллюстрирует различия между .write и .validate правил. Как показано, все эти правила должны быть написано с использованием .validate , с возможным исключением newData.hasChildren() правило, которое будет зависеть от того, должна ли быть разрешена делеция.

Правила на основе запросов

Хотя вы не можете использовать правила , как фильтры , вы можете ограничить доступ к подмножествам данных с помощью параметров запроса в правилах. Использование query. выражения в ваших правилах, чтобы предоставить доступ для чтения или записи на основе параметров запроса.

Например, следующий запрос на основе правило использует пользовательские на baskets основе правил безопасности и правила на основе запросов , чтобы ограничить доступ к данным в baskets коллекции только корзины активного пользователя владеет:

"baskets": {
  ".read": "auth.uid != null &&
            query.orderByChild == 'owner' &&
            query.equalTo == auth.uid" // restrict basket access to owner of basket
}

Следующий запрос, который включает параметры запроса в правиле, будет успешным:

db.ref("baskets").orderByChild("owner")
                 .equalTo(auth.currentUser.uid)
                 .on("value", cb)                 // Would succeed

Однако запросы , которые не включают в себя параметры в правиле потерпит неудачу с PermissionDenied ошибки:

db.ref("baskets").on("value", cb)                 // Would fail with PermissionDenied

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

Например, следующее правило ограничивает доступ для чтения только первыми 1000 результатами запроса, отсортированными по приоритету:

messages: {
  ".read": "query.orderByKey &&
            query.limitToFirst <= 1000"
}

// Example queries:

db.ref("messages").on("value", cb)                // Would fail with PermissionDenied

db.ref("messages").limitToFirst(1000)
                  .on("value", cb)                // Would succeed (default order by key)

Следующий query. Выражения доступны в Правилах безопасности баз данных в реальном времени.

Выражения правил на основе запросов
Выражение Тип Описание
query.orderByKey
query.orderByPriority
query.orderByValue
логический Верно для запросов, упорядоченных по ключу, приоритету или значению. В противном случае неверно.
query.orderByChild нить
нулевой
Используйте строку для представления относительного пути к дочернему узлу. Например, query.orderByChild == "address/zip" . Если запрос не упорядочен дочерним узлом, это значение равно нулю.
query.startAt
query.endAt
query.equalTo
нить
количество
логический
нулевой
Извлекает границы выполняемого запроса или возвращает null, если не установлено никаких границ.
query.limitToFirst
query.limitToLast
количество
нулевой
Извлекает ограничение на выполнение запроса или возвращает null, если ограничение не установлено.

Следующие шаги

После обсуждения условий вы лучше понимаете Правила и готовы:

Узнайте, как обрабатывать основные варианты использования, и изучите рабочий процесс для разработки, тестирования и развертывания правил:

Изучите особенности правил, характерные для базы данных реального времени: