Security rules Firestore - google-cloud-firestore

I'm trying to secure my data in Firestore. I have read the documentation and watch some videos but I still have some difficulties getting it right.
What I have built is a project app. With a data structure like this:
"School": {
school1:
school2: {
"Users": {
userId: {
"SchoolName": "school2"
}
}
"Projects": {
projectId: {
}
}
}
}
Only authenticated users can read and write to the whole database and only users in the same school can read and write data to that school. For example, only users in school2 can add a project to school2.
I tried something like this but it didn't work
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth.uid != null;
}
match /School/{schoolName} {
allow read, write: if get(/databases/{database}/documents/School/$(schoolName)/Users/{userId}).data.SchoolName[(schoolName)]
}
}
}
Can someone please show me how to do this and maybe some good explanation on how to think about security rules. Thank you very much in advance!

you made just one mistake, replace this line:
allow read, write: if get(/databases/{database}/documents/School/$(schoolName)/Users/{userId}).data.SchoolName[(schoolName)]
with this :
allow read, write: if get(/databases/{database}/documents/School/$(schoolName)/Users/{request.auth.uid}).data.SchoolName == "school2"

Related

Firestore security when retrieving document by id denies permission

When I directly retrieve a document using .doc().get() firestore security rues deny permission. What do I need to do to allow CRUD operations for authenticated users of my Ionic app?
I have the following function in my PWA Ionic app
``` async getUserProfile(): Promise<firebase.firestore.DocumentSnapshot> {
const user: firebase.User = await this.authProvider.getUser();
this.currentUser = user;
console.log('found current user');
this.userProfile = firebase.firestore().doc(`userProfile/${user.uid}`);
console.log('returing userprofile', this.userProfile.get());
return this.userProfile.get();
}```
But the call to this.userprofile.get() fails due to security warning from Firestore Security rules
found current user
returing userprofile
ZoneAwarePromise
__zone_symbol__state: 0
__zone_symbol__value: FirebaseError: Missing or insufficient permissions. at new e (http://localhost:8100/vendor.js:92760:23) at http://localhost:8100/vendor.js:102987:28
.
.
.
(http://localhost:8100/polyfills.js:10248:56)
code: "permission-denied"
name: "FirebaseError"
toString: ƒ ()
message: "Missing or insufficient permissions."
stack: "FirebaseError: Missing or insufficient permissions.\n at new e
The Firestore Security Rules are set simply as
service cloud.firestore {
match /databases/{database}/documents {
match /userProfile/{userId}/{documents=**} {
allow read, update, get,delete: if request.auth != null && request.auth.uid == userId;
allow create: if request.auth != null;
}
}
}
What should be the correct security rule to implement to allow authenticated users to CRUD their documents? Or the correct way to retrieve the document from the collection
UPDATE: It works directly from the simulator but not from the app
Some inspiration. I use this very basic role based access to allow read and write where a normal user can read and admins can write:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
function googleProvider() {
return request.auth.token.firebase.sign_in_provider == "google.com";
}
function getUserData() {
return get(/databases/$(database)/documents/users/$(request.auth.uid)).data
}
allow read: if googleProvider() && getUserData().roles['user'] == true;
allow write: if googleProvider() && getUserData().roles['admin'] == true;
}
}
}
What version of security rules do you have?
There is a hint for the version 1:
Security rules use version 1 by default. In version 1, recursive wildcards match one or more path items. They do not match an empty path, so match /cities/{city}/{document=} matches documents in subcollections but not in the cities collection, whereas match /cities/{document=} matches both documents in the cities collection and subcollections.
That could explain why you can't get the data you want.
The version 2 behaves different:
In version 2 of the security rules, recursive wildcards match zero or more path items. match/cities/{city}/{document=**} matches documents in any subcollections as well as documents in the cities collection.
Here you can see the whole docu for it.
The version should be on top of your rules like here:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Matches any document in the cities collection as well as any document
// in a subcollection.
match /cities/{city}/{document=**} {
allow read, write: if <condition>;
}
}
}

Firebase Firestore server side rules - problems

I have an app in iOS that I'm nearly complete and I'm working to configure the server side rules in Firestore. I'm copying the rules identically to another app I have which users the same authentication and database structure. This set of code is not working however.
My data structure has two root collections: User and Feed. Feed is a public collection that any authenticated user should be able to access.
The User Collection and sub collections should only be accessible to the authenticated user who is logged in.
The feed access is working correctly here, but I'm not able to read and presumably not write anything to the user collection/subcollections.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read, write: if request.auth.uid == userId;
}
match /feed/{docId} {
allow read, write: if request.auth != null;
}
}
}
This works - so it is established that the authentication is working. The user documentID is the same as the UID in the authentication table. I'm going nuts with this.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read: if request.auth != null;
}
}
}
I got it to work with this - I still don't understand why my other app works but not this one.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId}/{document=**} {
allow read, write: if request.auth.uid == userId;
}
match /feed/{docId} {
allow read, write: if request.auth != null;
}
}
}

Firestore Storage Rules Chat

I have a chat application in which images can be sent, I want to write the security rules for Storage so that only what is in the chats collection and in the UserIds array can send images, but it always tells me that I am not authorized.
service firebase.storage {
match /b/{bucket}/o {
match /Users/{userId}/{allPaths=**} {
allow read: if request.auth.uid != null;
allow write: if request.auth.uid == userId;
}
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.userIds;
}
}
}
Posting this as a Community Wiki as it's based on Frank's Comments:
Reading data from Firestore directly in the Firebase Rules of your Storage is not possible and the alternative os using custom claims is not possible since they cannot exceed 1000 bytes.
The alternative you have in this case would be to create the control in your app's code or in a Cloud Function.

"Insufficient permissions" querying with Vue.js to Firestore

I'm having problems querying Firestore with Vue.js.
This are my rules on Firestore:
service firebase.storage {
match /databases/{database}/documents {
match /users/{userid=**} {
function getRole(role){
return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.roles[role]
}
allow read, write: if getRole('admin') == true
}
}
}
This is my structure, if I have to change it, it doesn't matter. I'm learning, I want to make the best practices:
-users[Collection]
-userID[Document]
-forms[Collection]
-author: userID
-name: "name"
-roles: {
-admin: true
}
and this is the query that I'm trying with Vue.js:
usersRef.doc(`${this.user.uid}`).collection('forms').where('author', '==', true).get().then(snapshot => {
console.log(snapshot)
})
There was a typo. Instead of firebase, is firestore:
service firebase.storage
service firestore.storage.

How to allow only particular fields of a firestore document to be accessed publicly

Suppose I have this structure in a firestore database:
collection[
document: {
x: 'x',
y: 'y'
}
]
and I have this firebase rule in place:
service cloud.firestore {
match /databases/{database}/documents {
match /collection/{document} {
allow read: if true;
}
}
}
But this rule exposes the whole document in the above collection, What I want to do is to expose only x field, is it possible? Thanks
You can't. Security rules only work on a document-level basis, so you can't restrict read access to specific fields.
If you want to do something like you're suggesting, you'll probably need to restructure your data so that your non-public data is in a separate document. Most likely you'll want to do this by putting your private data in a subcollection. So you might end up with something like this...
collection: [
document: {
y: 'y'
private-collection: [
document: {
x: 'x'
}
]
}
]
And then you'd set up your security rules like:
service cloud.firestore {
match /databases/{database}/documents {
match /collection/{document} {
allow read: if true;
match /private-collection/{otherdoc} {
allow read: if someOtherRuleThatYouAddHere();
}
}
}
}
YOU CAN!
not do this for reading, but for updating. This is not what was asked for, but it's probably what many people came here for - including myself.
allow update: request.resource.data.diff(resource.data).affectedKeys().hasOnly(['MyField']);
Docs