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

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

Related

In firestore rules, allow reading only users that belong to at least one of the workspaces to which the client belongs

According to the program architecture, the "member" object has a unique pair of workspaceId and userId. Thus, each workspace has one or more members (users), while each user has one or more members (workspaces).
To prevent the client from reading users that do not belong to any of the workspaces s/he belongs to, I came up with the following firestore rule:
match /users/{userId} {
allow read: if request.auth != null && belongsToAnyWorkspace(userId);
}
function belongsToAnyWorkspace(userId) {
let memberWithUser = get(/databases/$(database)/documents/members/{document=**}).data;
return memberWithUser.userId == userId && existAuth(memberWithUser)
}
function existAuth(memWithUser){
let member = get(/databases/$(database)/documents/members/{document=**}).data;
return member.workspaceId == memWithUser.workspaceId
&& member.userId == request.auth.uid;
}
The result is that firestore blocks reading for all users. My impression is that the logic of my formulas is correct, but the syntax is flawed. For example, I don't think it's correct to use there wildcard {document=**} as a way to get "any document from the collection members". Any help is appreciated!

Get role in every rule Firebase security

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)

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

How to read/write specific data on Firestore

I'm having some problem with the READ rules of Firestore currently
Here is my data structure
{
email: example#gmail.com,
username: geekGi3L,
birthday: 1995/02/14,
photo: <firestore-download-url>
}
The rules currently I set is
service cloud.firestore {
match /databases/{database}/documents {
match /users/{user} {
allow read;
allow write: if request.auth.uid != null && request.auth.uid == user;
}
}
}
How could I set the rules to allow user to READ the specific fields like email and birthday only if request.auth.uid != null && request.auth.uid == uid while username and photo should be readable by every user?
Thank you <3
In Firstore, there is no per-field access control for reading fields of a document. The most granular unit of access is the document. A user either has full access to read a document in its entirety, or they don't have any access at all.
If you need to change access per field, you'll have to split the fields of the document into multiple collections, with each collection having access control appropriate for the fields of the documents within. It's very common to have a split between public and private data like this.

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.