Firebase Security Rules - using get in a function - firebase

I am trying to setup security rules for my firestore instance, I have three basic requirements:
User must be authenticated to read
Owners of documents are the only
ones who can write to them (use a field called owner on document to
verify)
Any admin user can also write to any document
The code below achieves all of this (excluded the check for ownership) but the get function to determine the users role only works when specified on the same row as the if condition. In the code below the update and delete works for the admin but not the create.
Can anyone please tell why the isAdmin() function does not evaluate to the same result?
service cloud.firestore {
match /databases/{database}/documents {
// Only 'owners' of data can make changes to data
match /posts/{post} {
allow read: if isAuthenticated();
allow create: if isAuthenticated() && isAdmin();
allow update, delete: if isAuthenticated() && get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == 1;
}
}
/// FUNCTIONS ///
function isAuthenticated() {
return request.auth.uid != null;
}
// function requestIsOwner() {
// return request.resource.data.owner == request.auth.uid;
// }
// function resourceIsOwner() {
// return resource.data.owner == request.auth.uid;
// }
function isAdmin() {
return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == 1
}
}

You need to pass the database variable as argument in your function like this:
service cloud.firestore {
match /databases/{database}/documents {
// Only 'owners' of data can make changes to data
match /posts/{post} {
allow read: if isAuthenticated();
allow create: if isAuthenticated() && isAdmin(database);
allow update, delete: if isAuthenticated() && get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == 1;
}
}
/// FUNCTIONS ///
function isAuthenticated() {
return request.auth.uid != null;
}
// function requestIsOwner() {
// return request.resource.data.owner == request.auth.uid;
// }
// function resourceIsOwner() {
// return resource.data.owner == request.auth.uid;
// }
function isAdmin(database) {
return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == 1
}
}

Related

firebase rule for admin

I wrote this but its not working:
isAdmin(): not working
isLogin(): working well
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write : if false;
}
function isLogin() {
return request.auth != null
}
function isAdmin() {
return isLogin() &&
get(/databases/$(database)/documents/users/$(request.auth.uid)).data.isAdmin == true
}
// allow for only auth & is admin
match /users/{userId} {
allow read, write : if isAdmin();
}
}
Thanks in advance
You need to pass the database variable as an argument in your function like this:
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write : if false;
}
function isLogin() {
return request.auth != null
}
function isAdmin(database) {
return isLogin() &&
get(/databases/$(database)/documents/users/$(request.auth.uid)).data.isAdmin == true
}
// allow for only auth & is admin
match /users/{userId} {
allow read, write : if isAdmin(database);
}
}
}

How do I access the document ID in firestore rules

I am seemingly unable to access the resource.id value when trying queries using these rules. when I manually enter the schools id (the commented out line) the data returns fine. I only have 1 school and the doc ID definitely matches the string. but when I ask to match to the resource.id value, my rules return an 'insufficient permissions' error.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
//functions
function signedIn() {
return request.auth.uid != null;
}
function returnUID(){
return request.auth.uid;
}
function getUserData() {
return get(/databases/$(database)/documents/All%20Users/$(request.auth.uid)).data;
}
match /All%20Users/{userID} {
allow read,write: if
signedIn() && returnUID() == userID;
}
match /All%20Schools/{schoolID}{
allow read, write: if
// signedIn() && getUserData().school == "f7asMxUvTs3uFhE08AJr"
signedIn() && getUserData().school == resource.id
}
}
}
my structure is like this
All Schools / school (document) / Classrooms (subcollection)
All Users / User (document) (each user doc has a classroomID associated to it)
as a point of reference this is a query that is successful
var docRef = db.collection("All Users").doc(uid).get()
and the one that is failing
db.collection("All Schools/" + properties.schoolid + "/Classrooms").onSnapshot()
[update]
the working set of rules!
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
//functions
function signedIn() {
return request.auth.uid != null;
}
function returnUID(){
return request.auth.uid;
}
function getUserData() {
return get(/databases/$(database)/documents/All%20Users/$(request.auth.uid)).data;
}
match /All%20Users/{userID} {
allow read,write: if
signedIn() && returnUID() == userID;
}
match /All%20Schools/{schoolID}{
allow read, write: if schoolID == 'f7asMxUvTs3uFhE08AJr'
}
match /All%20Schools/{schoolID}/Classrooms/{classId} {
allow read, write: if getUserData().school == schoolID;
}
match /All%20Schools/{schoolID}/Student%20List/{student} {
allow read, write: if getUserData().school == schoolID;
}
match /All%20Schools/{schoolID}/Staff/{staff} {
allow read, write: if getUserData().school == schoolID;
}
}
}
The following rules will be effective on documents of 'All Schools' collection only and not documents of 'Classrooms' sub-collection:
match /All%20Schools/{schoolID} {
// ...
}
That's why db.collection("All Users").doc(uid).get() works and fetching 'Classrooms' collection fail since you do not have any rules specified for it. Although you had a recursive wildcard earlier (before editing the question), resource object contains data of those documents being matched in 'Classrooms' sub-collection and hence getUserData().school == resource.id failed too.
That being said, try specifying rules for 'Classrooms' sub-collection as well:
match /All%20Schools/{schoolID}/Classrooms/{classId} {
allow read, write: if getUserData().school == schoolID;
}
match /All%20Schools/{schoolID}/Classrooms/{classID} {
// schoolID is the documentId
allow read, write: if signedIn() && getUserData().school == schoolID
}
If this was my code, I would not use spaces in my collection or field names. Rather I will use snake_case or camelCase.
So instead of All Schools, I will use either all_schools or allSchools.

Firebase rules are not applied as supposed to

I am having weird behaviors with my Firebase Firestore Rules recently.
I am protecting a collection like so:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
function getRoles() {
return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role;
}
function isAdmin() {
return 'admin' in getRoles();
}
match /registrationsRequests/{document} {
allow create: if true;
allow read, update, delete: if false;
}
match /users/{document} {
allow read: if request.auth.uid == document || isAdmin();
allow write: if request.auth.uid == document || isAdmin();
}
}
}
After I log in (and I check if the user is available with its UID), I fetch the user. But I receive the following error (both checks should return true => isAdmin() and document)
FirebaseError: Missing or insufficient permissions.
#edit: The query used
const usrDoc = await this.db.collection('users').doc(user.uid).ref.get()

Dynamic auth.token based on resource.data

I'm trying to write a rule allowing an user to access only the documents he has access to:
match /websites/{website} {
function isSignedIn() {
return request.auth.uid != null;
}
function isAuthorized(rsc) {
return request.auth.token[rsc.data.role] == true
}
function isAdmin() {
return request.auth.token.admin == true
}
allow read, write: if isSignedIn() && (isAuthorized(resource) || isAdmin());
}
Seems like there is a issue with request.auth.token[rsc.data.role] but I can't figure out what's the problem. rsc.data.role is set as a string in the database, and my user as a token as a boolean.
Example: website.role: 'editor' and request.auth.token.editor: true.
Here's a screenshot of the document I'm trying to access:
Any idea?

Error: Missing or insufficient permissions

I got a firestore like this:
:stores
|
$Store
:orders
|
$Order
:items
I want to read orders from my database using a user having an workerUid same as the request.auth.uid but geht the Error: Missing or insufficient permissions.
The important part of my firebase rules:
service cloud.firestore {
match /databases/{database}/documents {
//Matches any document in the stores collection
match /stores/{store} {
function isStoreAdmin(uid) {
return get(/databases/stores/$(store)).data.adminUid == uid;
}
function isStoreWorker(uid) {
return get(/databases/stores/$(store)).data.workerUid == uid;
}
allow read: if request.auth.uid != null;
allow write: if request.auth.uid == resource.data.adminUid;
//Matches any document in the orders collection
match /orders/{document=**} {
allow read, write: if isStoreAdmin(request.auth.uid) || isStoreWorker(request.auth.uid);
}
}
}
}
Funny thing is, that it works if I do this:
match /orders/{document=**} {
allow read, write: if isStoreWorker(request.auth.uid);
}
or this:
match /orders/{document=**} {
allow read, write: if request.aut.uid != null;
}
When deploying the rules I get no syntax error so I really can't understand why this is not working. Does anyone have any ideas? Thank you so much!
Edit:
function readAllDocuments(collectionReference, callback,finishedCallback){
collectionReference.get().then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
callback(doc.id,doc.data());
});
finishedCallback();
});
}
const storeDocument = getRootCollection(STORES_COLLECTION_ID).doc(storeId);
const orderCollection = storeDocument.collection(STOREORDERS_COLLECTION_ID);
orders=new Map();
readAllDocuments(orderCollection, function (id, data) {
orders.set(id,data);
},function(){
finishedLoading();
});
The documentation for use of get() in a security rule states:
...the path provided must begin with /databases/$(database)/documents
Make these changes to the get() paths:
function isStoreAdmin(uid) {
return get(/databases/$(database)/documents/stores/$(store)).data.adminUid == uid;
}
function isStoreWorker(uid) {
return get(/databases/$(database)/documents/stores/$(store)).data.workerUid == uid;
}

Resources