Firestore Security Rules - How can I check that a field is/isn't being modified?

前端 未结 9 602
孤城傲影
孤城傲影 2020-11-29 07:24

For the life of me, I cannot understand why the following is resulting in a false for allowing writes. Assume my users collection is empty to start

相关标签:
9条回答
  • 2020-11-29 07:57

    I solved it by using writeFields. Please try this rule.

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

    In my case, I use list to restrict updating fields. It works, too.

    allow update: if !(['leader', '_created'] in request.writeFields);
    
    0 讨论(0)
  • 2020-11-29 07:58

    With this single function you can check if a fields are/aren't being created/modified.

    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
    
    0 讨论(0)
  • 2020-11-29 07:59

    Since reference to writeFields in documentation has disappeared, I had to come up new way to do what we could do with 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')
      }
    
    0 讨论(0)
  • 2020-11-29 08:09

    This might seem like over kill but for updating documents where you might have other fields that are not user generated, eg. roles, created etc. you need functions that can test that those fields don't change. Hence meet these three FNs.

    function hasOnlyFields(fields) {
      if request.resource.data.keys().hasOnly(fields) 
    }
    function hasNotChanged(fields) {
      return (fields.size() < 1 || equals(fields[0]))
        && (fields.size() < 2 || equals(fields[1]))
        && (fields.size() < 3 || equals(fields[2]))
        && (fields.size() < 4 || equals(fields[3]))
        && (fields.size() < 5 || equals(fields[4]))
        && (fields.size() < 6 || equals(fields[5]))
        && (fields.size() < 7 || equals(fields[6]))
        && (fields.size() < 8 || equals(fields[7]))
        && (fields.size() < 9 || equals(fields[8]))
    }
    function equals(field) {
      return field in request.resource.data && field in resource.data && request.resource.data[field] == request.resource.data[field]
    }
    

    So on update of say user document, where the user may only update their name, age, and address, but not roles and email you can do:

    allow update: if hasOnlyFields(['name', 'age', 'address']) && hasNotChanged(['email', 'roles'])
    

    Note the hasNotChanged can check up to 9 fields. Also these aren't the only checks you would want to do. You'd need to check the the types and ownership of the document as well.

    0 讨论(0)
  • 2020-11-29 08:16

    So in the end, it seems I was assuming that resource.data.nonExistentField == null would return false, when it actually returns an Error (according to this and my tests). So my original solution may have been running into that. This is puzzling because the opposite should work according to the docs, but maybe the docs are referring to a value being "non-existent", rather than the key -- a subtle distinction.

    I still don't have 100% clarity, but this is what I ended up with that worked:

    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();
    }
    

    Another thing that still puzzles me is that, according to the docs, I shouldn't need the && 'role' in request.resource.data part in isChangingRole(), because it should be inserted automatically by Firestore. Though this didn't seem to be the case, as removing it causes my write to fail for permissions issues.

    It could likely be clarified/improved by breaking the write out into the create, update, and delete parts, instead of just allow write: if !isEditingOwnRole() && (isOwnDocument() || isAdmin());.

    0 讨论(0)
  • 2020-11-29 08:16

    Found this rule to work quite well:

    function propChanged(key) {
      // Prop changed if key in req but not res, or if key req and res have same value
      return (
        (key in request.resource.data) && !(key in resource.data)
      ) || (
        (key in request.resource.data) && request.resource.data[key] != resource.data[key]
      );
    }
    
    0 讨论(0)
提交回复
热议问题