I am trying to improve my Firestore database security, but I am getting desperate with this. I have tried editing it for hours to try if it would work, but it keeps erroring or denying access. The rules playground indicates it should work.. but it doesn't.
I am trying to access /environment/STABLE/route_sets/w3TBJrovQbgFPr2GFfug with user /environment/STABLE/users/RMwv3mmP4RIOVqPOsa77. User has a field called db_role containing the string ADMIN. The document in route_sets contains an array called _permissions_read with the values TEST_NOTHING and ADMIN.
Testing it with rules playground works, but when I try it via my Angular app, I get "ERROR FirebaseError: Missing or insufficient permissions.". For authentication, I am using Firebase Authentication with a custom provider, uid is document ID in users table.
The info on the tab "monitor rules" on Cloud Firestore -> Rules seems to indicate that the current rules (as below) are generating an error when trying to access a route_set.
rules_version = "2";
service cloud.firestore {
match /databases/{database}/documents {
match /environment/{environment}/{collectionName}/{documentId} {
allow read, write: if request.auth != null && collectionName == "route_sets" && checkPermissionRead(environment, collectionName, documentId);
allow read: if request.auth != null && collectionName != "route_sets";
allow write, delete: if request.auth != null;
function checkPermissionRead(environment, collectionName, documentId) {
return get(/databases/$(database)/documents/environment/STABLE/users/$(request.auth.uid)).data.db_role in get(/databases/$(database)/documents/environment/$(environment)/$(collectionName)/$(documentId)).data._permissions_read;
}
}
match /environment/STABLE/licenseholders/{document=**} {
allow read: if true
allow read, write: if request.auth != null;
}
match /environment/STABLE/users/{document=**} {
allow read: if true
allow read, write: if request.auth != null;
}
}
}
Angular app code:
import {Component, OnDestroy, OnInit} from '@angular/core';
import {AngularFirestore} from '@angular/fire/firestore';
import {AngularFireAuth} from '@angular/fire/auth';
@Component({
selector: 'app-example-security-rules',
template: `<span *ngFor="let set of routeSets"></span>`,
styles: [``],
providers: [ ]
})
export class ExampleSecurityRulesComponent implements OnInit {
routeSets = [];
constructor(private db: AngularFirestore,
public afAuth: AngularFireAuth) {
}
ngOnInit() {
this.afAuth.onAuthStateChanged(async (user) => {
if (user) {
this.db.collection<any>('environment/STABLE/route_sets').get().subscribe((d) => {
d.forEach(docT => {
this.routeSets.push({__document__key: docT.id, ...docT.data()});
});
});
}
});
}
}
NEW code & security rules working:
Security rules
rules_version = "2";
service cloud.firestore {
match /databases/{database}/documents {
match /environment/{environment}/{collectionName}/{documentId} {
allow read: if request.auth != null && collectionName != "route_sets";
allow write, delete: if request.auth != null;
}
match /environment/{environment}/route_sets/{document=**} {
allow read, write: if request.auth != null && checkPermissionRead(environment);
function checkPermissionRead(environment) {
let user = get(/databases/$(database)/documents/environment/$(environment)/users/$(request.auth.uid)).data;
return user.db_role in resource.data._permissions_read && user.licenseholder_id == resource.data.licenseholder_id;
}
}
match /environment/STABLE/licenseholders/{document=**} {
allow read: if true
allow read, write: if request.auth != null;
}
match /environment/STABLE/users/{document=**} {
allow read: if true
allow read, write: if request.auth != null;
}
}
}
Angular
this.db.collection<any>('environment/STABLE/route_sets').ref.where('licenseholder_id', '==', environment.licenseholder_id)
.where('_permissions_read', 'array-contains', DatabaseRoles.ADMIN).get().then((d) => {
d.forEach(docT => {
this.routeSets.push({__document__key: docT.id, ...docT.data()});
});
});
