Get role in every rule Firebase security - google-cloud-firestore

Hello so I have a role in my user collection and I wanted to write the rules depending on the role so if the role is the teacher you can have access to a little more stuff than the parent role. Now my question is there a possibility that I can access the role and use it for every collection, not only the user collection. Like a function that just checks every time what your role is?
I'm doing this for the first time and I'm not pretty sure if I understand everything right, so far.
This is what I have in my rules so far:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
function isSignedIn() {
return request.auth != null;
}
function isOneOfRoles(rsc, array) {
return isSignedIn() && ((getRole() in array) || rsc.data.openWorld == true);
}
function getRole() {
return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == 'pädagoge';
}
match /posts/{userPosts} {
allow read: if isSignedIn();
allow create: if isOneOfRoles(resource, ['pädagoge']);
}
match /messages/{messages} {
allow read, write: if isSignedIn();
}
}
}
UPDATE

I've tried your security rules in the Firestore "Rules playground". You can see below that you need to do isOneOfRoles(request.resource, ['pädagoge']);: with only resource, the rule engine cannot check the value of the field openWorld beacause the future state of the document is contained in the request.resource variable, not in the resource one. See the doc for more details.
You also need to have a corresponding user in the users collection with a role field with the value pädagoge: in my example the user's UID is A1 (i.e. the ID of the Firestore doc in the users collection). See on the second and third screenshots below how we use this value in the Firebase UID field in the "Rules playground" simulator.
(same screenshot as above, only the left pane was scrolled down to show the Firebase UID field)

Related

How to lock updating/deleting a collection created by a user to the same user who created the collection in firestore (without using cloud Functions)?

I have the following model and rules... the first part of the equation is working fine a user can CRUD his own collection that he created, and it has the the same id as the creator.
the issue lies in the other collection that created by that user since it has a different id (an autogenerated id by firestore and I want to lock this collection to by modified only by the user that created it) any idea how to do this ?
Collections Model
// Data // doc id === uid
subDataId: null,
createdAt: Timestamp.now(),
// Sub Data // doc id === generated automatically by fb refrenced in the data collection.
online: false,
createdAt: Timestamp.now(),
// Firebase rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /Data/{userId} {
allow read , create: if request.auth != null
allow update, delete : if request.auth.uid == userId
}
match /SubData/{SubDataId} {
allow read , create: if request.auth != null
// allow update : if request.auth.uid == ?????????
}
}
}
Write a rule that requires the user to provide their own user ID as a field of the document being created (see the documentation about requiring that a field is provided). Make sure the code in your app does exactly that. Also write a rule that checks if the user id of the user updating the document also matches the field that was previously written by that user.
You might want to read this other question as well.

Firestore rules: unable to verify role in subcollection

I'm having difficulties to setting up my security rules
Data Structure
-- books (collection)
-- bookId (autogenerated Id - doc)
-- {chapter: text}, {chapter: text},
userBooks
-- email (email of user logged in - doc)
-- books (sub collection)
-- bookId (referencing bookId doc on collection book)
-- role: admin or read
The use case is as follow, a user with admin role is the only one permitted to share a book, allowing him to add an entry in books of another user.
created the function trying to achieve that but it does not pass successfully
I'm am logged in as user with email userWantsToShare#gmail.com. When user tries to share his book to addUser#gmail.com I make the following request:
userWantsToShare#gmail.com making the request to write under addUser#gmail.com collection.
final role = {'role': 'edit'};
await FirebaseFirestore.instance
.collection('userBooks')
.doc("addUser#gmail.com")
.collection('books')
.doc('9KHYZJVBY3BNAlYPYYoA')
.set(role);
When coming to firebase
this should translate to /userBooks/addUser#gmail.com/books/9KHYZJVBY3BNAlYPYYoA
Data {'role': 'edit'}.
match /userBooks/{emailId}/books/{bookId} {
allow write: if isSharedEmail(bookId);
}
//here im verifying the user that wants to share has admin role and therefore is authorised to write in another user book subcollection
function isSharedEmail(bookId){
//this should translate to /userBooks/userWantToShare#gmail.com/books/9KHYZJVBY3BNAlYPYYoA
get(/databases/$(database)/documents/userBooks/$(request.auth.token.email)/books/$(bookId)).data.role == "admin" ;
}
As userWantToShare#gmail.com has under that path admin role it should allow the insertion. But most likely I'm omitting something as it does not work at all.
What m I missing?
Thanks in advance
Ok, for anyone making the same dumb mistake as I did. When making the call to the function isSharedEmail was outside the context.
Meaning that by moving the function to the scope inside the context was enough to make it work
Before: not working
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /userBooks/{email} {
allow write: if ( isAppShared())
allow read: if true;
}
}
}
function isAppShared() {
return get(/databases/$(database)/documents/userBooks/$(request.auth.token.email)).data.bookId == request.resource.data.bookId;
}
After: Working
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /userBooks/{email} {
allow write: if ( isAppShared())
allow read: if true;
}
function isAppShared() {
return get(/databases/$(database)/documents/userBooks/$(request.auth.token.email)).data.bookId == request.resource.data.bookId;
}
}
}

Firestore Database Rules Get document

Being honest, i'm a bit lost with what i'm trying to do in firestore.
For this simple situation, I want to get a value from a different collection, that has nothing to do with user.
However, I keep getting a malformed error. Any help?
My issues are around the //NOT SURE IF {business} IS CORRECT HERE and //I WANT TO GET THE THE RECORD WHERE THE AREAS.NAME == DASHBOARD.
(it will contain the allowed users for that specific area that i will match against the users role later on)
service cloud.firestore {
match /databases/{database}/documents {
//match /{document=**} {
// allow read, write: if isSignedIn();
//}
//NOT SURE IF {business} IS CORRECT HERE
match /business/{business} {
//I WANT TO GET THE THE RECORD WHERE THE AREAS.NAME == DASHBOARD
allow write: if get(/databases/$(database)/documents/areas/{}).data.name == "dashboard";
}
}
You have to define a specific document id to access document from another collection
match /business/{business} {
allow write: if get(/databases/$(database)/documents/areas/document_id).data.name == "dashboard";
}

Firestore: How to set security on object types with users as field names

As I understand it, Firestore does not allow queries within fields of type array, which is a shame. Therefore, if you want to be able to query the contents of an array you have to set up a field as an object type and then set the fields like a map, this is called a nested map. I want to have a map where the key is the ID of another user. Therefore, the database structure is:
database
users
{userId}
friends
userId1: true
userId2: true
The 'userId1' and 'userId2' field names will vary depending on the userId of the person listed as a friend.
The question is, how do I write my security rule so I can find my documents (via my {userId}) and the documents of other users where my {userId} is a field in the 'friends' object of the other user's document?
I guess it needs to be something like..
match /users/{userId} {
allow read, update, delete: if resource.data.userId == request.auth.uid;
allow read: if resource.data.friends.{userId} == true;
}
But of course this does not work because you cannot seem to use the variable {userId} to name the field that you want to perform a test on. So, if this cannot be done, what is a way to search for documents and have my {userId} stored somehow in someone else's document?
Edit
Well, I think I have the rules determined (see below). However, when trying to test these rules I can't seem to write a Swift call to retrieve data based on that friends object. My Swift call is:
db.collection("users").whereField(FieldPath(["friends.\(userId)"]), isEqualTo: true)
So, my questions are:
Are the rules below correct?
How do I make a Swift call to find the people with a certain userId in the field name of an object type?
service cloud.firestore {
match /databases/{database}/documents {
match /users/{documentId} {
allow read, write: if isOwner();
allow read: if getFriend(request.auth.uid) == true;
function isOwner() {
return request.auth.uid == resource.auth.uid;
}
function getFriend(userId) {
return getUserData().friends[userId]
}
function getUserData() {
return get(/databases/$(database)/documents/rooms/{documentId}).data
}
}
}
}
I still have not resolved the problem of accessing fields in an object, but it is noted that my Security Rules where generally invalid. You cannot have multiple lines with the same rule type in it, you cannot have multiple lines with 'allow: read' for example. You must use && and ||. For example, the correct definition for the basic rules if you want to check two things are:
// Database rules
service cloud.firestore {
// Any Cloud Firestore database in the project.
match /databases/{database}/documents {
// Handle users
match /users/{documentId} {
// The owner can do anything, you can access public data
allow read: if (isOwner() && isEmailVerified()) || isPublic();
allow write: if isOwner() && isEmailVerified();
// Functions //
function isSignedIn() {
return request.auth != null;
}
function isOwner() {
return request.auth.uid == resource.data.userId;
}
function isPublic() {
return resource.data.userId == "public";
}
function isEmailVerified() {
return request.auth.token.email_verified
}
}
}
}

Firestore Security Rule Allows Reads But Not Creates

I have a top level collection called "readings". Each document in readings has a field called "patientUid".
I have the following security rules:
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false;
}
//Takes a uid and returns true if it's the uid of the current user
function isTheUidOfCurrentUser(uid) {
return request.auth.uid == uid;
}
//A patient or one of their doctors can view their readings
match /readings/{reading} {
allow read, write: if isTheUidOfCurrentUser(resource.data.patientUid);
}
}
}
Currently everything is fine if a user is reads one of the readings, but a user can't create a reading for some reason. What's most strange is the simulator in the console doesn't even show the rule matching when a reading is trying to be created.
What am I doing wrong here. Why does the rule only match on reads, and not on writes?
The problem was that resource.data represents the document before the write occurs. Therefore when creating a document, resource.data did not have a patientUid field.
Once I changed
resource.data.patientUid
to
request.resource.data.patientUid
it worked since request.resource represents what the document will be after the write.