Artem Arkhipov
Artem Arkhipov

Reputation: 7455

Declaring a function in Firestore rules

Here is a problem I am facing now with the Firestore security rules.

First of all here is an example of data structure I have in my firestore database:

userProfiles/userId/userData

companies/companyId/companyData

Looks pretty simple. Each userData includes an array named companies which includes all companyIds which this user has access to.

Now I need to write rules to allow read companyData only if companyId is in particular user info companies list.

Here are the rules which work for me:

service cloud.firestore {
  match /databases/{database}/documents {
    match /companies/{companyId} {
      allow read: if companyId in get(/databases/$(database)/documents/userProfiles/$(request.auth.uid)).data.companies
    }
  }
}

Taking the fact that I am going to have much more rules, I would like to make them more readable and comfortable to reuse. According to this official guide I can create custom functions and according to this article they can be common and declared outside of the main rules block.

I refactored my rules to look like this and it also worked for me:

service cloud.firestore {
  match /databases/{database}/documents {
    match /companies/{companyId} {
      allow read: if companyId in getUserCompanies()
    }
    function getUserCompanies() {
        return get(/databases/$(database)/documents/userProfiles/$(request.auth.uid)).data.companies
    } 
  }
}

But now I would like to move function outside of the rules block to make it even more clear:

service cloud.firestore {
  match /databases/{database}/documents {
    match /companies/{companyId} {
      allow read: if companyId in getUserCompanies()
    } 
  }
}

function getUserCompanies() {
    return get(/databases/$(database)/documents/userProfiles/$(request.auth.uid)).data.companies
}

And that doesn't work. There are no any errors, I just receive the regular Read denied message from the simulator.

So the questions are: is it possible to move function outside as I did it in my example? Are there any obvious mistakes I've done here? is there better way to make my rules set even more clear?

P.S. I also tried to pass some parameters to that function, including user and company ids - no luck.

Upvotes: 7

Views: 8453

Answers (2)

Frank van Puffelen
Frank van Puffelen

Reputation: 598765

Functions can be defined on any level in your rules file. But they only have access to variables that are defined in the scope where you define them. Anything else you have to pass in as a variable.

So this (useless) function works when defined globally:

function isTrue() {
  return true;
}

But this one won't, because it doesn't have access to request:

function isAdmin() {
  return (request.auth.token.email_verified && 
    request.auth.token.email.matches(".*@google.com"));
}

What I sometimes do is add a parameter to the function definition:

function isAdmin(request) {
  return (request.auth.token.email_verified && 
    request.auth.token.email.matches(".*@google.com"));
}

and then pass the variable in to the call:

allow update: if isAdmin(request) || 
  (request.auth.uid == uid && isUnmodified(request, resource, 'name'));

Upvotes: 18

andresmijares
andresmijares

Reputation: 3744

It’s not possible, rules only work on the scope they are defined, they mentioned very subtly here

Upvotes: 2

Related Questions