firestore rules with custom functions error - firebase

I'm trying to create a rule to allow certain functions in my firestore db (I worked with firebase since 3 months ago, I'm really new in this). The main idea is to find the _key of the user profile that I have stored in an user document. Then, search all the permissions allowed in the user-profiles document with the id that I found before. If I found the permission, I'd get access to the function
service cloud.firestore {
match /databases/{database}/documents {
function getPermisos(idPermiso){
return get(/databases/{database}/documents/user-profiles/$(idPermiso)).data;
}
function getUserType(){
return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.profile._key ;
}
match /proyectos/{document=**}{
allow list,get: if getPermisos(getUserType()).acessList.proyectos==true;
}
}
}
Is this the right way to make it? Thanks in advance for any help

You don't query rules for accessibility. You can define a collection of accessLevels where each document represents your various access types such as "admins", "groupone", "grouptwo", etc. see documentation
Control Access with Custom Claims and Security Rules
The Firebase Admin SDK supports defining custom attributes on user accounts. This
provides the ability to implement various access control strategies,
including role-based access control, in Firebase apps. These custom
attributes can give users different levels of access (roles), which
are enforced in an application's security rules.
User roles can be defined for the following common cases:
Giving a user administrative privileges to access data and resources.
Defining different groups that a user belongs to.
Providing multi-level access:
Differentiating paid/unpaid subscribers.
Differentiating moderators from regular users.
Teacher/student application, etc.
Add an additional identifier on a user. For example, a Firebase user could map to a different UID in another system.

Related

Firestore schema that supports group write access

I'm trying to design a firestore schema that allows many groups of people to edit only their own group's documents, but anyone service-wide can read them. It doesn't appear possible with firestore's security rules setup.
Role-based access
Firebase supports role-based access control, but it applies service-wide to documents, and I wouldn't be able to support many groups.
Custom roles are created and assigned via the gcloud console, so I could not create new, dynamic custom roles for each group, in order for them to have their own groups.
Firestore triggers to copy documents
I considered using firestore triggers (onCreate, onUpdate, onDelete) to copy documents to other user's subcollections who are in the same group. The issue with doing that is it could create an endless loop of triggers since each member can update a document. You could in theory use a property set on a copied doc that could prevent that, but it feels a little kludgy and brittle.
Is there a best practice, or is this not possible with Firestore?
Firebase Authentication with Custom Claims might be a good fit for this use case. You can add a claim groupId and set it's value to the ID of group that user belongs to. If user can be a part of multiple groups then store an array of groupIds. Your can check if this groupId is included in user's claims in security rules.
You would have to store the groupId in every document so we know which group that doc belongs to. You can try the following rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /collection/{docId} {
allow read, write: if resource.data.groupId == request.auth.token.groupId;
}
}
}
The above rules will allow users to read, write a document only if groupId in document matched groupId in their custom claim.
The custom claims can be changed using Firebase Admin SDK in a secure environment only so you might have to use a Cloud function/server to add users to their respective group.

Firestore, Firebase Authentication, and Firestore Rules with multiple business entities

I am trying to see if Firestore is the right tool for my need.
I am designing a multi-tenanted system using Firestore for scalability and security. Can this be done correctly using just Firestore, Firebase authentication, and Firebase rules to handle all CRUD operations? The idea behind the entities is:
System > Account > Business > Customer
Roles at each entity level would be:
Administrator, Manager, Support, Report, Unauthenticated (the customer)
Accounts have businesses and businesses have customers. Customers would not need to authenticate but read a specific document (designated by the business) and then create their own specific new document. They could create multiple documents but there would be time-based volume thresholds (no DOS!).
I believe Firestore Rules would work great if it didn’t have the Account as a superset that has multiple of the Businesses.
Is it possible to create a system like this with just Firestore, authentication, and rules alone? I want to keep things simple but also not pound a nail using a pair of pliers.
Thanks in advance for the insight!
With a set of well defined Custom Claims you can indeed use Firestore for such a multi-tenant system.
In your security rules, you would combined the different claims, depending on the access role.
In particular you should:
Check the user has the correct claims e.g. auth.token.businessId == "xyz", auth.token.role == "Support"
Check that the user is writing/reading a doc that corresponds to his entity, e.g. allow create: if request.auth.uid == userId && request.resource.data.businessId == auth.token.businessId
Don't forget that Security Rules are not filters, so in each query executed by a user, you need to add the different ids from his/her custom claims (businessId, accountId, etc...). Either by mimicking your entities tree with (sub)collections, or by using some where() clauses in the queries.
Note that since Customers would not need to authenticate, they could potentially read and create documents from/for others businesses, if they have the ID of these businesses (i.e. the DocumentReferences).
You will probably have to manage a set of claims for each user (role, account, business, ... ), which can become complex, so you might be interested by the following article which explains how to create an Admin module for managing Firebase users access and roles.

Firebase API in JavaScript and therefore public, concern for Auth?

I appreciate that variants of this have been posted before, however I am not seeing one that directly hits this concern. The Firebase recommendation is that for Web interfaces the JS includes enough information for the client side JS to identify the application in question. This config can be used for all the activities that are needed. In my case :
createUserWithEmailAndPassword / signInWithEmailAndPassword / sendEmailVerification
Basically this means that anybody can simply start creating users for my application. They can also sign-in, which means any data rules I have that, an example like below, are now open to the world for updates.
service cloud.firestore {
match /databases/{database}/documents {
function isSignedIn() {
return request.auth != null;
}
match /clubs/{club}
{
allow write: if isSignedIn();
allow read: if true;
}
}
Here is what I have done. I have added a Role to the User list, and the User list is not writeable by anybody except my back-end processes which bypass the rules.
Therefore although malicious people could create accounts and sign-in, they could never give themselves the most vital Roles. It still leaves me with 2 issues, firstly it just does not feel quite right :-), secondly how do I know which accounts are valid people trying to sign-up for my application and which are bogus. Those that are valid need their Role updating in the back-end. Right now when I have like 5 users of my app it is ok (haha), but how do people manage this in a more voluminous application?
I appreciate you taking the time to read this and I appreciate any thoughts and / or suggestion you might have.
if you need more granular control around your roles, you can check custom claims
Also, there are a few posts around which tell you a few patterns, take a look:
Controlling Data Access Using Firebase Auth Custom Claims
Patterns for security with Firebase: supercharged custom claims with Firestore and Cloud Functions
there's enough information there to get you started.

How public is "if request.auth.uid != null" as a security rule in Firestore?

I'm using Firestore to store my data. This includes user profile details and their current location in documents, which are stored in a collection with the below security rules:
match /profile/{w9o3948s} {
allow read, write: if request.auth.uid != null;
}
Is there any way for people to "browse" the list of documents in the collection and look through user's locations? Or can that only be done by code within in my app?
The document ID is randomly generated, so even if someone hypothetically knows a current document ID - how would they query the document?
Is there any way for people to "browse" the list of documents in the
collection and look through user's locations?
Yes, with your security rules it is totally possible.
As soon as (1) someone has the apiKey of your Firebase Project and (2) the email/password sign-in method is enabled, this person can use the Firebase Auth REST API and sign-up to your project (i.e. create a new account).
Getting the apiKey is not very difficult if you deploy an app linked to your Firebase project (Android, iOS, Web...).
One standard way to give access to only a set of users (for example, the employees of your company, or some paying subscribers to your app) is to use Custom Claims. You will find in the documentation the guidelines for setting access control with Claims.
You may be interested by this article which presents how to build, with a Callable Cloud Function, a module for allowing end-users with a specific Admin role creating other users and how to restrict access to users with one or more specific Custom Claim(s). (disclaimer, I'm the author).
Or can that (i.e. browse the list of documents in the collection) only
be done by code within in my app?
Anybody who can reverse engineer your app can find the name of your Firestore collections and, with an account created as explained above, can access the documents in those collections.
The document ID is randomly generated, so even if someone
hypothetically knows a current document ID - how would they query the
document?
As you will read in the section of the Security Rules documentation dedicated to Granular Operations, using read allows users to get one document AND to list all documents of a Collection (of a Query). So if you want to restrict the read access rights of a user to only his/her profile, you will need to have two different rules for get and list.
So, in conclusion:
Use Custom Claims: they can only be set from a "privileged server environment" through the Firebase Admin SDK. "Privileged server environment” meaning a server that you fully control or a Cloud Function in your Firebase Project.
Fine tune your Security Rules to (a) use the custom claims in the rules and (b) use granular access rights.

Firestore - security rules for users within companies

Our current Firestore structure is as follows:
Currently we are not using any subcollections
Users have list of companies to which they belong
Every project is connected only with 1 company
Project belongs to a company, when in companyId field is written that company UID
My 1st question is how we can specify security rules defined by this database? Is there some best practice approach?
Our first idea was to do this:
match /databases/{database}/documents/projects/{projectUid}/{document=**} {
allow read: if
(/databases/$(database)/documents/projects/$(projectUid)/companyId) ===
(/databases/$(database)/documents/users/$(request.auth.uid)/companyId)
}
But according to the documentation this would mean that we would have for each read basically 3 reads (2 queries for security and 1 real read from DB). This seems like a waste of queries.
Is there a better approach than this?
We were thinking about changing to subcollections:
at the end we would have in root collections 'companies' and 'users' (to store all users details)
projects would be subcollection of companies
pages would be subcollection of projects
...etc
and companies would contain list of users (not the other way around like now) - but only list, not user details
This way we can use similar approach as from the doc, where each match would contain {companyId} and in allow statement we would use something like
match /databases/{database}/documents/companies/{companyId}/projects/{projectId} {
allow read: if
exists(/databases/$(database)/documents/companies/$(companyId)/users/$(request.auth.uid));
}
Thanks for any recommendations on how to build it in the most scalable and especially most secure way.
Have you considered adding a user's company ID as a custom claim to their profile? That way no additional reads are needed in your security rules.
Since setting these claims requires the Admin SDK, it will require that you can run trusted code somewhere. But if you don't have your own trusted environment yet, you could use Cloud Functions for that e.g. based on some other action like writes to your current Firestore structure.
Adding an answer to Frank.
Borrowing from other API SDKs such as microsoft graph, typically to make a resource request you start by initializing a Client object with an authentication token representing the scope/rights of the user. For example:
const client = new SDKClient(my_auth_token);
The client constructor would have a token validation step on claims. You can then make REST calls such as
const response = await client.someEndpoint({ method: 'POST', body: my_object });
I suggest rather than using the admin SDK for read/write to your firestore, you use the regular firebase nodejs client. To restrict access with security rules, pass a firebase JWT token into this custom SDKClient class with the token that you obtain from the header of your requests. In the constructor, initialize a new firebase 'app'. Because a regular firebase client is
subject to security rules, this will do what you're looking for.
Some example code has already been offered in this answer.
I should add that according to this firebase doc there is a 'warning' to use the admin-sdk server-side, but I'm not sure I see why.
One approach I've thought of for something similar that we are working on, that is, private chatrooms where only certain users have access, is to encrypt all messages with an on-server key, and only grant read access for that key to certain users. That way the extra read only has to occur one time, just when getting the key for the first time, then normal reads with no additional security rules are fine, as an attacker wouldn't be able to do anything with them since they are encrypted and they don't have access to the key.

Resources