Référence des règles de sécurité Firebase pour le stockage dans le cloud

Les règles de sécurité Firebase pour Cloud Storage sont utilisées pour déterminer qui a un accès en lecture et en écriture aux fichiers stockés dans Cloud Storage, ainsi que la manière dont les fichiers sont structurés et les métadonnées qu'ils contiennent. Les règles de sécurité du stockage cloud sont composées de règles qui prennent en compte la request et resource pour autoriser ou refuser une action souhaitée, telle que le téléchargement d'un fichier ou la récupération de métadonnées de fichier. Ces documents de référence couvrent les types de règles, les propriétés d'une request et d'une resource , les types de données utilisés par les règles de sécurité Cloud Storage et la manière dont les erreurs se produisent.

Règle

Une rule est une expression évaluée pour déterminer si une request est autorisée à effectuer une action souhaitée.

Les types

Permettre

Les règles allow se composent d'une méthode, telle que read ou write , ainsi que d'une condition facultative. Lorsqu'une règle est exécutée, la condition est évaluée, et si la condition est évaluée à true , la méthode souhaitée est autorisée ; sinon, la méthode est refusée. Une règle allow sans condition autorise toujours la méthode souhaitée.

// Always allow method
allow <method>;

// Allow method if condition is true
allow <method>: if <condition>;

Actuellement, allow est le seul type de règle pris en charge.

Méthodes de demande

Lire

La méthode read couvre toutes les requêtes dans lesquelles des données ou des métadonnées de fichiers sont lues, y compris les téléchargements de fichiers et les lectures de métadonnées de fichiers.

// Always allow reads
allow read;

// Allow reads if condition evaluates to true
allow read: if <condition>;

Écrire

La méthode write couvre toutes les requêtes dans lesquelles des données ou des métadonnées de fichiers sont écrites, y compris les téléchargements de fichiers, les suppressions de fichiers et les mises à jour de métadonnées de fichiers.

// Always allow writes
allow write;

// Allow writes if condition evaluates to true
allow write: if <condition>;

Correspondre

Les règles sont exécutées lorsqu'une request d'utilisateur (telle qu'un téléchargement ou un téléchargement de fichier) correspond à un chemin de fichier couvert par une règle. Une match se compose d'un chemin et d'un corps, qui doivent contenir au moins une règle allow . Si aucun chemin ne correspond, la demande est rejetée.

Vous pouvez match un chemin entièrement nommé ou insérer des caractères génériques pour faire correspondre tous les chemins qui correspondent à un certain modèle.

Segments de chemin

single_segment

Vous pouvez utiliser des segments de chemin unique pour créer une règle qui correspond à un fichier stocké dans Cloud Storage.

// Allow read at "path" if condition evaluates to true
match /path {
  allow read: if <condition>;
}

Plusieurs segments de chemin et chemins imbriqués sont également autorisés :

// Allow read at "path/to/object" if condition evaluates to true
match /path {
  match /to {
    match /object {
      allow read: if <condition>;
    }
  }
}

{single_segment_wildcard}

Si vous souhaitez appliquer une règle à plusieurs fichiers sur le même chemin, vous pouvez utiliser un segment de chemin générique pour faire correspondre tous les fichiers sur un certain chemin. Une variable générique est déclarée dans un chemin en enveloppant une variable entre accolades : {variable} . Cette variable est accessible dans l'instruction match sous forme de string .

// Allow read at any path "/*", if condition evaluates to true
match /{single_path} {
  // Matches "path", "to", or "object" but not "path/to/object"
  allow read: if <condition>;
}

Plusieurs segments de chemin et chemins imbriqués peuvent également avoir des caractères génériques :

// Allow read at any path "/path/*/newPath/*", if condition evaluates to true
match /path/{first_wildcard} {
  match /newPath/{second_wildcard} {
    // Matches "path/to/newPath/newObject" or "path/from/newPath/oldObject"
    allow read: if <condition>;
  }
}

{multi_segment_wildcard=**}

Si vous souhaitez faire correspondre un nombre quelconque de segments de chemin au niveau ou en dessous d'un chemin, vous pouvez utiliser un caractère générique multi-segments, qui correspondra à toutes les demandes vers et en dessous de l'emplacement. Cela peut être utile pour fournir à un utilisateur son propre espace de stockage de forme libre ou pour créer des règles qui correspondent à de nombreux segments de chemin différents (comme la création d'un ensemble de fichiers lisible publiquement ou l'exigence d'une authentification pour toutes les écritures).

Un chemin générique multi-segments est déclaré de la même manière qu'un chemin générique à segment unique, avec l'ajout du =** à la fin de la variable : {variable=**} . Une variable générique multi-segments est disponible dans l'instruction match en tant qu'objet path .

// Allow read at any path "/**", if condition evaluates to true
match /{multi_path=**} {
  // Matches anything at or below this, from "path", "path/to", "path/to/object", ...
  allow read: if <condition>;
}

Demande

La variable request est fournie dans une condition pour représenter la requête effectuée sur ce chemin. La variable request possède un certain nombre de propriétés qui peuvent être utilisées pour décider d'autoriser ou non la requête entrante.

Propriétés

auth

Lorsqu'un utilisateur authentifié effectue une requête sur Cloud Storage, la variable auth est renseignée avec l' uid de l'utilisateur ( request.auth.uid ) ainsi que les revendications du JWT d'authentification Firebase ( request.auth.token ).

request.auth.token contient tout ou partie des clés suivantes :

Champ Description
email L'adresse e-mail associée au compte, si présente.
email_verified true si l'utilisateur a vérifié qu'il a accès à l'adresse email . Certains fournisseurs vérifient automatiquement les adresses e-mail dont ils sont propriétaires.
phone_number Le numéro de téléphone associé au compte, s'il est présent.
name Le nom d’affichage de l’utilisateur, s’il est défini.
sub L'UID Firebase de l'utilisateur. C’est unique dans un projet.
firebase.identities Dictionnaire de toutes les identités associées au compte de cet utilisateur. Les clés du dictionnaire peuvent être les suivantes : email , phone , google.com , facebook.com , github.com , twitter.com . Les valeurs du dictionnaire sont des tableaux d'identifiants uniques pour chaque fournisseur d'identité associé au compte. Par exemple, auth.token.firebase.identities["google.com"][0] contient le premier identifiant Google associé au compte.
firebase.sign_in_provider Fournisseur de connexion utilisé pour obtenir ce jeton. Il peut s'agir de l'une des chaînes suivantes : custom , password , phone , anonymous , google.com , facebook.com , github.com , twitter.com .
firebase.tenant Le tenantId associé au compte, s'il est présent. par exemple tenant2-m6tyz

Si vous utilisez une authentification personnalisée, request.auth.token contient également toutes les revendications personnalisées spécifiées par le développeur.

Lorsqu'un utilisateur non authentifié effectue une requête, request.auth est null .

// Allow requests from authenticated users
allow read, write: if request.auth != null;

path

La variable path contient le chemin sur lequel une request est exécutée.

// Allow a request if the first path segment equals "images"
allow read, write: if request.path[0] == 'images';

resource

La variable resource contient les métadonnées d'un fichier en cours de téléchargement ou les métadonnées mises à jour pour un fichier existant. Ceci est lié à la variable resource , qui contient les métadonnées du fichier actuel au chemin demandé, par opposition aux nouvelles métadonnées.

// Allow a request if the new value is smaller than 5MB
allow read, write: if request.resource.size < 5 * 1024 * 1024;

request.resource contient les propriétés suivantes de resource :

Propriété
name
bucket
metadata
size
contentType

time

La variable time contient un horodatage représentant l'heure actuelle du serveur à laquelle une demande est évaluée. Vous pouvez l'utiliser pour fournir un accès temporel aux fichiers, par exemple : autoriser uniquement le téléchargement des fichiers jusqu'à une certaine date, ou autoriser uniquement la lecture des fichiers jusqu'à une heure après leur téléchargement.

// Allow a read if the file was created less than one hour ago
allow read: if request.time < resource.timeCreated + duration.value(1, 'h');

De nombreuses fonctions sont fournies pour écrire des règles en utilisant des horodatages et des durées .

Ressource

La variable resource contient les métadonnées des fichiers dans Cloud Storage, telles que le nom du fichier, la taille, l'heure de création et les métadonnées personnalisées.

Propriétés

name

Une chaîne contenant le nom complet du fichier, y compris le chemin d'accès au fichier.

// Allow reads if the resource name is "path/to/object"
allow read: if resource.name == 'path/to/object'

bucket

Chaîne contenant le bucket Google Cloud Storage dans lequel ce fichier est stocké.

// Allow reads of all resources in your bucket
allow read: if resource.bucket == '<your-cloud-storage-bucket>'

generation

Un entier contenant la génération d'objets Google Cloud Storage du fichier. Utilisé pour la gestion des versions d'objet.

// Allow reads if the resource matches a known object version
allow read: if resource.generation == <known-generation>

metageneration

Un int contenant la métagénération de l'objet Google Cloud Storage du fichier. Utilisé pour la gestion des versions d'objet.

// Allow reads if the resource matches a known object metadata version
allow read: if resource.metageneration == <known-generation>

size

Un entier contenant la taille du fichier en octets.

// Allow reads if the resource is less than 10 MB
allow read: if resource.size < 10 * 1024 * 1024;

timeCreated

Un horodatage représentant le moment où le fichier a été créé.

// Allow reads if the resource was created less than an hour ago
allow read: if resource.timeCreated < request.time + duration.value(60, "m")

updated

Un horodatage représentant la dernière mise à jour du fichier.

// Allow reads if the resource was updated less than an hour ago
allow read: if resource.updated < request.time + duration.value(60, "m")

md5Hash

Une chaîne contenant le hachage MD5 du fichier.

// Allow writes if the hash of the uploaded file is the same as the existing file
allow write: if request.resource.md5Hash == resource.md5Hash;

crc32c

Une chaîne contenant le hachage crc32c du fichier.

// Allow writes if the hash of the uploaded file is the same as the existing file
allow write: if request.resource.crc32c == resource.crc32c;

etag

Une chaîne contenant l' étiquette etag du fichier.

// Allow writes if the etag matches a known object etag
allow write: if resource.etag == <known-generation>

contentDisposition

Une chaîne contenant la disposition du contenu du fichier.

// Allow reads if the content disposition matches a certain value
allow read: if resource.contentDisposition == 'inlined';

contentEncoding

Une chaîne contenant l'encodage du contenu du fichier.

// Allow reads if the content is encoded with gzip
allow read: if resource.contentEncoding == 'gzip';

contentLanguage

Une chaîne contenant la langue du contenu du fichier.

// Allow reads if the content language is Japanese
allow read: if resource.contentLanguage == 'ja';

contentType

Une chaîne contenant le type de contenu du fichier.

// Allow reads if the content type is PNG.
allow read: if resource.contentType == 'image/png';

metadata

Un Map<String, String> contenant des champs de métadonnées supplémentaires fournis par le développeur.

// Allow reads if a certain metadata field matches a desired value
allow read: if resource.metadata.customProperty == 'customValue';

firestore.get et firestore.exists

Les fonctions firestore.get() et firestore.exists() vous permettent d'accéder aux documents dans Cloud Firestore pour évaluer des critères d'autorisation complexes.

Les fonctions firestore.get() et firestore.exists() attendent toutes deux des chemins de documents entièrement spécifiés. Lorsque vous utilisez des variables pour construire des chemins pour firestore.get() et firestore.exists() , vous devez échapper explicitement aux variables en utilisant la syntaxe $(variable) .

firestore.get

Obtenez le contenu d'un document Cloud Firestore.

service firebase.storage {
  match /b/{bucket}/o {
    match /users/{club}/files/{fileId} {
      allow read: if club in
        firestore.get(/databases/(default)/documents/users/$(request.auth.uid)).data.memberships
    }
  }
}

firestore.exists

Vérifiez si un document Cloud Firestore existe.

service firebase.storage {
  match /b/{bucket}/o {
    match /users/{userId}/photos/{fileId} {
      allow read: if
        firestore.exists(/databases/(default)/documents/users/$(userId)/friends/$(request.auth.uid))
    }
  }
}

Service

Le service est la première déclaration d'un fichier de règles de sécurité Cloud Storage et spécifie le service auquel ces règles s'appliqueront.

Nom

name

Le nom du service auquel les règles seront appliquées. La seule valeur actuelle est firebase.storage .

// Specify the service name
service firebase.storage {
  match /b/{bucket}/o {
    ...
  }
}

Types de données

Le langage Rules vous permet de vérifier le type à l’aide de l’opérateur is .

// For example
a is null
a is string

null

Le type de données null représente une valeur qui n'existe pas.

allow read: if request.auth != null;

bool

Le type bool représente une valeur booléenne true ou false .

allow read: if true;   // always succeeds
allow write: if false; // always fails

Comparaison

Les valeurs booléennes peuvent être comparées à l'aide des opérateurs == != .

Opérations booléennes

Opération Expression
AND x && y
OR x || y
NOT !x

Les opérations court-circuitent et peuvent renvoyer true , false ou Error .

allow read: if true || false;   // always succeeds, short circuits at true
allow write: if false && true; // always fails, short circuits at false

int et float

Les types int et float représentent des nombres. Les entiers sont : 0 , 1 , -2 , etc. , tandis que les flottants sont : 1.0 , -2.0 , 3.33 , etc.

Les entiers sont des valeurs signées 64 bits et les flottants sont des valeurs 64 bits conformes à la norme IEEE 754. Les valeurs de type int seront contraintes de float lorsqu'elles sont utilisées dans des comparaisons et des opérations arithmétiques avec une valeur float .

Comparaison

Les entiers et les flottants peuvent être comparés et ordonnés à l'aide des opérateurs == , != , > , < , >= et <= .

Arithmétique

Les entiers et les flottants peuvent être ajoutés, soustraits, multipliés, divisés, modulés et annulés :

Opération Expression
Ajout x + y
Soustraction x - y
Multiplication x * y
Division x / y
Module x % y
Négation -x

Fonctions mathématiques

Les règles de sécurité Firebase pour Cloud Storage fournissent également un certain nombre de fonctions d'assistance mathématique pour simplifier les expressions :

Fonction Description
math.ceil(x) Plafond de la valeur numérique
math.floor(x) Plancher de la valeur numérique
math.round(x) Arrondissez la valeur d'entrée à l'entier le plus proche
math.abs(x) Valeur absolue de l'entrée
math.isInfinite(x) Teste si la valeur est ±∞ , renvoie un bool
math.isNaN(x) Teste si la valeur n'est pas un nombre NaN , renvoie un bool

string

Comparaison

Les chaînes peuvent être comparées et ordonnées lexographiquement à l'aide des opérateurs == , != , > , < , >= et <= .

Enchaînement

Les chaînes peuvent être concaténées à l'aide de l'opérateur + .

// Concatenate a file name and extension
'file' + '.txt'

Indice et plage

L'opérateur index , string[] , renvoie une chaîne qui contient le caractère à l'index fourni dans la chaîne.

// Allow reads of files that begin with 'a'
match /{fileName} {
  allow read: if fileName[0] == 'a';
}

L'opérateur range , string[i:j] , renvoie une chaîne qui contient les caractères entre les indices spécifiés, de i (inclus) jusqu'à j (exclusif). Si i ou j ne sont pas spécifiés, leur valeur par défaut est respectivement 0 et la taille de la chaîne, mais au moins i ou j doivent être spécifiés pour que la plage soit valide.

// Allow reads of files that begin with 'abcdef'
match /{fileName} {
  allow read: if fileName[0:6] == 'abcdef';
}

Les opérateurs index et range généreront une erreur si les indices fournis dépassent les limites de la chaîne.

size

Renvoie le nombre de caractères dans la chaîne.

// Allow files with names less than 10 characters
match /{fileName} {
  allow write: if fileName.size() < 10;
}

matches

Effectue une correspondance d'expression régulière, renvoie true si la chaîne correspond à l'expression régulière donnée. Utilise la syntaxe Google RE2 .

// Allow writes to files which end in ".txt"
match /{fileName} {
  allow write: if fileName.matches('.*\\.txt')
}

split

Divise une chaîne selon une expression régulière fournie et renvoie une list de chaînes. Utilise la syntaxe Google RE2 .

// Allow files named "file.*" to be uploaded
match /{fileName} {
  allow write: if fileName.split('.*\\..*')[0] == 'file'
}

path

Les chemins sont des noms de type répertoire avec une correspondance de modèle facultative. La présence d'une barre oblique / indique le début d'un segment de chemin.

path

Convertit un argument string en path .

// Allow reads on a specific file path
match /{allFiles=**} {
  allow read: if allFiles == path('/path/to/file');
}

timestamp

Les horodatages sont en UTC, avec des valeurs possibles commençant à 0001-01-01T00.00.00Z et se terminant à 9999-12-31T23.59.59Z.

Comparaison

Les horodatages peuvent être comparés et classés à l'aide des opérateurs == , != , > , < , >= et <= .

Arithmétique

Les horodatages prennent en charge l'addition et la soustraction entre les horodatages et les durées comme suit :

Expression Résultat
timestamp + duration timestamp
duration + timestamp timestamp
timestamp - duration timestamp
timestamp - timestamp duration
duration + duration duration
duration - duration duration

date

Une valeur timestamp contenant uniquement l' year , month et day .

// Allow reads on the same day that the resource was created.
allow read: if request.time.date() == resource.timeCreated.date()

year

La valeur de l'année sous forme d'entier, de 1 à 9999.

// Allow reads on all requests made before 2017
allow read: if request.time.year() < 2017

month

La valeur du mois sous forme d'entier, de 1 à 12.

// Allow reads on all requests made during the month of January
allow read: if request.time.month() == 1;

day

Le jour actuel du mois sous forme d'entier, de 1 à 31.

// Allow reads on all requests made during the first day of each month
allow read: if request.time.day() == 1;

time

Une valeur duration contenant l’heure actuelle.

// Allow reads on all requests made before 12PM
allow read: if request.time.time() < duration.time(12, 0, 0, 0);

hours

La valeur des heures est un entier, de 0 à 23.

// Allow reads on all requests made before 12PM
allow read: if request.time.hours() < 12;

minutes

La valeur des minutes sous forme d'entier, de 0 à 59.

// Allow reads during even minutes of every hour
allow read: if request.time.minutes() % 2 == 0;

seconds

La valeur des secondes sous forme d'entier, de 0 à 59.

// Allow reads during the second half of each minute
allow read: if request.time.seconds() > 29;

nanos

Les fractions de seconde en nanos sous forme d'int.

// Allow reads during the first 0.1 seconds of each second
allow read: if request.time.nanos() < 100000000;

dayOfWeek

Le jour de la semaine, de 1 (lundi) à 7 (dimanche).

// Allow reads on weekdays (Monday to Friday)
allow read: if request.time.dayOfWeek() < 6;

dayOfYear

Le jour de l'année en cours, de 1 à 366.

// Allow reads every fourth day
allow read: if request.time.dayOfYear() % 4 == 0;

toMillis

Renvoie le nombre actuel de millisecondes depuis l'époque Unix.

// Allow reads if the request is made before a specified time
allow read: if request.time.toMillis() < <milliseconds>;

duration

Les valeurs de durée sont représentées en secondes plus des fractions de secondes en nanosecondes.

Comparaison

Les durées peuvent être comparées et classées à l'aide des opérateurs == , != , > , < , >= et <= .

Arithmétique

Les durées prennent en charge l'addition et la soustraction entre les horodatages et les durées comme suit :

Expression Résultat
timestamp + duration timestamp
duration + timestamp timestamp
timestamp - duration timestamp
timestamp - timestamp duration
duration + duration duration
duration - duration duration

seconds

Le nombre de secondes dans la durée actuelle. Doit être compris entre -315 576 000 000 et +315 576 000 000 inclus.

nanos

Le nombre de fractions de secondes (en nanosecondes) de la durée actuelle. Doit être compris entre -999 999 999 et +999 999 999 inclus. Pour les secondes non nulles et les nanosecondes non nulles, les signes des deux doivent être en accord.

duration.value

Les durées peuvent être créées à l'aide de la fonction duration.value(int magnitude, string units) , qui crée une durée à partir de la magnitude et de l'unité données.

// All of these durations represent one hour:
duration.value(1, "h")
duration.value(60, "m")
duration.value(3600, "s")

unit possibles sont :

Durée unit
Semaines w
Jours d
Heures h
Minutes m
Secondes s
Millisecondes ms
Nanosecondes ns

duration.time

Les durées peuvent être créées à l'aide de la fonction duration.time(int hours, int minutes, int seconds, int nanoseconds) , qui crée une durée des heures, minutes, secondes et nanosecondes données.

// Create a four hour, three minute, two second, one nanosecond duration
duration.time(4, 3, 2, 1)

list

Une liste contient un tableau ordonné de valeurs, qui peuvent être de type : null , bool , int , float , string , path , list , map , timestamp ou duration .

Étant donné x et y de type list et i et j de type int

Création

Pour créer une liste, ajoutez des valeurs entre parenthèses :

// Create a list of strings
['apples', 'grapes', 'bananas', 'cheese', 'goats']

Comparaison

Les listes peuvent être comparées à l'aide des opérateurs == != . L'égalité de deux listes nécessite que toutes les valeurs soient égales.

Indice et plage

L'opérateur index , list[] , renvoie l'élément à l'index fourni dans la liste.

// Allow reads of all files that begin with 'a'
match /{fileName} {
  allow read: if fileName[0] == 'a';
}

L'opérateur range , list[i:j] , renvoie tous les éléments d'une liste entre les indices spécifiés, de i (inclus) jusqu'à j (exclusif). Si i ou j ne sont pas spécifiés, leur valeur par défaut est respectivement 0 et la taille de la liste, mais au moins i ou j doivent être spécifiés pour que la plage soit valide.

// Allow reads of all files that begin with 'abcdef'
match /{fileName} {
  allow read: if fileName[0:6] == 'abcdef';
}

in

Renvoie true si la valeur souhaitée est présente dans la liste ou false si elle n'est pas présente.

// Allow read if a filename has the string 'txt' in it
match /{fileName} {
  allow read: if 'txt' in fileName.split('\\.');
}

join

Combine une liste de chaînes en une seule chaîne, séparées par la chaîne donnée.

// Allow reads if the joined array is 'file.txt'
allow read: if ['file', 'txt'].join('.') == 'file.txt';

size

Le nombre d'éléments dans la liste.

// Allow read if there are three items in our list
allow read: if ['foo', 'bar', 'baz'].size() == 3;

hasAll

Renvoie true si toutes les valeurs sont présentes dans la liste.

// Allow read if one list has all items in the other list
allow read: if ['file', 'txt'].hasAll(['file', 'txt']);

map

Une carte contient des paires clé/valeur, où les clés sont des chaînes et les valeurs peuvent être l'une des valeurs suivantes : null , bool , int , float , string , path , list , map , timestamp ou duration .

Création

Pour créer une carte, ajoutez des paires clé/valeur entre accolades :

// Create a map of strings to strings
{
  'mercury': 'mars',
  'rain': 'cloud',
  'cats': 'dogs',
}

Comparaison

Les cartes peuvent être comparées à l'aide des opérateurs == != . L'égalité de deux cartes nécessite que toutes les clés soient présentes dans les deux cartes et que toutes les valeurs soient égales.

Indice

Les valeurs d'une carte sont accessibles en utilisant la notation entre crochets ou par points :

// Access custom metadata properties
allow read: if resource.metadata.property == 'property'
allow write: if resource.metadata['otherProperty'] == 'otherProperty'

Si une clé n’est pas présente, une error sera renvoyée.

in

Renvoie true si la clé souhaitée est présente dans la carte ou false si elle n'est pas présente.

// Allow reads if a property is present in the custom metadata
allow read: if property in resource.metadata;

size

Le nombre de clés dans la carte.

// Allow reads if there's exactly one custom metadata key
allow read: if resource.metadata.size() == 1;

keys

Une liste de toutes les clés de la carte.

// Allow reads if the first metadata key is 'myKey'
allow read: if resource.metadata.keys()[0] == 'myKey';

values

Une liste de toutes les valeurs de la carte, par ordre clé.

// Allow reads if the first metadata value is 'myValue'
allow read: if resource.metadata.values()[0] == 'myValue';

les erreurs

Évaluation des erreurs

Les règles de sécurité Firebase pour Cloud Storage poursuivent l'évaluation lorsque des erreurs sont rencontrées. Ceci est utile car conditionnel && et || les expressions peuvent absorber une erreur si le conditionnel court-circuiterait respectivement à false ou true . Par exemple:

Expression Résultat
error && true error
error && false false
error || true true
error || false error

Les endroits courants où des erreurs sont générées sont : la division par zéro, l'accès à des valeurs dans une liste ou une carte qui n'existent pas et la transmission de valeurs de type incorrect à une fonction.

// Error if resource.size is zero
allow read: if 1000000 / resource.size;

// Error, key doesn't exist
allow read: if resource.metadata.nonExistentKey == 'value';

// Error, no unit 'y' exists
allow read: if request.time < resource.timeCreated + duration.value(1, 'y');