I have a Firestore database and has the following structure:
collection (teachers): {
doc (id): {
name: name
lastName: lastName
collection (classes): {
doc (id): {
name: math
}
}
}
}
What I'm trying to achieve is that the headmaster is able to get the teacher's name and add/create some classes for the very same teacher. The problem comes with adding the Firestore rules. I've tried this three rule possibilities, but none worked as expected. I was able to read, but no writing was possible.
1
service cloud.firestore {
match /databases/{database}/documents {
match /teachers/{teacher} {
allow get if true;
allow create if true;
}
}
}
2
service cloud.firestore {
match /databases/{database}/documents {
match /teachers/{teacher} {
allow get if true;
}
match /teachers/{teacher}/classes/{class} {
allow create if true;
}
}
3
service cloud.firestore {
match /databases/{database}/documents {
match /teachers/{teacher} {
allow get if true;
match /classes/{class} {
allow create if true;
}
}
}
I'm working with angularfirestore2, by the way.
What I get from your question is that, you want to give read, write for /teachers/{teacher}/classes/{class} to some users with headmaster role.
For that, first you need to check which users are headmasters.
If your teachers doc id is same as userid created in firebase auth, you can add a data field to your teacher document named isHM and set this to true, if user is headmaster:
collection (teachers): {
doc (id): {
name: name
lastName: lastName
isHM: true
collection (classes): {
doc (id): {
name: math
}
}
}
}
Now add the following rule:
service cloud.firestore {
match /databases/{database}/documents {
match /teachers/{teacher}/classes/{class} {
function isHeadMaster() {
return get(/databases/$(database)/documents/teachers/$(request.auth.uid)).data.isHM;
}
// HM can read, write classes
allow read, write: if isHeadMaster == true;
}
}
}
or else you need to create a different collection with firebase userid as the doc id and add isHM field if the user is headmaster, as follows:
collection (headmasters): {
doc (uid): {
----
isHM: true
----
}
}
and then add following rule:
service cloud.firestore {
match /databases/{database}/documents {
match /teachers/{teacher}/classes/{class} {
function isHeadMaster() {
return get(/databases/$(database)/documents/headmasters/$(request.auth.uid)).data.isHM;
}
// HM can read, write classes
allow read, write: if isHeadMaster == true;
}
}
}
To find more role based access rules, check this https://firebase.google.com/docs/firestore/solutions/role-based-access
Related
So I am trying to only allow a client to create a file it it has a certain name, but failing completely doing so.
// This is what I have tried so far:
https://firebase.google.com/docs/reference/rules/rules.List#hasAny
request.resource.__name__.split('/').hasAny('someDocument')
https://firebase.google.com/docs/reference/rules/rules.String#matches
request.resource.__name__.matches('.*someDocument')
And trying to compare the exact string from the rules-playground
request.resource.__name__="/databases/%28default%29/documents/someCollection/someDocument/someOtherCollection/thisDocument"
But all of them returned false for me.
service cloud.firestore
{
match /databases/{database}/documents
{
match /{document=**}
{
allow read: if true;
allow write: if {insert correct condition here};
}
}
}
You just need a wildcard to get the value of a document ID. Try the following:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /collectionName/{docId} {
allow read, write: if docId.size() > 5; // example
}
}
}
If you want to match collection name as well then you can add another wildcard like this:
match /{colId}/{docId} { ... }
Checkout the documentation for more information.
I have the following Firestore rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users {
match /{userId} {
allow get: if true;
allow list: if isQueryLimitRespected(20);
allow write: if false;
}
match /--stats-- {
allow read, write: if false;
}
}
}
}
But, as stated here,
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.
How can I deny access to --stats--? Is it impossible?
As I am not doing
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users {
match /{document=**} { <-------
allow get: if true;
allow list: if isQueryLimitRespected(20);
allow write: if false;
}
match /--stats-- {
allow read, write: if false;
}
}
}
}
I can re-organize my data structure in my database to:
/users (col)
/--private-- (doc)
/privateData (col)
/stats (doc)
/userId1 (doc)
/userId2 (doc)
And secure this subcollection as follows:
match /users {
match /{userId} {
allow get: if true;
allow list: if isQueryLimitRespected(20);
allow write: if false;
}
match /--private--/{document=**} {
allow read, write: if false;
}
}
My auth token includes a stores property. This property is an Array that includes ID's of all stores the user is allowed to access. For example:
stores: ['abc123', 'def456'].
Now, in my Firebase Realtime Database, I am saving store specific data like this:
{
"stores": {
"abc123": {
"data": "mydata"
},
"def456": {
"data": "mydata"
}
}
}
Users are allowed to access the realtime database data if their stores property array includes the storeID specific data they want to access.
What I would like my rules to look like:
{
"rules": {
"stores": {
"$store_id": {
".read": "auth.token.stores.includes($store_id)"
}
}
}
}
This is not possible. I get the following error:
Error saving rules - line 5: type error: function call on target that is not a function.
Is it possible to search trough token property arrays in the firebase rules or will I have to use an object? Thanks for any answers!
Firebase Realtime Database does not have any includes() to check if a value if present in an array or not. You can store the claims as a map instead of an array as shown below:
// custom claims
{
stores: {
store1: true,
store2: true,
store3: false
}
}
Then you can use the following rules to check if user has access to a particular store or no:
{
"rules": {
"stores": {
"$store_id": {
".read": "auth.token.stores[$store_id] == true"
}
}
}
}
You can use in operator from List
v in x
Check if value v exists in list x.
'a' in ['a','b'] == true
Sample Security Rules how it can be done
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
match /stores/{storeId} {
allow read: if isAuthenticated() && isUserAllowed();
}
function isAuthenticated() {
return request.auth != null;
}
function isUserAllowed() {
return request.auth.uid in resource.data.stores
}
}
}
}
Does anyone have their heads around this auth business? I have nurses (users) and patients and I want to give the nurses permission on some patient records.
Now (discarding everything i know about relational databases) I've put permissions under each patient, where each permission has ID of the user/nurse
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write:
if get(/databases/$(database)/documents/
patients/89QL8XXXXFf/
permissions/KZztXXXXXRf1)!=null;
}
}
}
..ok got it...
service cloud.firestore {
match /databases/{database}/documents{
//define the database variable, use it later as $(database)
match /patients/{patientId}{
//define variable patientId, use later as $(patientId)
allow read, write:
// if exists(/databases/$(database)/documents //WORKS
// /patients/$(patientId)
// /permissions/$(request.auth.uid))
if get(/databases/$(database)/documents //WORKS
/patients/$(patientId)
/permissions/$(request.auth.uid)).data.write == true;
//you have to get(...).data.myProperty
}
// match /{document=**} {
// allow read,write: if false;
// }
}
}
In my company documents, I have a reference field named owner, which points to a user document. In the rule, I am trying to check if the authenticated uid is the owner of the company:
match /companies/{companyId} {
allow read: if isOwner(resource.data.owner, request.auth.uid);
}
function isOwner(owner, userId) {
return path('/users/' + userId) == owner;
}
I tried many things but can't figure out how to make this work.
(I know using a string instead of a reference works, but I would rather use a reference)
When you construct the path, include this prefix: /databases/(default)/documents/. It's part of the full path to a document.
match /companies/{companyId} {
allow read: if isOwner(resource.data.owner, request.auth.uid);
}
function isOwner(owner, userId) {
return path('/databases/(default)/documents/users/' + userId) == owner;
}
The following should enable you to compare on the reference field.
match /companies/{companyId} {
allow read: if /databases/$(database)/documents/user/$(request.auth.uid) == resource.data.owner
}
Note: resource.data.owner NOT request.resource.data.owner
Why not making an ownerId field in the compagny document and check if the authenticated user uid is equal to the value?
service cloud.firestore {
match /databases/{database}/documents {
match /companies/{compagnyId} {
allow read: if isOwner()
}
}
}
function currentData() {
return resource.data
}
function isOwner() {
return currentData().ownerId == request.auth.uid
}