Error: Missing or insufficient permissions - firebase

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;
}

Related

How to perform a query with where against a public collection in Firestore with Security Rules?

I have a ruleset in Firestore Security Rules defined in this way:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
function isUserAllowed(root, metric) {
return metric != 'private-data' || (metric == 'private-data' && request.auth.token.sub in get(/databases/$(database)/documents/$(root)/users).data.users);
}
match /{root}/{doc} {
allow read: if root != 'ispd' && doc != 'users';
allow write: if false;
match /{metric}/{docs=**} {
allow read: if isUserAllowed(root, metric);
allow write: if false;
}
}
}
}
I'm trying to execute this method from my React application
const checkForbiddenEmailDomain = (emailDomain) => {
const collectionRef = collection(db, 'forbidden-domains');
const q = query(collectionRef, where('domain', '==', emailDomain));
return getDocs(q)
.then(({ docs }) => {
if (docs.length > 0) {
const errorToThrown = { message: `The ${emailDomain} domain is forbidden` };
throw errorToThrown;
}
});
};
Since I'm accessing forbidden-domains which is public I shouldn't have problems, however I always get Missing or insufficient permission. I know that there is the section in Firestore documentation where they say something like "Rules are not queries, blablabla", probably the Egyptian hieroglyphs are simpler to understand 😃.
With no jokes, can someone help me? I really don't understand what should I do.
Thanks a lot!
I finally ended up modifying mu ruleset in this way, and it works:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
function isUserAllowed(root, metric) {
return metric != 'private-data' || (metric == 'private-data' && request.auth.token.sub in get(/databases/$(database)/documents/$(root)/users).data.users);
}
match /forbidden-domains/{docs=**} {
allow read;
allow write: if false;
}
match /{root}/{doc} {
allow read: if root != 'instagram-service-private-data' && doc != 'users';
allow write: if false;
match /{metric}/{docs=**} {
allow read: if isUserAllowed(root, metric);
allow write: if false;
}
}
}
}

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.

How to grant collection access to user based in other collection?

Let's assume that there is 3 collections and they are at the same hierarchy level:
User
UserAndOtherCollectionRelationship
OtherCollection
I desire to grant access on "OtherCollection" records to the users that own that record or are related to it (only read access).
Understand "UserAndOtherCollectionRelationship" as
UserAndOtherCollectionRelationship: {
'userId': uid, //user id provided by Firebase Auth Service
'otherCollectionId': 000,
'roles': ['owner', 'reader', ...]
}
This is what I have:
match /databases/{database}/documents {
match /otherCollection/{otherCollectionId} {
allow read, update, delete: if(isOtherCollectionOwner());
allow create: if(isSignedIn());
}
match /user/{userId} {
allow read, write: if(isSignedIn() && isUserOwner(userId));
}
match /userAndOtherCollectionRelationship/{userAndOtherCollectionRelationshipId} {
allow read: if(resource.data.userId == request.auth.uid && isSignedIn());
allow create: if(isSignedIn());
allow update, delete: if(resource.data.userId == request.auth.uid);
}
// Functions
function isSignedIn() {
return request.auth != null;
}
function isUserOwner(userId) {
return request.auth.uid == userId;
}
function isOtherCollectionOwner() {
return isUserOwner(getUserAndOtherCollectionRelationshipData().userId) && getOtherCollectionData().roles.hasAll(['owner']);
}
//This is the function that I believe that it's not working propertly
function getuserAndOtherCollectionRelationshipData() {
return get(/databases/$(database)/documents/userAndOtherCollectionRelationship/{document=**}).data;
}
}
Considering that the client (the app) must create a filter (where clause) to get only the desired records, I could not find a way to do that with this schema too.
So I put the user roles as a field on the "otherCollection" record:
otherCollection: {
...,
'userAndRoles': {
'replaceByUID': ['owner', ...]
},
}
updated the security rule function to:
function isOtherCollectionOwner() {
return get(/databases/$(database)/documents/OtherCollection/$(otherCollectionId)).data.roles[request.auth.uid].hasAll(['owner']);
}
Here is the client call:
final querySnapshot = await firestore.collection('otherCollection')
.where('user.$userId', arrayContains: 'owner')
.where('otherCollectionId', whereIn: otherCollectionIdList)
.get();
What is the best solution?
Change the data model to...
Set a different security rule as...
When a user wants to access an another collection, we have to set a rule under that collection.
So when you create a document in a collection, you have to create the same id in the other collection and create a field called owner.
This field owner contains the uid of the person who created the document.
userAndOtherCollectionRelationshipId === otherCollectionId
UserAndOtherCollectionRelationship: {
'owner': uid
}
In this way, when a user try to read the document, we check if he is owner or not with isOwner(otherCollectionId, request.auth.uid) function with Collectionid, and the userId. In the function you check if the owner who created the document is the same who is trying to read the document.
You can do create a rule as following:
match /otherCollection/{otherCollectionId} {
allow read, update, delete: if isOwner(otherCollectionId, request.auth.uid);
allow create: if isSignedIn();
}
function isOwner(docId, userId) {
return get(/databases/$(database)/documents/userAndOtherCollectionRelationship/$(docId)).data.owner == userId;
}
To solve the issue, I updated the data model removing the userAndOtherCollectionRelationship collection and add the owner attribute to the otherCollection.
Any other relationship would be added as an attribute to otherCollection.
So the otherCollection looks like this now:
otherCollection: {
owner: ["user_uid", "other_user_id"],
..., //other atributes
}
The security rules were updated to:
match /otherCollection/{otherCollectionId} {
allow read, update, delete: if(isOtherCollectionOwner());
allow create: if(isSignedIn());
}
function isOtherCollectionOwner() {
return ([request.auth.uid] in (resource.data.owner));
}
The security rules tests were updated to:
const myAuth = {uid: 'my_user_uid', email: 'my#mail.com'};
const MY_PROJECT_ID = "my_project_id";
function getAdminFirestore() {
return firebase.initializeAdminApp({projectId: MY_PROJECT_ID, auth: myAuth}).firestore();
}
function getFirestore(auth) {
return firebase.initializeTestApp({projectId: MY_PROJECT_ID, auth: auth}).firestore();
}
describe("MyApp", () => {
it("Can list if is owner", async () => {
const admin = getAdminFirestore();
const setupOtherCollection = admin.collection('otherCollection').doc('otherCollectionId');
await setupOtherCollection.set({'name': 'myOtherCollection', 'owner': [myAuth.uid]});
const db = getFirestore(myAuth);
const otherCollectionCollection = db.collection("otherCollection").where("owner", "array-contains", [myAuth.uid]);
await firebase.assertSucceeds(otherCollectionCollection.get());
});
});

Firestore rules compare reference to constructed path

In my company documents, I have a reference field named owner, which points to a user document. In the rule, I am trying to check if the authenticated uid is the owner of the company:
match /companies/{companyId} {
allow read: if isOwner(resource.data.owner, request.auth.uid);
}
function isOwner(owner, userId) {
return path('/users/' + userId) == owner;
}
I tried many things but can't figure out how to make this work.
(I know using a string instead of a reference works, but I would rather use a reference)
When you construct the path, include this prefix: /databases/(default)/documents/. It's part of the full path to a document.
match /companies/{companyId} {
allow read: if isOwner(resource.data.owner, request.auth.uid);
}
function isOwner(owner, userId) {
return path('/databases/(default)/documents/users/' + userId) == owner;
}
The following should enable you to compare on the reference field.
match /companies/{companyId} {
allow read: if /databases/$(database)/documents/user/$(request.auth.uid) == resource.data.owner
}
Note: resource.data.owner NOT request.resource.data.owner
Why not making an ownerId field in the compagny document and check if the authenticated user uid is equal to the value?
service cloud.firestore {
match /databases/{database}/documents {
match /companies/{compagnyId} {
allow read: if isOwner()
}
}
}
function currentData() {
return resource.data
}
function isOwner() {
return currentData().ownerId == request.auth.uid
}

Firebase Security Rules - using get in a function

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
}
}

Resources