In Firestore, I have data within a sub-collection called ‘exclusiveB’, and a field of this collection’s documents is ‘creditCard’ which accepts boolean values.
Within the same Firestore referred to above, I have another sub-collection called ‘exclusiveA’.
I wrote a security rule for enabling the ‘exclusiveA’ collection to be read by the current user: if the current user has a uid, and if details concerning the current-user in sub-collection ‘exclusiveB’ confirm field-value ‘creditCard’ as false.
The result received is that Firestore has denied the read permission (despite the ‘creditCard’ field-value is false). Can anyone explain why and offer a solution please?
Please note that the parent collections ‘users’, ‘userA’, and ‘userB’ are empty except for a single value that confirms the iD of the document holding sub-collections, and, ‘exclusiveB’ documents feature a field that confirms the current-user’s uid, and another field that confirms the document’s iD.
Photos of the Firestore formation are as follows:
The security rules are:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users {
// if a rule isn't specified, Firestore denies by default
allow read;
}
match /users/{docId}/userA {
allow read;
}
match /users/{docId}/userB {
allow read;
}
match /users/{docId}/userA/{docId2}/exclusiveA/{docId3} {
// allow read if user: (1) has a uid, (2) has creditcard = false
allow get: if request.auth.uid != null && get(/databases/$(database)/documents/users/{docId}/userB/{docIdB}/exclusiveB/$(request.auth.uid)).data.creditCard == false;
}
match /users/{docId}/userB/{docId2}/exclusiveB/{xcluB} {
allow get: if resource.data.uid == request.auth.uid;
}
match /users/{docId}/userA/{docId2}/otherDetails/{id} {
allow read: if request.auth.uid == resource.data.id;
}
match /users/{docId}/userB/{docId2}/otherDetails/{id} {
allow read: if request.auth.uid == resource.data.id;
}
}
}
The Flutter code for the Firestore query at the user-interface level is:
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart' as auth;
import 'package:cloud_firestore/cloud_firestore.dart';
class StreamResults extends StatefulWidget {
@override
State<StreamResults> createState() => _StreamResultsState();
}
class _StreamResultsState extends State<StreamResults> {
final _auth = auth.FirebaseAuth.instance;
final _dB = FirebaseFirestore.instance;
final _dataSource = FirebaseFirestore.instance
.collection(
'users/l9NzQFjN4iB0JlJaY3AI/userA/2VzSHur3RllcF5PojT61/exclusiveA');
String? vehicleMake, vehicleTitle, currentUserID, pCurrency, currency, uid, docRefID;
int? maxSpeed, pullStrength;
void getIdentity() {
currentUserID = _auth.currentUser!.uid;
print('The current user's ID is: $currentUserID.');
}
Future<void> matchDetails() async {
final exclusiveBcollection = await _dB.collection('users/l9NzQFjN4iB0JlJaY3AI/userB/mv6YgmAfIDEkUNmstrzg/exclusiveB')
.where('uid', isEqualTo: currentUserID).get().then((QuerySnapshot querySnapshot) {
querySnapshot.docs.forEach((doc) {
currency = doc['currency'];
uid = doc['uid'];
docRefID = doc['docRefID'];
});
});
}
@override
void initState() {
getIdentity();
matchDetails();
super.initState();
}
@override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: _dataSource.where('preferredCurrency', isEqualTo: currency).snapshots().distinct(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
CircularProgressIndicator();
} else if (snapshot.hasData) {
final retrievedData = snapshot.data!.docs;
for (var specific in retrievedData) {
maxSpeed = specific['topSpeed'];
vehicleTitle = specific['vehicleName'];
pullStrength = specific['horsePower'];
vehicleMake = specific['vehicleBrand'];
pCurrency = specific['preferredCurrency'];
print('The vehicle's maximum speed = $maxSpeed.');
print('The vehicle's pulling strength = $pullStrength bph.');
print(
'The vehicle's brand is $vehicleMake, and its model-name is $vehicleTitle.');
print('The preferred currency = $pCurrency.');
}
}
return Column(
children: [
Text('The vehicle's maximum speed = $maxSpeed.'),
Text('The vehicle's pulling strength = $pullStrength bph.'),
Text(
'The vehicle's brand is $vehicleMake, and its model-name is $vehicleTitle.'),
],
);
});
}
}
With thanks.
2
Answers
In the end, I found that calling exists(/databases/) on the data relevant to the get(/databases/) call, first, activated the perfectly written get(/databases/) call. It seems like a fault, but another way of looking at it could be to say that Firestore will accept a get(/databases/) call if it is aware of the relevant data ahead of the get(/databases/) call.
This query:
And this query:
Both attempt to
list
documents the subcollection, from the perspective of security rules. But your rules only allow individual documentget
operations:If you want to query (
list
) a collection, you will need to add that permission. You can do that by specifyinglist
, or just usingread
, which is a combination ofget
andlist
, as discussed in the documentation.