Apprenez la syntaxe de base du langage Realtime Database Rules

Les règles de sécurité de la base de données en temps réel Firebase vous permettent de contrôler l'accès aux données stockées dans votre base de données. La syntaxe flexible des règles vous permet de créer des règles qui correspondent à tout, de toutes les écritures dans votre base de données aux opérations sur des nœuds individuels.

Les règles de sécurité de la base de données en temps réel sont une configuration déclarative pour votre base de données. Cela signifie que les règles sont définies séparément de la logique du produit. Cela présente un certain nombre d'avantages : les clients ne sont pas responsables de l'application de la sécurité, les implémentations boguées ne compromettront pas vos données, et peut-être plus important encore, il n'y a pas besoin d'un arbitre intermédiaire, tel qu'un serveur, pour protéger les données du monde.

Cette rubrique décrit la syntaxe et la structure de base des règles de sécurité de base de données en temps réel utilisées pour créer des ensembles de règles complets.

Structurer vos règles de sécurité

Les règles de sécurité de base de données en temps réel sont constituées d'expressions de type JavaScript contenues dans un document JSON. La structure de vos règles doit suivre la structure des données que vous avez stockées dans votre base de données.

Les règles de base identifient un ensemble de nœuds à sécuriser, les méthodes d'accès (par exemple, lecture, écriture) impliquées et les conditions dans lesquelles l'accès est autorisé ou refusé. Dans les exemples suivants, nos conditions seront de simples déclarations true et false , mais dans la rubrique suivante, nous aborderons des manières plus dynamiques d'exprimer des conditions.

Ainsi, par exemple, si nous essayons de sécuriser un child_node sous un parent_node , la syntaxe générale à suivre est :

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

Appliquons ce modèle. Par exemple, disons que vous gardez une trace d'une liste de messages et que vous avez des données qui ressemblent à ceci :

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

Vos règles doivent être structurées de la même manière. Voici un ensemble de règles de sécurité en lecture seule qui pourraient avoir un sens pour cette structure de données. Cet exemple illustre comment nous spécifions les nœuds de base de données auxquels les règles s'appliquent et les conditions d'évaluation des règles sur ces nœuds.

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

Règles de base Opérations

Il existe trois types de règles pour appliquer la sécurité en fonction du type d'opération effectuée sur les données : .write , .read et .validate . Voici un bref résumé de leurs objectifs :

Types de règles
.lis Décrit si et quand les données sont autorisées à être lues par les utilisateurs.
.écrivez Décrit si et quand les données sont autorisées à être écrites.
.valider Définit à quoi ressemblera une valeur correctement formatée, si elle a des attributs enfants et le type de données.

Variables de capture génériques

Toutes les instructions de règles pointent vers des nœuds. Une instruction peut pointer vers un nœud spécifique ou utiliser des variables de capture génériques $ pour pointer vers des ensembles de nœuds à un niveau de la hiérarchie. Utilisez ces variables de capture pour stocker la valeur des clés de nœud à utiliser dans les instructions de règles suivantes. Cette technique vous permet d'écrire des conditions de règles plus complexes , ce que nous aborderons plus en détail dans la rubrique suivante.

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

Les variables dynamiques $ peuvent également être utilisées en parallèle avec des noms de chemin constants. Dans cet exemple, nous utilisons la variable $other pour déclarer une règle .validate qui garantit que le widget n'a pas d'autres enfants que title et color . Toute écriture qui entraînerait la création d'enfants supplémentaires échouerait.

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

Cascade de règles de lecture et d'écriture

.read règles .read et .write fonctionnent de haut en bas, les règles moins profondes remplaçant les règles plus profondes. Si une règle accorde des autorisations de lecture ou d'écriture sur un chemin particulier, elle accorde également l'accès à tous les nœuds enfants sous-jacents. Considérez la structure suivante :

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

Cette structure de sécurité permet à /bar/ d'être lu à partir de chaque fois que /foo/ contient un enfant baz avec la valeur true . La ".read": false sous /foo/bar/ n'a aucun effet ici, car l'accès ne peut pas être révoqué par un chemin enfant.

Bien que cela ne semble pas immédiatement intuitif, il s'agit d'une partie puissante du langage de règles et permet de mettre en œuvre des privilèges d'accès très complexes avec un effort minimal. Cela sera illustré lorsque nous aborderons la sécurité basée sur l'utilisateur plus loin dans ce guide.

Notez que les règles .validate ne sont pas en cascade. Toutes les règles de validation doivent être satisfaites à tous les niveaux de la hiérarchie pour qu'une écriture soit autorisée.

Les règles ne sont pas des filtres

Les règles sont appliquées de manière atomique. Cela signifie qu'une opération de lecture ou d'écriture échoue immédiatement s'il n'y a pas de règle à cet emplacement ou à un emplacement parent qui accorde l'accès. Même si chaque chemin enfant affecté est accessible, la lecture à l'emplacement parent échouera complètement. Considérez cette structure :

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

Sans comprendre que les règles sont évaluées de manière atomique, il peut sembler que la récupération du chemin /records/ renvoie rec1 mais pas rec2 . Le résultat réel, cependant, est une erreur :

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
});
Objectif c
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
}];
Rapide
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
  });
});
DU REPOS
curl https://docs-examples.firebaseio.com/rest/records/
# response returns a PERMISSION_DENIED error

Étant donné que l'opération de lecture sur /records/ est atomique et qu'il n'y a pas de règle de lecture qui autorise l'accès à toutes les données sous /records/ , cela PERMISSION_DENIED une erreur PERMISSION_DENIED . Si nous évaluons cette règle dans le simulateur de sécurité de notre console Firebase , nous pouvons voir que l'opération de lecture a été refusée car aucune règle de lecture n'autorisait l'accès au chemin /records/ . Cependant, notez que la règle pour rec1 n'a jamais été évaluée car elle n'était pas dans le chemin que nous avons demandé. Pour récupérer rec1 , nous aurions besoin d'y accéder directement :

JavaScript
var db = firebase.database();
db.ref("records/rec1").once("value", function(snap) {
  // SUCCESS!
}, function(err) {
  // error callback is not called
});
Objectif c
FIRDatabaseReference *ref = [[FIRDatabase database] reference];
[[ref child:@"records/rec1"] observeSingleEventOfType:FEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
    // SUCCESS!
}];
Rapide
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
  }
});
DU REPOS
curl https://docs-examples.firebaseio.com/rest/records/rec1
# SUCCESS!

Chevauchement des déclarations

Il est possible que plusieurs règles s'appliquent à un nœud. Dans le cas où plusieurs expressions de règles identifient un nœud, la méthode d'accès est refusée si l' une des conditions est 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"
      }
    }
  }
}

Dans l'exemple ci-dessus, les lectures sur le nœud message1 seront refusées car la deuxième règle est toujours false , même si la première règle est toujours true .

Prochaines étapes

Vous pouvez approfondir votre compréhension des règles de sécurité de la base de données en temps réel Firebase :

  • Découvrez le prochain concept majeur du langage Rules, les conditions dynamiques , qui permettent à vos règles de vérifier l'autorisation de l'utilisateur, de comparer les données existantes et entrantes, de valider les données entrantes, de vérifier la structure des requêtes provenant du client, etc.

  • Passez en revue les cas d'utilisation de sécurité typiques et les définitions des règles de sécurité Firebase qui les traitent .