Firebase FireStore Abstracted function not working - firebase

I have a working read rule in fireStore to check that a user is in the users array of the /accounts/{account} resource:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /accounts/{account} {
allow read: if request.auth != null && request.auth.token.email in resource.data.users;
}
}
}
In order to simplify the code and to use a function elsewhere to check user access based on a resource id, and following the information in this link: Security Rules! 🔑 | Get to know Cloud Firestore #6 19:25 I have attempted to abstract the code:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
function hasUserAccess(account){
return request.auth.token.email in get(/databases/$(database)/documents/accounts/$(account)).data.users;
}
match /accounts/{account} {
allow read: if request.auth != null && hasUserAccess(account);
}
}
}
So that when I want to cross-reference the access for related documents, I can call the function. Why does the abstracted version fail to work? It seems like it should be working correctly based on the youtube video.

You will need to use hasAny() to check if the users list of accounts contains the email passed.
Also note that you have to pass the request.auth.token.email in the function.
Please use the following code in order to see if the user exists in the field "users" of your other collection:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
function hasUserAccess(account,email){
return get(/databases/$(database)/documents/accounts/$(account)).data.users.hasAny([email]);
}
match /accounts/{account} {
allow read: if request.auth != null && hasUserAccess(account, request.auth.token.email);
}
}
}

This works, it seems I couldn't use the function within the collection the function was inspecting?
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
function hasUserAccess(account){
return request.auth.token.email in get(/databases/$(database)/documents/accounts/$(account)).data.users;
}
// You can't use the function within this match condition
match /accounts/{account} {
allow read: if request.auth != null && request.auth.token.email in resource.data.users;
}
// You CAN use the function within this match condition as it isn't for the collection /accounts
match /otherData/{account} {
allow read: if request.auth != null && hasUserAccess(account)
}
}
}

Related

In Firestore rules how do you check the id of the requesting document?

I mean I want to check to be sure the document id of the request is the same as the document id in the database.
I tried this but it doesn't work:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read: if request.resource.id == userId;
}
}
}
Also tried this and it also didn't work:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read: if request.resource.id == resource.id;
}
}
}
I've searched online for hours and every single post is about using authentication uid.
if you want to make sure the uid in request matches name of the user document, then use this:
match /users/{userId} {
allow read, update, delete: if request.auth != null && request.auth.uid == userId;
}
and if you want to allow new users to be created add this too:
allow create: if request.auth != null;
more details : https://firebase.google.com/docs/firestore/security/rules-conditions

Firestore security rules and vuefire

i have the following sample app here: Github repo
It uses vuefire in ChatList.vue
// vuefire firestore component manages the real-time stream to that reactive data property.
firestore() {
return {
chats: db.collection('chats').where('members', 'array-contains', this.uid)
}
},
I now wrote security rules to secure the data, but can't seem to get the combination of vuefire and security rules to work:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false;
}
// THIS IS THE PART I'D LIKE TO REMOVE
match /chats/{chatId=**} {
allow read: if request.auth.uid != null;
}
// THIS WORKS AS INTENDED, AND I'D LIKE TO INCLUDE "READ"
match /chats/{chatId}/{documents=**} {
allow write: if chatRoomPermission(chatId)
}
function chatRoomPermission(chatId) {
return request.auth.uid in get(/databases/$(database)/documents/chats/$(chatId)).data.members;
}
}
}
So the goal is: make the individual chats only readable and writable to users that are in the members array in firestore. (Currently i achieved this partially, since all chats are readable to anyone, but only writable to users in the members array.)
Do i have to rewrite the vuefire component so i can have the following security rule? (It gives an error message: listing of chats not possible due to missing permissions)
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false;
}
match /chats/{chatId}/{documents=**} {
allow read, write: if chatRoomPermission(chatId)
}
function chatRoomPermission(chatId) {
return request.auth.uid in get(/databases/$(database)/documents/chats/$(chatId)).data.members;
}
}
}
For completeness, the working solution is (credits to Renaud Tarnec):
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false;
}
match /chats/{chatId=**} {
allow read: if request.auth.uid in resource.data.members;
}
match /chats/{chatId}/{documents=**} {
allow read, write: if chatRoomPermission(chatId)
}
function chatRoomPermission(chatId) {
return request.auth.uid in get(/databases/$(database)/documents/chats/$(chatId)).data.members;
}
}
}
Since you want to check, in your Security Rules, if a given value (the user uid in this case) is contained in a field of type Array in your document, you can use the in operator of the List type.
So, the following should do the trick:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false;
}
// THIS IS THE PART I'D LIKE TO REMOVE
match /chats/{chatId=**} {
allow read: if request.auth.uid in resource.data.members;
}
// ....
}
}

firebase firestore rules authenticated access to all collections except one

I have the following firestore structure, basically 3 collections
publicdata
protecteddata1
protecteddata2
I want to have protecteddata1 and protecteddata 2, and really the entire firestore database as authenticated users only.
But i want the public to have readonly access to 'publicdata' collection..
The following is my attempt but it doesn't work
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read;
allow write: if (request.auth.uid != null);
}
match /publicdata {
allow read;
}
}
}
You can use the following functions I created to do this
function isUserAuthenticated() {
return request.auth.uid != null;
}
You can then use it like this:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if isUserAuthenticated();
}
match /publicdata/{itemId} {
allow read : if true;
allow create : if isUserAuthenticated();
allow update: if isUserAuthenticated();
allow delete: if isUserAuthenticated();
}
/* Functions */
function isUserAuthenticated() {
return request.auth.uid != null;
}
}
}
As others have explained, if you have multiple matches for the same document they are OR'ed together, so you can't implement an exception with that.
What you can do though is capture the collection name in a variable, and then implement the exception in a single match:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{collection}/{document=**} {
allow read: if collection != 'publicdata';
allow write: if (request.auth.uid != null);
}
}
}
So here we allow reads from all collections except publicdata.
Because here is says:
Overlapping match statements
It's possible for a document to match more than one match statement. In the case where multiple allow
expressions match a request, the access is allowed if any of the
conditions is true: ...
You can use this:
rules_version = '2';
service cloud.firestore {
// Check if the request is authenticated
function isAuthenticated() {
return request.auth != null;
}
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if isAuthenticated();
}
match /publicdata/{document=**} {
allow read: if true;
}
}
}

How can I make firebase rules that allow auth users to read all documents but only create their own user document not giving themselves admin rights?

I am struggling with the firebase security rules, I can get parts of it to work, but when I try to connect it all together I am having issues.
I would like to have my rules do the following:
Allow read to all documents if authenticated
Allow create or update user document by authenticated only at document /databases/$(database)/documents/users/$(request.auth.uid) but not add admin to the roles array
!("admin" in getAfter(/databases/$(database)/documents/users/$(request.auth.uid)).data.roles)
Allow only getRole(“admin”) == true to create, edit, or delete any user document and any other document
function getRole(role) {
return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.roles.hasAny([role]);
}
Here is what I have that does not include the users being able to create their own user account. It works to allow only admin to write any document.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
function getRole(role) {
return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.roles.hasAny([role]);
}
match /{document=**} {
allow read: if true;
allow write: if getRole('admin') == true;
}
}
}
Here is what I tried to add to allow users to create their user document. It seems to be not cascading to the next rule, it tries the getRole in the match /{document=**} path and finds that the user is not an admin so it fails. I have tried reordering and placing the /users/ path above and it goes thru that path fine then does the same thing and fails on the getRole in the /{document=**} path again. I also tried specifying the document names rather than using the wildcard and that seems to not allow any get or write. Can you please point me in the right direction?
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
function getRole(role) {
return exists(/databases/$(database)/documents/users/$(request.auth.uid)) && get(/databases/$(database)/documents/users/$(request.auth.uid)).data.roles.hasAny([role]);
}
match /{document=**} {
allow read: if true;
allow write: if getRole('admin') == true;
}
match /users/{userId}{
allow read: if request.auth.uid != null;
allow create: if getRole('admin') == true || request.auth.uid == userId &&
!(getAfter(/databases/$(database)/documents/users/$(request.auth.uid)).data.roles.hasAny(["admin"]));
allow update: if getRole('admin') == true || request.auth.uid == userId && exists(/databases/$(database)/documents/users/$(request.auth.uid)) == true && !(getAfter(/databases/$(database)/documents/users/$(request.auth.uid)).data.roles.hasAny(["admin"]));
}
}
}
It turns out that I can't allow admin to dynamically create collections that are not defined in the security rules unless I use the match /{document=**} which applies those rules to every path and is not the desired result. I was able to get the rules setup to accomplish the other parts pretty easily as follows:
service cloud.firestore {
match /databases/{database}/documents {
function getRole(role) {
return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.roles.hasAny([role]);
}
match /users/{userId} {
allow read: if request.auth.uid != null;
allow create: if request.auth.uid == userId && request.resource.data.roles.hasAny(["admin"]) == false;
}
match /collectionName/{collectionNameId} {
allow read: if request.auth.uid != null;
allow write: if request.auth.uid != null && getRole('admin') == true;
}
}
}

How to set Firestore security rules? resource.data: Null value error

I need some help making my security rules for firestore work.
These are my firestore rules:
service cloud.firestore {
match /databases/{database}/documents {
match /orders/{orderID} {
allow read, update: if request.auth.uid == resource.data.buyerId || request.auth.uid == resource.data.sellerId;
}
}
}
my orders collection:
orders: {
sellerId: 'some-id',
createdAt: timestamp,
buyerId: 'some-id'
}
It should return all documents from orders collection which has either buyerId or sellerId equal to authorised user (request.auth.uid).
but the above rule is not working as expected.
firestore collections screenshot
firebase simulator output
That error message is suggesting that the requested document was not actually present in the database. You entered "orders/{orderId}", which looks like you put a wildcard in the Location field in the simulator. That's not going to work. You need to enter the path to an actual document that exists if you want to test your rule that uses its field values.
resource.data: Null - this error happens when you try to create a new entity.
Split write rule, on create and update.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /user/{userId} {
allow read: if request.auth.uid == userId;
function authed() {
return request.auth.uid == userId;
}
allow create: if authed() && request.resource.data.keys().hasOnly(['name']);
allow update: if authed() && request.resource.data.diff(resource.data).changedKeys().hasOnly(['name']);
allow delete: if authed();
}
}
}

Resources