web-dev-qa-db-fra.com

Règles de sécurité Firestore - Comment puis-je vérifier qu'un champion est/n'est pas en cours de modification?

Pour ma vie, je ne peux pas comprendre pourquoi ce qui suit a pour résultat une false pour permettre des écritures. Supposons que ma collection users soit vide pour commencer et que j'écris un document au format suivant à partir de mon interface angulaire:

{
  displayName: 'FooBar',
  email: '[email protected]'
}

Mes règles de sécurité actuelles:

service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userId} {
      function isAdmin() {
        return resource.data.role == 'ADMIN';
      }

      function isEditingRole() {
        return request.resource.data.role != null;
      }

      function isEditingOwnRole() {
        return isOwnDocument() && isEditingRole();
      }

      function isOwnDocument() {
        return request.auth.uid == userId;
      }

      allow read: if isOwnDocument() || isAdmin();
      allow write: if !isEditingOwnRole() && (isOwnDocument() || isAdmin());
    }
  }
}

En général, je ne veux pas que les utilisateurs puissent éditer leur propre rôle. Les utilisateurs normaux peuvent éditer leur propre document autrement, et les administrateurs peuvent éditer n'importe qui.

Remplacer isEditingRole() pour false donne le résultat attendu. Je l'ai donc réduit à cette expression.

L'écriture revient toujours fausse, et je ne peux pas déterminer pourquoi. Toute idée ou solution serait utile!

Modifier 1

Choses que j'ai essayées:

function isEditingRole() {
  return request.resource.data.keys().hasAny(['role']);
}

et

function isEditingRole() {
  return 'role' in request.resource.data;
}

et

function isEditingRole() {
  return 'role' in request.resource.data.keys();
}

Edit 2

Notez que les administrateurs vont éventuellement définir un rôle pour les utilisateurs. Par conséquent, un rôle pourrait éventuellement exister sur un document. Cela signifie que, selon les docs Firestore ci-dessous, la demande aura une clé role, même si elle n'était pas dans la demande d'origine.

Les champs non fournis dans la demande qui existent dans la ressource sont ajoutés à request.resource.data. Les règles peuvent tester si un champ est modifié en comparant request.resource.data.foo à resource.data.foo, sachant que chaque champ de la resource sera également présent dans request.resource même s'il n'a pas été soumis dans la demande d'écriture.

Selon cela, je pense que les trois options de "Edit 1" sont exclues. J'ai essayé la suggestion de request.resource.data.role != resource.data.role et cela ne fonctionne pas non plus ... Je suis perdue et je commence à me demander s'il existe réellement un bug dans Firestore.

10
menehune23

Donc au final, il me semble que je supposais que resource.data.nonExistentField == null renverrait false, alors qu’il renverrait en fait une Error (selon this et mes tests). Donc, ma solution initiale a peut-être été confrontée à cela. C'est étonnant, car l'inverse devrait fonctionner selon les docs , mais peut-être les docs font-ils référence à une valeur "non-existante", plutôt qu'à la clé - une distinction subtile.

Je n'ai toujours pas de clarté à 100%, mais voici ce qui m'a bien aidé:

function isAddingRole() {
  return !('role' in resource.data) && 'role' in request.resource.data;
}

function isChangingRole() {
  return 'role' in resource.data && 'role' in request.resource.data && resource.data.role != request.resource.data.role;
}

function isEditingRole() {
  return isAddingRole() || isChangingRole();
}

Une autre chose qui me surprend encore est que, selon la documentation, je n’aurais pas besoin de la partie && 'role' in request.resource.data dans isChangingRole(), car elle devrait être insérée automatiquement par Firestore. Bien que cela ne semble pas être le cas, sa suppression entraîne l'échec de mon écriture pour des problèmes d'autorisations.

Il pourrait probablement être clarifié/amélioré en divisant l'écriture en parties create, update et delete, au lieu de simplement allow write: if !isEditingOwnRole() && (isOwnDocument() || isAdmin());.

1
menehune23

Je l'ai résolu en utilisant writeFields. S'il vous plaît essayez cette règle.

allow write: if !('role' in request.writeFields);

Dans mon cas, j'utilise list pour restreindre la mise à jour des champs. Cela fonctionne aussi.

allow update: if !(['leader', '_created'] in request.writeFields);
5
gekijin

request.resource.keys.hasAny () est probablement votre meilleur pari ici. Il vous permet de vérifier si une requête donnée a l'une des clés spécifiées. En renonçant à la logique, vous pouvez vous assurer que les clés d’écriture des demandes d’écriture ne figurent pas sur la liste noire. Par exemple:

service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userId} {
      //read rules here...
      allow write: if !request.resource.data.keys().hasAny(["role", "adminOnlyAttribute"]);
    }
  }
}
2
Tom Bailey

Avec cette fonction unique, vous pouvez vérifier si des champs sont/ne sont pas créés/modifiés.

function incomingDataHasFields(fields) {
    return ((
        request.writeFields == null
        && request.resource.data.keys().hasAll(fields)
    ) || (
        request.writeFields != null
        && request.writeFields.hasAll(fields)
  ));
}

Usage:

match /xxx/{xxx} {    
    allow create:
        if incomingDataHasFields(['foo'])              // allow creating a document that contains 'foo' field
           && !incomingDataHasFields(['bar', 'baz']);  // but don't allow 'bar' and 'baz' fields to be created
1
Metu

Depuis que la référence à writeFields dans la documentation a disparu, je devais trouver une nouvelle façon de faire ce que nous pouvions faire avec writeFields.

function isSameProperty(request, resource, key) {
    return request.resource.data[key] == resource.data[key]
}

match /myCollection/{id} {
    // before version !request.writeFields.hasAny(['property1','property2','property3', 'property4']);
  allow update: isSameProperty(request, resource, 'property1')
    && isSameProperty(request, resource, 'property2')
    && isSameProperty(request, resource, 'property3')
    && isSameProperty(request, resource, 'property4')
  }
1
Chonghyuk Won

Pour appliquer les champs en lecture seule du client, utilisez writeFields ( https://firebase.google.com/docs/reference/rules/rules.firestore.Request#writeFields ). Gekijin a suggéré cela, mais la syntaxe a été légèrement erronée.

match /myCollection/{id}
  // Readonly fields
  allow update: if !(request.writeFields.hasAny(['field1', 'field2']));
}

Firestore peuplera les writeFields pour vous, de sorte que la vérification ci-dessus peut toujours être effectuée en toute sécurité dans vos règles update & create.

EDIT 9 oct 2018

@ dls101 a eu la gentillesse d'informer que Google semble avoir supprimé toute mention de writeFields de la documentation. Alors soyez prudent en utilisant cette solution.

0
DarkNeuron