Improve Firestore rules - firebase

I have been trying to improve the Firestore rules that secure the database for a few days now. I only seems to lock everyone out with every edit. The rules I use now are the basic rules found in the Firestore documentation. Which are:
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth.uid != null;
}
}
}
But I would like to extend the security a bit to tighten up the edit rules. My database looks like this:
Users (collection) > User (document) > User specific data
What I would like to have is that every authenticated user can read all the data, but only the user a document belongs to (by unique user id) may edit/add/delete their data.
I hope one of you could point me in the right direction, as I seem to not get any wiser from the official documentation.
Update: How I integrated Firestore in my Android app.
user = FirebaseAuth.getInstance().getCurrentUser();
db = FirebaseFirestore.getInstance().collection("users");
CollectionReference colRef = db.document(user.getUid()).collection("watched");
colRef.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
ArrayList<Movie> tempItems = new ArrayList<>();
for (DocumentSnapshot document : task.getResult()) {
// Handle data
}
} else {
Log.d(TAG, "Error getting documents: ",task.getException());
}
}
});

I apparantly read the documentation about the resource.data wrong. I had to add the author_id field myself. I did not know this, but once I added this it worked like a charm!

You can write a rule to make sure that the uid of the requesting user matches the author_id field of the document:
service cloud.firestore {
match /databases/{database}/documents {
match /Users/{User} {
allow read: if request.auth.uid != null;
allow create, update, delete: if request.auth.uid == resource.data.author_id;
}
}
}

Related

Firebase Firestore Database Rules: using arrays from another document

I am trying to make a web app that displays different elements based on what permissions I give to a user.
All the permissions are stored in the the Cloud Firestore database at /users/{userId} in the field "permissions", which is an array containing the permissionId's.
In /photo_libraries/{libraryId} I have a field called permissionId, which is a string.
I now want to give users that have the right permissionId to be able to read the document in /photo_libraries/{libraryId} that has that permissionId.
I've tried this:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read: if request.auth.uid == userId;
}
match /photo_libraries/{libraryId} {
allow read: if get(/database/$(database)/documents/photo_libraries/$(libraryId)).data.permissionId in get(/databases/$(database)/documents/users/$(request.auth.uid)).data.permissions;
}
}
}
But this doesn't seem to work, I'm quite new to the Firestore rules. Can anyone help me out?
P.S. This is how my database looks like:
This is the code I try to run:
const db = firebase.firestore(); const auth = firebase.auth();
auth.onAuthStateChanged(user => {
if (user) {
db.collection('photo_libraries').get().then(snapshot => {
// set up the UI
}, err => {
console.log(err.message);
});
} else {
// Logging out stuff
};
});
In the console I get the error message:
Missing or insufficient permissions.
Thank you,
Jonas
Try this:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read: if request.auth.uid == userId;
}
match /photo_libraries/{libraryId} {
allow read: if resource.data.permissionId in get(/databases/$(database)/documents/users/$(request.auth.uid)).data.permissions;
}
}
}
But a better solution would be to add the persmissions array as a custom claim, then you dont need to call get.
When querying Firestore make sure you are only querying the documents you can actually access. Look for "rules are not filter" on google and you will get plenty of hits on SO and in the official Firebase documentation.

Access database reference in firestore rules

I am struggling to access document references in the firestore rules. My database looks like this. (Simplified for brevity):
curriculum
session1
roles
admin
--- canEditContent
user
--- canEditContent
users
userid
--- role
roles/admin <document reference>
I want to access the admin permissions based on the document reference.
I have tried several ways however can't seem to get anywhere. This is my code so far
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
function isSignedIn() {
return request.auth != null;
}
function getUser() {
return get(/databases/$(database)/documents/users/$(request.auth.uid));
}
function getUserRole() {
let role = get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role;
return get(role);
}
match /curriculum/{curriculum} {
allow write: if isSignedIn() && getUserRole().data.canEditContent;
}
match /users/{userId} {
allow read, update, delete, write: if request.auth != null && request.auth.uid == userId;
allow create: if request.auth != null;
}
}
}
I have tried many ways and can't seem to solve it.
Thanks in advance
EDIT
Added screenshots below of collections
Users collection showing role as a document ref to a role document in the roles collection.
Roles collection
I can see two issues in your rules:
get needs the full document path, so your function getUserRole wont work. Try this instead:
function getUserRole() {
let role = getUser().data.role;
return get(path("/databases/" + database + "/documents/" + role));
}
Your rule uses the role canEditContent but the data you show uses editContent, is that on purpose?
As already mentioned please provide the complete set of data & query & rules, here we cant see the query you are using. Also note that you can use the Firestore emulator to get information on what rule is failing and where.

Firestore rules access to parent document

So i'm making an app with a friends system and trying to set up rules for firebase to handle reads & writes if the users is friends or not.
I'm very stuck at a particular call that i just don't have any idea on how to make.
My firestore is structured as follows:
users/userUID/places/documentsofplaces
each userdocument have some fields of the usual information, name, username, etc. and an array of friendsUID.
I have managed to get the first part down, that a user can only read and write if it's UID matches the documentUID, and looking in the friendslist a friend can only read but not write.
The next part, in the places collection, just throws me off, how can i get the parent document and compare the userUID to a UID in the friendslist?
This is what i have so far:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
// Allow write and read if user, and read if friend
allow write: if isUser(userId);
allow read: if isUser(userId) || isFriend();
function isUser(userId) {
return (request.auth.uid == userId);
}
function isFriend() {
return (request.auth.uid in resource.data.friendsList);
}
}
match /users/{userId}/places/{documents} {
allow write: if isUser(userId);
allow read: if isUser(userId) || isFriend(userId);
function isUser(userId) {
return (request.auth.uid == userId);
}
function isFriend(userId) {
return (request.auth.uid in get(/databases/$(database)/documents/users/userId.resource.data.friendsList));
}
}
}
}
Any help is greatly appreciated!
Your document get() should look more like this:
get(/databases/$(database)/documents/users/$(userId)).data.friendsList
You have to use variables with $(var) notation inside the document path. get() returns a Resoruce object with a data property. I suggest reading over the documentation for accessing other documents for more details.

Firebase DocumentSnapshot is always null

I am trying to get the data of a document in Firebase. I am using this function:
DocumentSnapshot docRef =
await Firestore.instance.collection("products").document("SF").get();
print(docRef.exists);
docRef.exists returns "false" even if the document is exisiting for sure.
I think it has something to do with the auth flow and the system does not recognize the logged in user.
print(FirebaseAuth.instance.currentUser());
results in Instance of 'Future < FirebaseUser>'.
Any idea how to solve the problem?
Best regards
EDIT:
Here are my rules from firebase:
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write;
}
}
}
The way you define the security rules is not correct. Change your security rules.
match /products/{document=**} {
allow read, write: if true;
}

How can I setup rules so I can write documents to firestore protected by uid?

I want to make it so I can have authenticated users write to a collection that is only read/write/create-able to that user only. I'm struggling with the most basic Firestore setup. My firestore rules look like this:
service cloud.firestore {
match /databases/{database}/documents {
match /{userId} {
allow read, write, create: if request.auth.uid == userId;
}
}
}
I'm assuming this will prevent read/writes/creates to the database unless the initial part of the path matches the UID of the logged in user.
My JavaScript code looks like this:
function addSomeData(data) {
console.log( "Sending data with: ", user.uid, data );
db.collection(user.uid).add({ data })
.then(function(docRef) {
console.log("Document written with ID: ", docRef.id);
})
.catch(function(error) {
console.error("Error adding document: ", error);
});
}
I definitely have user.id set correctly after successful login.
Using things this way always gives me this error in the console:
Error adding document: Error: Missing or insufficient permissions.
If I revert to the original rules like this then the document is successfully created:
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write;
}
}
}
What I'm trying to do is have each collection start with the UID of the logged in user. So, a sample shape of the data might be this:
/ax323/brty/data="Hello"
/ax323/98da/data="Goodbye"
/br981/ha31/data="No comment"
So, ax323 is a UID in Firebase, as is br981. ax323 has two documents in the collection, while br981 has one.
Is the "shape" of my data the problem?
I don't really understand what the {document=**} means in the original rules, and whether I need to convert my authentication rule to something similar.
In your database rules you have used match /users/{userId} {
This rule will apply only to the document mathching that particular path.
So if your document path is /ax323/brty/data then your rules should be like
service cloud.firestore {
match /databases/{database}/documents {
match /{userId}/{document=**} {
allow read, write, create: if request.auth.uid == userId;
}
}
}
Also looking at your question, I can't get what brty means when you mentioned
/ax323/brty/data="Hello" ?

Resources