I have firestore DB like below, uid field contains, uid of the authenticated user who has added the document
security role:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /crush/{doc} {
allow read: if request.auth != null && request.auth.uid == resource.data.uid;
allow write : if request.auth != null;
}
}
}
however, in the actual flutter App, only DB write is working, read fails with below error
/Firestore( 6538): (24.0.1) [Firestore]: Listen for Query(target=Query(crush order by name);limitType=LIMIT_TO_FIRST) failed: Status{code=PERMISSION_DENIED, description=Missing or insufficient permissions., cause=null}
which makes me believe request.auth.uid is blank for incoming read
the program looks something like below
# landing.dart
# Authentication class is helper for FirebaseAuth.instance
import 'home.dart';
class Landing extends StatefulWidget {
const Landing({Key? key}) : super(key: key);
@override
State<Landing> createState() => _LandingState();
}
class _LandingState extends State<Landing> {
dynamic authStream = Authentication().authState;
@override
Widget build(BuildContext context) {
return StreamBuilder(
stream: authStream,
builder: (BuildContext context, AsyncSnapshot<AppUser?> snapshot) {
if (snapshot.data != null) {
return Home(snapshot.data);
}
else {
return SignIn(); //via google
}
});
}
}
# home.dart
# Database class is helper for FirebaseFirestore.instance
import 'package:flutter/material.dart';
import 'package:password_safe/models/crush.dart';
import 'package:password_safe/models/user.dart';
import 'package:password_safe/services/authentication.dart';
import 'package:password_safe/services/database.dart';
class Home extends StatefulWidget {
AppUser? user;
Home(this.user, {Key? key}) : super(key: key);
@override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
dynamic streamCrush = Database().getCrushSnapshot;
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
provideInput(context, widget.user);
},
child: Icon(Icons.add),
),
body: StreamBuilder(
stream: streamCrush,
builder: (BuildContext context, AsyncSnapshot<List<Crush>> snapshot) {
if (snapshot.hasError) {
return Center(child: Text('Error Ocurred'));
}
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
}
return myList(snapshot.data!);
},
),
);
}
}
\\ database.dart
import 'dart:developer' as developer;
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:password_safe/models/crush.dart';
class Database {
late FirebaseFirestore _db;
late CollectionReference crushCollection;
final String _logName = "Database";
static final Database _instance = Database._internal();
Database._internal() {
try {
_db = FirebaseFirestore.instance;
crushCollection = _db.collection('crush');
} catch (e) {
// do something
}
}
factory Database() {
return _instance;
}
Future<void> addCrush(Crush crush) async {
await crushCollection
.add(crush.toMap())
.then(
(value) => developer.log('adding crush to backend', name: _logName),
)
.catchError(
(error) => developer.log('Error while adding crush',
name: _logName, error: error),
);
}
Stream<List<Crush>> get getCrushSnapshot {
return crushCollection
.snapshots()
.map((event) => _crushListFromSnapshot(event));
}
List<Crush> _crushListFromSnapshot(QuerySnapshot snapshot) {
return snapshot.docs.map((e) {
Map<String, dynamic> data = e.data() as Map<String, dynamic>;
data.addAll({'id': e.id});
return Crush.fromMap(data);
}).toList();
}
Future<void> deleteCrush(Crush crush) {
return crushCollection
.doc(crush.id)
.delete()
.then((value) =>
developer.log('deleting crush from backend', name: _logName))
.onError((error, stackTrace) => developer.log(
'error occured while deleting data from crush',
name: _logName,
error: error,
stackTrace: stackTrace));
}
}
pubspec.yaml
dependencies:
cloud_firestore:
google_sign_in: '^4.5.1'
firebase_auth:
firebase_core:
flutter:
sdk: flutter
CodePudding user response:
Your Query constraints do not match security rules constraints. You are trying to get /crush/*
(as per in your database query) but you only have access to /crush/{doc}
as per your Firestore security rule.
The query fails even if the current user actually is the author of every document. The reason for this behavior is that when Cloud Firestore applies your security rules, it evaluates the query against its potential result set, not against the actual properties of documents in your database. If a query could potentially include documents that violate your security rules, the query will fail.
So you need to filter your database query to match the uid field of the document with auth.uid. Something like this :
var user = FirebaseAuth.instance.currentUser;
db.collection("crush").where(“uid”, "==", user.uid).get()
You can read more about securing and querying documents based on auth.id here.