Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Firestore And Storage Security Rules for Flamelink projects. #86

Open
ribalnasr opened this issue Jul 16, 2019 · 20 comments
Open

Firestore And Storage Security Rules for Flamelink projects. #86

ribalnasr opened this issue Jul 16, 2019 · 20 comments

Comments

@ribalnasr
Copy link

ribalnasr commented Jul 16, 2019

Hi,

I've been working on a set of rules for Flamelink that i'd love to share with you.
Forgive me if it's not the right place for it, and feel free to move it to where it belongs.

EDITED: Find firestore updated rules below

@jperasmus
Copy link
Collaborator

Hi @ribalnasr

Thanks so much for doing this. I'm sure other users will also find this useful. I'll see where we can add this to our documentation.

@Danelund
Copy link

Danelund commented Oct 31, 2019

@ribalnasr - thanks for this awesome work. Would love to implement it in my solution. Currently however - if I implement these security rules I am unable to edit from flamelink CMS, as no permissions are set from there. How would I implement to have it work there as well?

@jperasmus
Copy link
Collaborator

Amazing work, guys. I've included this in one of the Flamelink articles for other developers to reference: https://intercom.help/flamelink/en/articles/3068550-flamelink-and-cloud-firestore

@ribalnasr
Copy link
Author

ribalnasr commented Nov 19, 2019

lovely update @Danelund , this is is exactly what i was trying to achieve without using custom claims but i couldn't at the time...

i remember your updated code was here in this thread before but its not now..
@jperasmus i made some updates after testing it in multiple scenarios and found some bugs, mainly typos.

please update the docs as the current ones do not allow creating new schemas or updating existing entries from the flamelink cms.

PS: It would be perfect if the guest profile is automatically created upon creating the project in flamelink cms.

here's the updated code:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
  

    function guestPermissions() {
      return get(/databases/$(database)/documents/fl_permissions/guest).data
    }  

    function userPermissions(){
      return get(get(/databases/$(database)/documents/fl_users/$(request.auth.uid)).data.permissions).data
    }

    function isContentPermitted(schema, action) {
      return guestPermissions().content.production[schema][action] == true || (request.auth != null && (userPermissions().content.production[schema][action] == true || userPermissions().id == '1'))
    }

    function isCollectionPermitted(collection, action) {
      return guestPermissions()[collection][action] == true || (request.auth != null && userPermissions()[collection][action] == true)
    }

    function isCollectionPermittedProduction(collection, action) {
      return guestPermissions()[collection].production[action] == true || (request.auth != null && userPermissions()[collection].production[action] == true)
    }

    function isSettingsPermitted(settings, action) {
      return guestPermissions().settings[settings][action] == true || (request.auth != null && userPermissions().settings[settings][action] == true)
    }
          
    match /fl_content/{document=**} {
      allow read: if isContentPermitted(resource.data._fl_meta_.schema, 'view') ;
      allow update: if isContentPermitted(request.resource.data._fl_meta_.schema, 'update');
      allow create: if isContentPermitted(request.resource.data._fl_meta_.schema, 'create');
      allow delete: if isContentPermitted(resource.data._fl_meta_.schema, 'delete');
    }
          
    match /fl_environments/{document} {
      allow read: if isSettingsPermitted('environments', 'view');
      allow update: if isSettingsPermitted('environments', 'update');
      allow create: if isSettingsPermitted('environments', 'create');
      allow delete: if isSettingsPermitted('environments', 'delete');
    }
          
    match /fl_files/{document=**} {
      allow read: if isCollectionPermitted('media', 'view');
      allow update: if isCollectionPermitted('media', 'update');
      allow create: if isCollectionPermitted('media', 'create');
      allow delete: if isCollectionPermitted('media', 'delete');
    }    

    match /fl_folders/{document=**} {
      allow read: if isCollectionPermitted('media', 'view');
      allow update: if isCollectionPermitted('media', 'update');
      allow create: if isCollectionPermitted('media', 'create');
      allow delete: if isCollectionPermitted('media', 'delete');
    }    

    match /fl_locales/{document=**} {
      allow read: if isSettingsPermitted('locales', 'view');
      allow update: if isSettingsPermitted('locales', 'update');
      allow create: if isSettingsPermitted('locales', 'create');
      allow delete: if isSettingsPermitted('locales', 'delete');
    }   
        
    match /fl_navigation/{document=**} {
      allow read: if isCollectionPermittedProduction('navigation', 'view');
      allow update: if isCollectionPermittedProduction('navigation', 'update');
      allow create: if isCollectionPermittedProduction('navigation', 'create');
      allow delete: if isCollectionPermittedProduction('navigation', 'delete');
    }
          
    match /fl_permissions/{document=**} {
      allow read: if isCollectionPermitted('permissions', 'view');
      allow update: if isCollectionPermitted('permissions', 'update');
      allow create: if isCollectionPermitted('permissions', 'create');
      allow delete: if isCollectionPermitted('permissions', 'delete');
    }
          
    match /fl_schemas/{document=**} {
      allow read: if isCollectionPermittedProduction('schemas', 'view');
      allow update: if isCollectionPermittedProduction('schemas', 'update');
      allow create: if isCollectionPermittedProduction('schemas', 'create');
      allow delete: if isCollectionPermittedProduction('schemas', 'delete');
    }    
          
    match /fl_settings/{document=**} {
      allow read: if isSettingsPermitted('general', 'view');
      allow update: if isSettingsPermitted('general', 'update');
      allow create: if isSettingsPermitted('general', 'create');
      allow delete: if isSettingsPermitted('general', 'delete');
    }
          
    match /fl_users/{document=**} {
      allow read: if request.auth.uid == resource.id || isCollectionPermitted('users', 'view');
      allow update: if request.auth.uid == resource.id || isCollectionPermitted('users', 'update');
      allow create: if isCollectionPermitted('users', 'create');
      allow delete: if isCollectionPermitted('users', 'delete');
    }   
          
    match /fl_backups/{document=**} {
      allow read: if isSettingsPermitted('backups', 'view');
      allow update: if isSettingsPermitted('backups', 'update');
      allow create: if isSettingsPermitted('backups', 'create');
      allow delete: if isSettingsPermitted('backups', 'delete');
    }
          
    match /fl_workflows/{document} {
      allow read: if isSettingsPermitted('workflows', 'view');
      allow update: if isSettingsPermitted('workflows', 'update');
      allow create: if isSettingsPermitted('workflows', 'create');
      allow delete: if isSettingsPermitted('workflows', 'delete');
    }
          
    match /fl_webhooks/{document} {
      allow read: if isCollectionPermitted('webhooks', 'view');
      allow update: if isCollectionPermitted('webhooks', 'update');
      allow create: if isCollectionPermitted('webhooks', 'create');
      allow delete: if isCollectionPermitted('webhooks', 'delete');
    }

    match /fl_webhooks/{document}/activityLog/{log} {
      allow read: if isCollectionPermitted('webhooks', 'view');
      allow update: if isCollectionPermitted('webhooks', 'update');
      allow create: if isCollectionPermitted('webhooks', 'create');
      allow delete: if isCollectionPermitted('webhooks', 'delete');
    }
    

  }
}

@gitdubz
Copy link
Contributor

gitdubz commented Nov 19, 2019

Thanks a million! I have updated the article with the latest code updates.

@Danelund
Copy link

@ribalnasr - excellent idea!
@gitdubz I thoroughly second the idea of having a default guest account created by flamelink (i.e. with id 0), which would mean that these rules could be copied in by anyone and work straight away.

Next step in improving the rules would be to evaluate [env], as currently they only set permissions for the production environment.

@ribalnasr
Copy link
Author

ribalnasr commented Dec 3, 2019

hello people! :)
@gitdubz, small bug fix that should be updated in the docs,

under
match /fl_content/{document=**} {
allow read: if isContentPermitted(resource.data._fl_meta_.schema, 'view') ;
allow update: if isContentPermitted(request.resource.data._fl_meta_.schema, 'update');
allow create: if isContentPermitted(request.resource.data._fl_meta_.schema, 'create');
allow delete: if isContentPermitted(resource.data._fl_meta_.schema, 'delete');
}

update the delete action to be resource instead of request.resource.
this was preventing deleting a doc by anyone except the sudo user.

@Danelund thank you and i agree about environment, i personally can't work on it soon so please feel free to update it

peace.

@gitdubz
Copy link
Contributor

gitdubz commented Dec 3, 2019

Thank you so much for continuously updating this!
I will update the docs on our end to reflect the changes.

As for the guest user suggestion, I will table it, but I think because we are limited to the number of users on the payment plans this might be possible right now.

@ribalnasr
Copy link
Author

a guest and a sudo profile within the free plan.. just a thought :)

@gitdubz
Copy link
Contributor

gitdubz commented Dec 3, 2019

It would not be too difficult to add a guest permission group with only read/view permissions.
It will then just keep in sync the same way Super Admin (permission id 1) does but only for those privileges.

@rileynetadmin
Copy link

What is the best way to secure content to the user who created the content?

@YariKoen
Copy link

YariKoen commented Mar 18, 2021

Hi, I am trying to login to my Flamelink, after I updated Firestore rules for my app. I added your rules and in test (Rules Playground), everything is OK / passed. But when I try to login to Flamelink Dashboard, access is denied. Honestly, I don't understand why - I removed the global rule for "deny all", bcs in default (without a match) is all denied. It seems that in these rules is something missing. If I use "allow all" global rule, the login works fine.

I also tried to just allow read & write for all fl_ collections if user is authenticated - without success. Am I missing something about Flamelink? 😅 It's very frustrating.

@ribalnasr
Copy link
Author

hey @YariKoen,

in the firebase console, inside firestorm, find your user under fl_users, does it have permissions = fl_permissions/1 ?

@YariKoen
Copy link

YariKoen commented Mar 24, 2021

@ribalnasr yea, there are two doc's in fl_users - first have field permissions = reference on fl_permissions/1, second (same field) have string 1. Not sure why there are two with the same email.

@ribalnasr
Copy link
Author

the problem seems to be there, change the one with a string value to a reference on 'fl_permissions/1', and check the user uid under authentication then compare it to the ids in the fl_users collection. delete the duplicated user with the wrong id.

@YariKoen
Copy link

@ribalnasr It works, thanks! 🎉

@gitdubz
Copy link
Contributor

gitdubz commented May 5, 2021

Hi @ribalnasr,

We have a user who has implemented the rules and mentioned that they are unable to log in, so I tried to replicate the issue and found that the emulator does give an error.

the user permissions is a reference

{ 
  ... 
  permissions: 'fl_permissions/LXmaoasdp1' // reference
}

The function that is giving an error 👇

function userPermissions(){
  return get(get(/databases/$(database)/documents/fl_users/$(request.auth.uid)).data.permissions).data
}

Screenshot 2021-05-05 at 09 45 10

I have tried to see if the following works

function getPermissionValue(r) {
  // return get(rel).data // 1
  // return get(rel.path).data // 2
  // return get(get(/databases/$(database)/documents/fl_permissions/$(r.id)).data // 3
  // return get(get(/databases/$(database)/documents/$(r.path)).data // 4
  return get(/databases/$(database)/documents/fl_permissions/LXmaoasdp1).data // sanity check - works.
}

function userPermissions(){
  return getPermissionValue(get(/databases/$(database)/documents/fl_users/$(request.auth.uid)).data.permissions))
}

From what I can tell the rules does not allow for a reference value.
Have you faced a similar issue at some point?

@ribalnasr
Copy link
Author

hi @gitdubz, sorry for the late reply..
not really, been using these rules for a while now without updating them and haven't experienced any issue.

the error here seems to be that the result of the first get() (the inner one) does not return a valid document on the permissions field, hence the parent get() function is unable to work.

are we sure the user.permissions reference refers to an existing document? i have noticed sometimes that the default permissions field for the sudo user when starting a new flamelink project is [object Object] for some reason instead of a path to document reference and i had to change this manually. could that be the case?

@gitdubz
Copy link
Contributor

gitdubz commented May 17, 2021

Thanks @ribalnasr,

The document in question is a reference, I even created on manually to make sure.
Thanks for the reply, I have reached out to the Firebase team for more assistance.

@ribalnasr
Copy link
Author

i might be able to assist you further if it's possible to have access to the firebase project. let me know if i can help.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants