Firestore rules - Acces document id in resource - firebase

I'm currently migrating a Realtime Database into Firestore and came across some questions. My rules are set as below:
service cloud.firestore {
match /databases/{database}/documents {
match /users/ {
allow read
match /{$user} {
allow create: if isAuthenticated(request) && exists(/databases/$(database)/documents/users/$(request.auth.uid)) === false
allow update: if isAuthenticated(request) && request.auth.id === resource.data;
match /notifications/{$notification} {
allow write: if request.auth.id === resource.data
}
}
}
}
}
function isAuthenticated(req) {
return request.auth.uid != null;
}
I want to secure updating for users only if $user is the same as the auth ID (request.auth.id). The same goes for notifications. Is it possible to access $user inside the notifications rule?

The rules are scoped, so you can access variables from a higher level in a nested scope.
In your case the allow write rule has access to $user, and $notification.

Related

Firestore: Prevent write/overwrite/update field once it is added

I have a collection in which I am storing user requests in documents having documents ID as user's email. In the document, I am creating fields the key for which is being generated at client side.
Now, the problem that I am facing is that user can overwrite the existing field/request in the document if the key matches which I don't want to happen.
What I tried was to use this rule which unfortunately does not work
resource.data.keys().hasAny(request.resource.data.key();
So how can I achieve this?
Below are the screen shot of the firestore data and the current security rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /roles/{userId}{
allow read: if isSignedIn() && hasId(userId);
}
match /requests/{email} {
allow read, update: if isSignedIn() && hasMail(email)
}
//functions//
function hasMail (email) {
return request.auth.token.email == email;
}
function hasId (userId) {
return request.auth.uid == userId;
}
function isSignedIn () {
return request.auth != null;
}
function getUserRole () {
return get(/databases/$(database)/documents/roles/$(request.auth.uid)).data.role
}
}
}
You can check if a resource already exists. Here an example:
allow write: if resource == null // Can create, not update
Use that to restrict any edit or update of the data. If you have additional rules you can granulate them to update, delete and create.

Firestore document RBAC- FirebaseError: Missing or insufficient permissions

I'm trying to restrict access to a collection based on a users "role" on the document as well as whether they're signed in.
Just checking if they are signed in works:
rules_version = '2';
function isSignedIn() {
return request.auth != null;
}
service cloud.firestore {
match /databases/{database}/documents {
match /workspaces/{workspace} {
allow read: if isSignedIn();
}
}
}
But I want to make it a bit more granular, I'm following this Google guide. When I configure it exactly the same my frontend client errors with FirebaseError: Missing or insufficient permissions. However, testing the rule in the Firebase portal works(?).
rules_version = '2';
function isSignedIn() {
return request.auth != null;
}
function getRole(rsc) {
return rsc.data.access[request.auth.uid];
}
function isOneOfRoles(rsc, array) {
return isSignedIn() && (getRole(rsc) in array);
}
service cloud.firestore {
match /databases/{database}/documents {
match /workspaces/{workspace} {
allow read: if isOneOfRoles(resource, ["owner", "viewer"]);
}
}
}
As mentioned, testing the rule via Firebase works, however when querying from my frontend app it fails.
// VueJS
let workspace = []
async fetchWorkspaces() {
const db = firebase.firestore();
await db.collection('workspaces').get().then(response => {
response.forEach(snapshot => {
this.workspaces.push(snapshot.data());
})
})
}
Database document
I've also tried storing the RBAC for each user as a document in a subcollection and using the following rule
allow read: if isSignedIn() && exists(/databases/$(database)/documents/workspaces/$(workspace)/users/$(request.auth.uid));
Still doesn't work. I can only seem to grant broad access (is signed in)

How to update Firestore Rules with custom Roles

How to configure RULES to control Reads/Writes and Deletes on a collection (RECORDS) based on custom user roles defined in another collection (USERS)?
---- User Collection ----
USERS: {
<RandomID1> : { uid: 234, email: "abc#xyz.com", role: "ENDUSER" },
<RandomID2> : { uid: 100, email: "def#xyz.com", role: "ADMIN" }
}
---- Records Collection ----
RECORDS: {
<RandomID1> : { uid: "234", name: "Record 123" },
<RandomID2> : { uid: "234", name: "Record 456" },
<RandomID3> : { uid: "999", name: "Record 999" } /* another user's record */
}
---- Current Rules ----
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth != null;
}
}
}
Assuming the user is logged in the web application and using client-side Firebase SDK, how to achieve the below cases?
IFF: USERS.RandomID1.role = 'ENDUSER'
How to restrict the user to READ only their records from RECORDS,
but not somebody else's?
How to restrict UPDATEs to only their
records in RECORDS?
How to restrict DELETEs on all of their records in RECORDS?
How to restrict all CRUD operations on the rest of collections(/COLLECTION**), except RECORDS?
IFF: USERS.RandomID1.role = 'ADMIN'
How to enable this (Admin) user to perform all CRUD operations in
RECORDS?
So, how to rewrite or update rules to control these operations? If not, are there better designs or alternatives?
Note: We need to handle these cases to block some users/hackers who may try to open browser console/inspect window, and execute firestore queries with or without any conditions.
I appreciate your help!
For 5 you will need to define custom claims for your admins, below I assume a field isadmin is set to true for admins.
The following rules should be a good start:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// 4: Will restrict all others access
match /records/{recordid} {
// 1+5(read): restrict reads
allow read: if request.auth != null
&& (resource.data.uid == request.auth.uid || request.auth.token.isadmin);
// 2+3+5(write): +create as a bonus
allow write: if request.auth != null
&& (request.resource.data.uid == request.auth.uid || request.auth.token.isadmin);
}
}
}
Here is my final answer:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
//Rule on a Collection Groups, instead of simple Collection
match /{NestedSubCollections=**}/<COMMON_COLLECTION-NAME>/{doc} {
allow create: if isSignedIn() && isAdmin();
allow read: if isSignedIn() && (isAdmin() || isThisUserRecord(resource.data.<CUST_USER-ID-FIELD>));
allow update: if isSignedIn() && (isAdmin() || isThisUserRecord(resource.data.<CUST_USER-ID-FIELD>));
allow delete: if isSignedIn() && isAdmin();
}
//Collection UserDB, to validate profile updates
match /UserDB/{userId=**} {
allow create: if isSignedIn() && isAdmin();
allow read: if isSignedIn() && (isAdmin() || hasUserProfile(resource.data.<CUST_USER-ID-FIELD>));
allow update: if isSignedIn() && (isAdmin() || hasUserProfile(resource.data.<CUST_USER-ID-FIELD>));
allow delete: if isSignedIn() && isAdmin();
}
//Checking if user is signed-in
function isSignedIn() {
return request.auth != null;
}
//Checking user's admin status, without the context of accessing collection, but directly
function isAdmin() {
return get(/databases/$(database)/documents/UserDB/$(request.auth.uid)).data.<CUST_ROLE-FIELD> == "<ADMIN-ROLE-NAME>";
}
//Check whether a current record belong to logged-in user; by <CUST_USER-ID-FIELD>, not oAuth-ID
function isThisUserRecord(custUserId) {
return get(/databases/$(database)/documents/UserDB/$(request.auth.uid)).data.<CUST_USER-ID-FIELD> == custUserId;
}
//Check whether a logged-in user has existing profile or not
function hasUserProfile(userOauthId) {
return userOauthId == request.auth.uid;
}
}
}
It is working perfectly fine in my production application and everything is secured without writing a single line of code in the application!
How to test it?
Updated a user record in UserDB with <CUST_ROLE_FIELD>, and <ADMIN_ROLE_NAME> as value
Then go to your frontend web application, go to browser Console, and trying to hit Firestore queries and validate the records and results
If it's a non-admin user, then he can only Read/Update as per our rules above, and the other queries will fail as expected.
Note, the create/delete is performed in the backend, via cloud function, for security reasons.
In sum, admin can perform any action on all records, but non-admin users can only touch his own records with limited Read/Update operations only.
You may have to tweak a bit based on other use cases. Let me know if anything.

Firebase - Cloud Firestore, Problems creating rule for admin

Im having a problem with creating firebase rules, I created a field "role" in my 'employees' collection, so there, I want to distinguish each type of users allowed to read/write, etc in the DB. My problem is that for some reason I cant verify if the user trying to read others information is really the admin, in the "isAdministrator" function I cant make the get's to work:
service cloud.firestore {
// Function: Check if an authenticated employee is admin
function isAdministrator(){
//return get(/databases/$(database)/documents/employees/$(request.auth.uid)).data.role == 'admin'
// || get(/databases/$(database)/documents/employees/$(request.auth.uid)).role == 'admin';
return false;
// (data.child('role').val() == 'admin')
}
// Function: Check if an employee is authenticated
// RULES
match /databases/{database}/documents {
// Allows only admins and 'owner' to acess his document
match /employees/{document=**} {
allow read, write, update: if request.auth.uid == resource.data.uid || isAdministrator();
allow create, delete, list: if isAdministrator();
}
}
}
This is how my collection looks like:
match /databases/{database}/documents must wrap everything, including the function, like this:
service cloud.firestore {
match /databases/{database}/documents {
// FUNCTIONS
// Function: Check if an authenticated employee is admin
function isAdministrator(){
return get(/databases/$(database)/documents/employees/$(request.auth.uid)).data.role == 'admin'
|| get(/databases/$(database)/documents/employees/$(request.auth.uid)).role == 'admin';
}
// RULES
// Allows only admins and 'owner' to acess his document
match /employees/{document=**} {
allow read, write, update: if request.auth.uid == resource.data.uid || isAdministrator();
allow create, delete, list: if isAdministrator();
}
}
}

Firestore rules objects

I am trying to setup some basic Firestore security rules in my database. I am having some trouble finding the relevant documentation to learn how to do this.
Currently my document is structured like this:
project(document): {
createdBy(string): chris#emailaddress.com,
users(object): {
{ graham#emailaddress.com(object): access: write },
{ paul#emailaddress.com(object): access: read }
}
}
I'd like to setup my rules so that:
Users must be signed in to read, write or delete anything
If a user is added to a project with 'read' access they can only read the document.
If a user is setup with write access they can update and read the document but not update the createdBy field.
If a user has created the document they can read, update and delete the document.
My security rules are setup like this:
service cloud.firestore {
match /databases/{database}/documents {
match /projects/{projectId} {
allow read: if existingData().users[getUser()token.email].access != null && isSignedInAndVerified()
allow read, update: if existingData().users[getUser()token.email].access != "write" && isSignedInAndVerified()
allow update, delete: if sameAsEmail(existingData().createdBy) && isSignedInAndVerified()
}
//my functions
function getUser(){
return request.auth
}
function existingData(){
return resource.data
}
function sameAsEmail(resource){
return resource == request.auth.token.email
}
function isSignedInAndVerified() {
return request.auth != null && request.auth.token.email_verified;
}
}
}
Incorrect use of syntax use: getUser().token.email instead.

Resources