简体   繁体   中英

Allow user to update his own document (but not the roles) using Firestore security rules

My app stores the user's role (subscriber, editor or admin) as a "roles" map within their user doc in the users collection of Firestore. The role map looks like this:

// Roles map for a subscriber
{
subscriber: true 
}

// Roles map for a editor
{
editor: true 
}

// Roles map for administrator
{
administrator: true 
}

I can also put multiple (or brand new) role fields within the user doc's role map as needed.

I want users to be able to update their profile without being able to change the role map. So for security rules I have tried ensuring this by checking that the user is the owner, and that the before and after roles are all the same. But I always get a permission error when trying to update. Here are the rules:

match /users/{userUid} {
  allow update: if 
  (
    isOwner(userUid) &&
    (
      (request.resource.data.roles.administrator == resource.data.roles.administrator) &&
      (request.resource.data.roles.editor == resource.data.roles.editor) &&
      (request.resource.data.roles.subscriber == resource.data.roles.subscriber)
    )
  );
}

The first isOwner(user) condition looks like this:

function isOwner(uid) {
    return (isSignedIn() && (request.auth.uid == uid));
}

I am confident this part is working because when I run it with only that, it works.

I suspect my issue may be that in cases where one of the roles fields (eg subscriber) doesn't exist before or after the write, it fails the equality check. So I also tried adding a condition to allow if the existing object doesn't have that field, but it still doesn't work:

match /users/{userUid} {
  allow update: if 
  (
    isOwner(userUid) &&
    (
      (!request.resource.data.roles.administrator || request.resource.data.roles.administrator == resource.data.roles.administrator) &&
      (!request.resource.data.roles.editor || request.resource.data.roles.editor == resource.data.roles.editor) &&
      (!request.resource.data.roles.subscriber || request.resource.data.roles.subscriber == resource.data.roles.subscriber)
    )
  );
}

Thanks in advance for any help.


UPDATE 1

I found a simpler solution using writeFields which works, but writeFields is deprecated so this is not a long-term solution:

allow update: if isOwner(userUid) && !('roles' in request.writeFields)

Again, writeFields is deprecated so should not be used.


UPDATE 2

This solution ended up working for me, replacing role with roles to match my case: https://stackoverflow.com/a/48214390/4407512

You can now use map diffs to solve this without using the deprecated 'writeFields'

allow update: if isOwner(userUid) && !('roles' in request.resource.data.diff(resource.data).affectedKeys());

request.resource.data is a Map type object. As you said, you can ensure that the properties always exist, or you can check to see if the property exists in the Map before using them. The in operation on Map objects will let you know if a property exists. See the documentation on Map for more details.

"administrator" in request.resource.data   // true if administrator property exists

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM