Изучите основной синтаксис языка правил безопасности Realtime Database.

Правила безопасности Firebase Realtime Database позволяют управлять доступом к данным, хранящимся в базе данных. Гибкий синтаксис правил позволяет создавать правила, которые соответствуют любым операциям – от всех записей в базу данных до операций на отдельных узлах.

Правила безопасности Realtime Database – это декларативная конфигурация для вашей базы данных. Это означает, что правила определяются отдельно от логики продукта. Это дает ряд преимуществ: клиенты не несут ответственности за обеспечение безопасности, ошибки в реализации не ставят под угрозу ваши данные, и, что, возможно, наиболее важно, нет необходимости в промежуточном арбитре, таком как сервер, для защиты данных от мира.

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

Структурирование правил безопасности

Правила безопасности Realtime Database состоят из выражений, похожих на JavaScript, которые содержатся в документе JSON. Структура правил должна соответствовать структуре данных, хранящихся в базе данных.

Базовые правила определяют набор узлов, которые нужно защитить, методы доступа (например, чтение, запись) и условия, при которых доступ разрешается или запрещается. В примерах ниже условия будут простыми операторами true и false, но в следующем разделе мы рассмотрим более динамичные способы выражения условий.

Например, если мы хотим защитить child_node в parent_node, общий синтаксис будет следующим:

{
  "rules": {
    "parent_node": {
      "child_node": {
        ".read": <condition>,
        ".write": <condition>,
        ".validate": <condition>,
      }
    }
  }
}

Давайте применим этот шаблон. Например, у вас есть список сообщений, который выглядит следующим образом:

{
  "messages": {
    "message0": {
      "content": "Hello",
      "timestamp": 1405704370369
    },
    "message1": {
      "content": "Goodbye",
      "timestamp": 1405704395231
    },
    ...
  }
}

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

{
  "rules": {
    // For requests to access the 'messages' node...
    "messages": {
      // ...and the individual wildcarded 'message' nodes beneath
      // (we'll cover wildcarding variables more a bit later)....
      "$message": {

        // For each message, allow a read operation if <condition>. In this
        // case, we specify our condition as "true", so read access is always granted.
        ".read": "true",

        // For read-only behavior, we specify that for write operations, our
        // condition is false.
        ".write": "false"
      }
    }
  }
}

Основные операции с правилами

Существует три типа правил для обеспечения безопасности в зависимости от типа операции, выполняемой с данными: .write, .read и .validate. Вот краткое описание их целей:

Типы правил
.read Описывает, разрешено ли пользователям читать данные и когда.
.write Описывает, разрешено ли записывать данные и когда.
.validate Определяет, как должно выглядеть правильно отформатированное значение, есть ли у него дочерние атрибуты и тип данных.

Переменные для записи подстановочных знаков

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

{
  "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 }
    }
  }
}

Каскад правил чтения и записи

Правила .read и .write работают сверху вниз, при этом правила более высокого уровня переопределяют правила более низкого уровня. Если правило предоставляет разрешения на чтение или запись по определенному пути, оно также предоставляет доступ ко всем дочерним узлам под ним. Рассмотрим следующую структуру:

{
  "rules": {
     "foo": {
        // allows read to /foo/*
        ".read": "data.child('baz').val() === true",
        "bar": {
          /* ignored, since read was allowed already */
          ".read": false
        }
     }
  }
}

Эта структура безопасности позволяет читать данные из /bar/, когда /foo/ содержит дочерний элемент baz со значением true. Правило ".read": false в /foo/bar/ не действует, поскольку доступ нельзя отозвать в дочернем пути.

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

Обратите внимание, что правила .validate не каскадируются. Чтобы запись была разрешена, все правила проверки должны соблюдаться на всех уровнях иерархии.

Правила – это не фильтры

Правила применяются атомарно. Это означает, что операция чтения или записи немедленно завершается ошибкой, если в этом месте или в родительском месте нет правила, предоставляющего доступ. Даже если все затронутые дочерние пути доступны, чтение в родительском местоположении полностью завершится ошибкой. Рассмотрим следующую структуру:

{
  "rules": {
    "records": {
      "rec1": {
        ".read": true
      },
      "rec2": {
        ".read": false
      }
    }
  }
}

Если не знать, что правила оцениваются атомарно, может показаться, что при получении пути /records/ будет возвращено значение rec1, но не rec2. Однако фактический результат – ошибка:

JavaScript
var db = firebase.database();
db.ref("records").once("value", function(snap) {
  // success method is not called
}, function(err) {
  // error callback triggered with PERMISSION_DENIED
});
Objective-C
Примечание. Эта функция Firebase недоступна для блиц-приложений.
FIRDatabaseReference *ref = [[FIRDatabase database] reference];
[[_ref child:@"records"] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
  // success block is not called
} withCancelBlock:^(NSError * _Nonnull error) {
  // cancel block triggered with PERMISSION_DENIED
}];
Swift
Примечание. Эта функция Firebase недоступна для блиц-приложений.
var ref = FIRDatabase.database().reference()
ref.child("records").observeSingleEventOfType(.Value, withBlock: { snapshot in
    // success block is not called
}, withCancelBlock: { error in
    // cancel block triggered with PERMISSION_DENIED
})
Java
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference ref = database.getReference("records");
ref.addListenerForSingleValueEvent(new ValueEventListener() {
  @Override
  public void onDataChange(DataSnapshot snapshot) {
    // success method is not called
  }

  @Override
  public void onCancelled(FirebaseError firebaseError) {
    // error callback triggered with PERMISSION_DENIED
  });
});
REST
curl https://docs-examples.firebaseio.com/rest/records/
# response returns a PERMISSION_DENIED error

Поскольку операция чтения в /records/ является атомарной и нет правила чтения, которое предоставляет доступ ко всем данным в /records/, это приведет к ошибке PERMISSION_DENIED. Если мы оценим это правило в симуляторе безопасности в нашей консоли Firebase, мы увидим, что операция чтения была отклонена, поскольку ни одно правило чтения не разрешало доступ к пути /records/. Однако обратите внимание, что правило для rec1 не было проверено, поскольку оно не находилось в запрошенном пути. Чтобы получить доступ к rec1, нам нужно обратиться к нему напрямую:

JavaScript
var db = firebase.database();
db.ref("records/rec1").once("value", function(snap) {
  // SUCCESS!
}, function(err) {
  // error callback is not called
});
Objective-C
Примечание. Эта функция Firebase недоступна для блиц-приложений.
FIRDatabaseReference *ref = [[FIRDatabase database] reference];
[[ref child:@"records/rec1"] observeSingleEventOfType:FEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
    // SUCCESS!
}];
Swift
Примечание. Эта функция Firebase недоступна для блиц-приложений.
var ref = FIRDatabase.database().reference()
ref.child("records/rec1").observeSingleEventOfType(.Value, withBlock: { snapshot in
    // SUCCESS!
})
Java
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference ref = database.getReference("records/rec1");
ref.addListenerForSingleValueEvent(new ValueEventListener() {
  @Override
  public void onDataChange(DataSnapshot snapshot) {
    // SUCCESS!
  }

  @Override
  public void onCancelled(FirebaseError firebaseError) {
    // error callback is not called
  }
});
REST
curl https://docs-examples.firebaseio.com/rest/records/rec1
# SUCCESS!

Пересекающиеся утверждения

К узлу может применяться несколько правил. Если узел определяется несколькими выражениями правил, способ доступа запрещается, если любое из условий имеет значение false:

{
  "rules": {
    "messages": {
      // A rule expression that applies to all nodes in the 'messages' node
      "$message": {
        ".read": "true",
        ".write": "true"
      },
      // A second rule expression applying specifically to the 'message1` node
      "message1": {
        ".read": "false",
        ".write": "false"
      }
    }
  }
}

В примере выше чтение узла message1 будет запрещено, поскольку второе правило всегда имеет значение false, несмотря на то, что первое правило всегда имеет значение true.

Дальнейшие действия

Вы можете узнать больше о правилах безопасности Firebase Realtime Database:

  • Изучите следующую важную концепцию языка Security Rules – динамические условия. Они позволяют Security Rulesпроверять авторизацию пользователей, сравнивать существующие и входящие данные, проверять входящие данные, проверять структуру запросов, поступающих от клиента, и многое другое.

  • Ознакомьтесь с типичными сценариями использования и определениями правил безопасности Firebase, которые к ним относятся.