Check out what’s new from Firebase at Google I/O 2022. Learn more

Aprenda la sintaxis central del lenguaje de reglas de la base de datos en tiempo real

Las reglas de seguridad de Firebase Realtime Database le permiten controlar el acceso a los datos almacenados en su base de datos. La sintaxis de reglas flexibles le permite crear reglas que coincidan con cualquier cosa, desde todas las escrituras en su base de datos hasta operaciones en nodos individuales.

Las reglas de seguridad de la base de datos en tiempo real son una configuración declarativa para su base de datos. Esto significa que las reglas se definen por separado de la lógica del producto. Esto tiene una serie de ventajas: los clientes no son responsables de hacer cumplir la seguridad, las implementaciones con errores no comprometerán sus datos y, quizás lo más importante, no hay necesidad de un árbitro intermedio, como un servidor, para proteger los datos del mundo.

Este tema describe la sintaxis y la estructura básicas de las reglas de seguridad de la base de datos en tiempo real que se utilizan para crear conjuntos de reglas completos.

Estructuración de sus reglas de seguridad

Las reglas de seguridad de la base de datos en tiempo real se componen de expresiones similares a JavaScript contenidas en un documento JSON. La estructura de sus reglas debe seguir la estructura de los datos que ha almacenado en su base de datos.

Las reglas básicas identifican un conjunto de nodos que deben protegerse, los métodos de acceso (por ejemplo, lectura, escritura) involucrados y las condiciones bajo las cuales se permite o deniega el acceso. En los siguientes ejemplos, nuestras condiciones serán declaraciones true y false simples, pero en el siguiente tema cubriremos formas más dinámicas de expresar condiciones.

Entonces, por ejemplo, si estamos tratando de asegurar un child_node bajo un parent_node , la sintaxis general a seguir es:

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

Apliquemos este patrón. Por ejemplo, supongamos que está realizando un seguimiento de una lista de mensajes y tiene datos que se ven así:

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

Sus reglas deben estar estructuradas de manera similar. Aquí hay un conjunto de reglas para la seguridad de solo lectura que podría tener sentido para esta estructura de datos. Este ejemplo ilustra cómo especificamos los nodos de la base de datos a los que se aplican las reglas y las condiciones para evaluar las reglas en esos nodos.

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

Operaciones de reglas básicas

Hay tres tipos de reglas para hacer cumplir la seguridad según el tipo de operación que se realiza en los datos: .write , .read y .validate . He aquí un breve resumen de sus propósitos:

Tipos de reglas
.leer Describe si los usuarios pueden leer los datos y cuándo.
.escribir Describe si se permite escribir datos y cuándo.
.validar Define el aspecto que tendrá un valor con el formato correcto, si tiene atributos secundarios y el tipo de datos.

Variables de captura comodín

Todas las declaraciones de reglas apuntan a nodos. Una instrucción puede apuntar a un nodo específico o usar variables de captura comodín $ para apuntar a conjuntos de nodos en un nivel de la jerarquía. Utilice estas variables de captura para almacenar el valor de las claves de nodo para su uso dentro de declaraciones de reglas posteriores. Esta técnica le permite escribir condiciones de Reglas más complejas, algo que trataremos con más detalle en el siguiente tema.

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

Las variables dinámicas $ también se pueden usar en paralelo con nombres de rutas constantes. En este ejemplo, usamos la variable $other para declarar una regla .validate que garantiza que el widget no tenga elementos secundarios además de title y color . Cualquier escritura que resulte en la creación de elementos secundarios adicionales fallará.

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

Cascada de reglas de lectura y escritura

Las reglas .read y .write funcionan de arriba hacia abajo, con reglas superficiales que anulan las reglas más profundas. Si una regla otorga permisos de lectura o escritura en una ruta en particular, también otorga acceso a todos los nodos secundarios debajo de ella. Considere la siguiente estructura:

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

Esta estructura de seguridad permite leer /bar/ siempre que /foo/ contenga un baz secundario con valor true . La regla ".read": false en /foo/bar/ no tiene efecto aquí, ya que una ruta secundaria no puede revocar el acceso.

Si bien puede no parecer inmediatamente intuitivo, esta es una parte poderosa del lenguaje de reglas y permite implementar privilegios de acceso muy complejos con un esfuerzo mínimo. Esto se ilustrará cuando nos adentremos en la seguridad basada en el usuario más adelante en esta guía.

Tenga en cuenta que las reglas .validate no se aplican en cascada. Todas las reglas de validación deben cumplirse en todos los niveles de la jerarquía para que se permita una escritura.

Las reglas no son filtros

Las reglas se aplican de manera atómica. Eso significa que una operación de lectura o escritura falla inmediatamente si no hay una regla en esa ubicación o en una ubicación principal que otorga acceso. Incluso si se puede acceder a todas las rutas secundarias afectadas, la lectura en la ubicación principal fallará por completo. Considere esta estructura:

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

Sin comprender que las reglas se evalúan atómicamente, podría parecer que obtener la ruta /records/ devolvería rec1 pero no rec2 . El resultado real, sin embargo, es un error:

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 objetivo
Nota: Este producto de Firebase no está disponible en el objetivo 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
}];
Rápido
Nota: Este producto de Firebase no está disponible en el objetivo 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
})
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
  });
});
DESCANSAR
curl https://docs-examples.firebaseio.com/rest/records/
# response returns a PERMISSION_DENIED error

Dado que la operación de lectura en /records/ es atómica, y no hay una regla de lectura que otorgue acceso a todos los datos en /records/ , generará un error PERMISSION_DENIED . Si evaluamos esta regla en el simulador de seguridad en nuestra consola Firebase , podemos ver que la operación de lectura fue denegada porque ninguna regla de lectura permitió el acceso a la ruta /records/ . Sin embargo, tenga en cuenta que la regla para rec1 nunca se evaluó porque no estaba en la ruta que solicitamos. Para obtener rec1 , necesitaríamos acceder a él directamente:

JavaScript
var db = firebase.database();
db.ref("records/rec1").once("value", function(snap) {
  // SUCCESS!
}, function(err) {
  // error callback is not called
});
C objetivo
Nota: Este producto de Firebase no está disponible en el objetivo App Clip.
FIRDatabaseReference *ref = [[FIRDatabase database] reference];
[[ref child:@"records/rec1"] observeSingleEventOfType:FEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
    // SUCCESS!
}];
Rápido
Nota: Este producto de Firebase no está disponible en el objetivo App Clip.
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
  }
});
DESCANSAR
curl https://docs-examples.firebaseio.com/rest/records/rec1
# SUCCESS!

Declaraciones superpuestas

Es posible que se aplique más de una regla a un nodo. En el caso de que varias expresiones de reglas identifiquen un nodo, se deniega el método de acceso si alguna de las condiciones es 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"
      }
    }
  }
}

En el ejemplo anterior, se denegarán las lecturas en el nodo message1 porque la segunda regla siempre es false , aunque la primera regla siempre es true .

Próximos pasos

Puede profundizar su comprensión de las reglas de seguridad de la base de datos en tiempo real de Firebase:

  • Aprenda el próximo concepto principal del lenguaje de reglas, condiciones dinámicas, que le permiten a sus reglas verificar la autorización del usuario, comparar datos existentes y entrantes, validar datos entrantes, verificar la estructura de consultas provenientes del cliente y más.

  • Revise los casos de uso de seguridad típicos y las definiciones de las reglas de seguridad de Firebase que los abordan .