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.
Feedback from a Firebase representative (that I read today) basically amounts to an admission that Firestore's get(/databases/) function is poorly designed, and, that in the way I use Firestore, which is probably the way that you use it too, the get(/databases/) function will never ever work!
The explanation given was that the $(request.auth.uid) aspect of the get(/databases/) function seeks a document within the relevant collection that has an identity that is equal to the unique-identity of the current user.
When creating new documents for Firestore, the identity of new documents is generated randomly (by my choice), and this seems the best way and the right way, especially considering privacy and data protection.
The random creation of a Firestore document's identity means that Firestore would never ever create a document that has the same identity as the current user, which means that the get(/databases/) call will never find a document with the same identity as the current user, which means that the get(/databases/) call will always return 'false' meaning it would be consistently fruitless and clients like myself would consistently find it not-working.
The official response,
"The call to get(/databases/$(database)/documents/users/$(request.auth.uid)) will load a single document in the Cloud Firestore database (not a collection, or a sub-collection). The given path is an "absolute path" and must begin with /databases/$(database)/documents. The value of $(request.auth.uid) will be replaced by the UID of the currently-logged in user. For example, if the UID of the currently-logged in user is "user123" then the get(...) call will load the document at the path users/user123."
Source: https://groups.google.com/g/google-cloud-firestore-discuss/c/WJ7oZ793260
Solution: We need to request an update of Firestore's technology for changes to the get(/databases/) function, so that its successor does what we have expected the get(/databases/) function to do. Smart and convenient functionality would be an asset to the Firestore product and to its clients.
Dear Firebase Team, please take note of the above.