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

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

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

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

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

Правила безопасности базы данных в реальном времени состоят из выражений, подобных 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 . Вот краткий обзор их целей:

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

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

Все операторы правил указывают на узлы. Оператор может указывать на конкретный узел или использовать переменные захвата с подстановочными знаками $ для указания на наборы узлов на уровне иерархии. Используйте эти переменные захвата, чтобы сохранить значение ключей узлов для использования в последующих операторах правил. Этот метод позволяет писать более сложные условия 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
});
Цель-C
Примечание. Этот продукт Firebase недоступен для цели App Clip.
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
}];
Быстрый
Примечание. Этот продукт Firebase недоступен для цели App Clip.
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
})
Джава
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
  });
});
ОТДЫХ
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
});
Цель-C
Примечание. Этот продукт Firebase недоступен для цели App Clip.
FIRDatabaseReference *ref = [[FIRDatabase database] reference];
[[ref child:@"records/rec1"] observeSingleEventOfType:FEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
    // SUCCESS!
}];
Быстрый
Примечание. Этот продукт Firebase недоступен для цели App Clip.
var ref = FIRDatabase.database().reference()
ref.child("records/rec1").observeSingleEventOfType(.Value, withBlock: { snapshot in
    // SUCCESS!
})
Джава
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
  }
});
ОТДЫХ
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:

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

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