I have a question about Firebase Database security rules.
I want only some documents to be reflected in the application.
I tried to create a "published" field in the database, and determine if the value is true or false.
【Flutter/Dart code】
class _HomePageState extends State<HomePage> {
late StreamSubscription<QuerySnapshot> subscription;
late List<DocumentSnapshot> snapshot;
CollectionReference collectionReference =
FirebaseFirestore.instance.collection('article');
passData(DocumentSnapshot snap) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => PostPage(snapshot: snap),
),
);
}
【In the rules of Cloud Firestore】
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /article/{document=**} {
allow read:if resource.data.published == 'true';
}
}
}
【In the Cloud firestore】
collection → article
field → published : false
In the above case, is it recognized that the information in document is not reflected in the application?
I checked with the Xcode simulator, but it is also reflected.
I assume that your code tries to read all documents from collectionReference. If that is the case, then your rules will reject that read, because they say that any user is only allowed to read published documents.
The key to understand here is that rules are not filters themselves, but instead merely ensure that any operation only tries to read documents that it is allowed to read.
So for your read operation to work, you should query to only request documents that have been published.
That'd be something like:
collectionReference.where("publish", isEqualTo: "true");
Note that I pass 'true' as a string here, since that's what your rules also check for. It is more custom to store true/false as actual boolean values, so I'd recommend doing that if possible.
Related
I am struggling with Firestore rules to allow access to some resources in a subcollection.
I have some requests documents, that may present a sub-collection named status. My current rules are something like that:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// I use it to check if user is signed in
function userSignedIn() {
return request.auth != null;
}
match /requests/{requestId} {
// I use it to check if user is the owner of the request
function userCanAccess () {
return userSignedIn() && request.auth.uid == get(/databases/$(database)/documents/requests/$(requestId)).data.userId;
}
// allow read to logged user who own request
allow read, update: if userCanAccess();
// anyone can create a new request
allow create: if true;
// no one can delete the request
allow delete: if false;
match /status/{statusId} {
// allow read and update of status to logged user who own request
allow read, update: if userCanAccess();
// anyone can create the status
allow create: if true;
// no one can delete the status
allow delete: if false;
}
}
}
}
My very first way to check if user had access was by request.auth.uid == resource.data.userId, however it only works for requests documents, not for status documents since they do not have the userId field.
I'm using flutter to make the request with the code:
FirebaseFirestore.instance
.collection('requests')
.where('userId', isEqualTo: user.uid)
.orderBy('requestedOn', descending: true)
.snapshots()
.listen((snapshot) {
// Here I read requests and their status
});
However I'm getting Error: [cloud_firestore/permission-denied] Missing or insufficient permissions.. How can I solve (or debug) it? Using rules playground in firestore, with the exact same uid, I am able to complete the reading.
I'm wondering if the get() call may be causing problems. Try this:
function userCanAccess () {
return userSignedIn() && request.auth.uid == requestId.data.userId;
}
Based on the documentation, Firebase Storage Rules version 2 can be writen with granular operations.
A read operation can be broken into get and list. A write rule can be
broken into create, update, and delete.
service firebase.storage {
match /b/{bucket}/o {
// A read rule can be divided into read and list rules
match /images/{imageId} {
// Applies to single document read requests
allow get: if <condition>;
// Applies to list and listAll requests (Rules Version 2)
allow list: if <condition>;
// A write rule can be divided into create, update, and delete rules
match /images/{imageId} {
// Applies to writes to nonexistent files
allow create: if <condition>;
// Applies to writes to existing files
allow update: if <condition>;
// Applies to delete operations
allow delete: if <condition>;
}
}
}
}
Supose I have some rules like this:
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /randomFiles {
allow get, create: if true;
allow list, update, delete: if false;
}
}
}
Based in the official documentation, with this rules I can:
Upload new files, if don't have any with the same id. (writes to nonexistent files)
Get a single document.
I can't update, delete or list any existing file.
These assuptions are right?
Because now, I have a strange behavior:
Using this rules, I can override existing files. This is a bug?
In my client if I upload a file and after that upload a new file using the same reference, the rules agree with that. Thus, override the first uploaded file.
The Minimum code to reproduces the aplication using Flutter 2.0 (stable channel) and firebase_storage 8.0.0
import 'dart:io';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
const FILES_DIR = "filesDir";
void main() async {
//
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
await uploadFiles();
}
Future<void> uploadFiles() async {
//
var firstfile = File('lib/someimage.png');
var secondFile = File('lib/otherImage.jpeg');
Reference dirRef = FirebaseStorage.instance.ref(FILES_DIR);
var firstTask = await dirRef.putFile(firstfile);
assert(firstTask.state == TaskState.success);
var secondTask = await dirRef.putFile(secondFile);
assert(secondTask.state == TaskState.success);
}
class MyApp extends StatelessWidget {
//
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Container(
child: const Center(child: Text("Minimum Flutter aplication for test purposes")),
),
);
}
}
The update permission only applies to updates to metadata. It's hinted at in the reference documentation here:
The write method covers all requests where file data or metadata is written, including file uploads, file deletes, and file metadata updates.
But that should definitely have been more explicit, so I filed a bug to get that updated.
You can implement your use-case already, but you'll do that through the create rule:
allow create: if resource == null
Let me know whether that works or not.
My app receives a webhook from a 3rd party service, telling it that data is ready to be queried.
The webhook payload includes:
UserId
ObjectId of the object whose data is ready.
In order to query the data, I need to get an access token:
const { accessToken } = await db
.collection('users').doc(userId)
.collection('objects').doc(objectId)
.get();
// then I can:
fetchUpdatedData(objectId, accessToken)
However, I have rules in place to require that users' data may only be accessed by the user:
# `firestore.rules`
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Reject by default
match /{document=**} {
allow read, write: if false;
}
// Users can edit their own document
match /users/{userId} {
allow read, update, delete: if request.auth.uid == userId;
allow create: if request.auth.uid != null;
}
// Users can manage their subcollections
match /users/{userId}/{document=**} {
allow read, write: if request.auth.uid == userId;
}
}
}
What's the typical way to do this?
It's not possible to use security rules to limit queries coming from backends like Cloud Functions that use any of the server SDKs. Server SDKs initialize with a service account, which always bypass security rules. You're going to have to duplicate the relevant checks from the rules in your backend code to check if the query should be done on behalf of the user.
I'm having a bit of trouble trying to configure my Cloud Firestore rules.
I'm trying to access a field inside a document, inside a collection... Like this
Future<void> fetchAndSetProducts([bool filterByUser = false]) async {
final filterString = filterByUser
? Firestore.instance.collection('products').getDocuments()
: Firestore.instance
.collection('products')
.where('creatorId', isEqualTo: userId)
.getDocuments();
try {
QuerySnapshot prodSnap = await filterString;
if (prodSnap == null) {
return;
}
'creatorId' is a field within database/products/{productId}
I want to distinguish between users and only allow them to update, and delete files they've created within database/products/... , but I also want them to be able to view all the documents inside of /products/...
the bool I have set up for fetchAndSetProducts is what I'm hoping to use to filter some of the information app side, e.g. only allowing using to view certain products (ones containing their userId). I'm not sure if I also needs to set up indexing on "products", but I have done already just in case..
So, I want to lock down all files that weren't created by a user.
I thought it would be something like this:
service cloud.firestore {
match /databases/{database}/documents {
match /products/{productId}/documents{
allow read: if resource.data.creatorId == request.auth.uid;
}
}
}
buuut that doesn't work, and nor does my app-side code for filtering by user..
If you want to match all documents in the products collection, it would look like this:
service cloud.firestore {
match /databases/{database}/documents {
match /products/{productId} {
allow read: if resource.data.creatorId == request.auth.uid;
}
}
}
Notice that I removed the "documents" from the end of the second match.
I am trying to get the data of a document in Firebase. I am using this function:
DocumentSnapshot docRef =
await Firestore.instance.collection("products").document("SF").get();
print(docRef.exists);
docRef.exists returns "false" even if the document is exisiting for sure.
I think it has something to do with the auth flow and the system does not recognize the logged in user.
print(FirebaseAuth.instance.currentUser());
results in Instance of 'Future < FirebaseUser>'.
Any idea how to solve the problem?
Best regards
EDIT:
Here are my rules from firebase:
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write;
}
}
}
The way you define the security rules is not correct. Change your security rules.
match /products/{document=**} {
allow read, write: if true;
}