How do I access the document ID in firestore rules - firebase

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.

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

Setting Firestore security rules with Collections generated at runtime

My Firestore database creates a new Collection whenever a new user Signs Up to my app. The name of the Collection is the username of the new user. I wanted to make the documents inside this collection to have restricted write access.
service cloud.firestore {
match /databases/{database}/documents {
match /User1/{info} {
allow read: if signedIn();
allow write: if isOwner(User1);
}
function signedIn() {
return request.auth != null;
}
function isOwner(userId) {
return request.auth.uid == userId;
}
}
}
This works if the current user is User1 but is not applicable to any new user that signs up. How do I add this Firestore Security Rule to every new user?
I notice that the first rule matches to /User1/{info}, meaning it will match any path in the collection User1. Instead, if you use brackets, this value becomes a wildcard, meaning the match will work for any value. Check out the examples in the guide for more information.
service cloud.firestore {
match /databases/{database}/documents {
match /{username}/{info} {
allow read: if signedIn();
allow write: if isOwner(username);
}
function signedIn() {
return request.auth != null;
}
function isOwner(userId) {
return request.auth.uid == userId;
}
}
}

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