Firebase Storage Rules strange behavior - firebase

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.

Related

Firebase security rules (.read)

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.

Firestore rule to access subcollection raising Missing or insufficient permissions

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;
}

'permission denied' error with random getDownloadURL flutter/firebase

I'm writing an app which has user profiles in it. The user is allowed to take a photo or choose from their photo library.
Once the photo is taken, I use this function:
Future<String> uploadFile(File image, String directory, String imageName) async {
assert(image.existsSync());
StorageReference storageReference = FirebaseStorage.instance.ref().child('$directory/$imageName');
StorageUploadTask uploadTask = storageReference.putFile(image);
await uploadTask.onComplete;
var dowurl = await storageReference.getDownloadURL();
return dowurl.toString();
}
to upload the image to firebase. The image appears in the Firebase storage area fine, and the URL is generated, and added to the user's profile as I've defined in the database.
The rules of firebase are set as follows:
Database
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth != null;
}
}
}
Storage
rules_version = '2';
service firebase.storage {
match /b/myappname.com/o {
match /{allPaths=**} {
allow read, write: if request.auth != null;
}
}
}
which I believe should allow access for any user as long as they're authenticated (they are!)
I'm uploading the URLs generated by uploadFile() to the users profile, but about 1/6 of them gives a URL which, when accessed gives the following error:
{
"error": {
"code": 403,
"message": "Permission denied. Could not perform this operation"
}
}
I'm at a loss as to what is going on here, as the rest of the code works fine, and the 'choice' of file which causes the problem seems completely random.
The URLs generated have different token IDs at the end of each, but I'm not generating them - they're coming from the getDownloadURL() function. I'm keen to stress that the process is the same for each file, but for some reason, randomly, one of the generated URLs doesn't work.
Any help would be much appreciated.

Firebase / Cloud Firestore / Rules Setup (read, write, update, delete)

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.

Improve Firestore rules

I have been trying to improve the Firestore rules that secure the database for a few days now. I only seems to lock everyone out with every edit. The rules I use now are the basic rules found in the Firestore documentation. Which are:
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth.uid != null;
}
}
}
But I would like to extend the security a bit to tighten up the edit rules. My database looks like this:
Users (collection) > User (document) > User specific data
What I would like to have is that every authenticated user can read all the data, but only the user a document belongs to (by unique user id) may edit/add/delete their data.
I hope one of you could point me in the right direction, as I seem to not get any wiser from the official documentation.
Update: How I integrated Firestore in my Android app.
user = FirebaseAuth.getInstance().getCurrentUser();
db = FirebaseFirestore.getInstance().collection("users");
CollectionReference colRef = db.document(user.getUid()).collection("watched");
colRef.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
ArrayList<Movie> tempItems = new ArrayList<>();
for (DocumentSnapshot document : task.getResult()) {
// Handle data
}
} else {
Log.d(TAG, "Error getting documents: ",task.getException());
}
}
});
I apparantly read the documentation about the resource.data wrong. I had to add the author_id field myself. I did not know this, but once I added this it worked like a charm!
You can write a rule to make sure that the uid of the requesting user matches the author_id field of the document:
service cloud.firestore {
match /databases/{database}/documents {
match /Users/{User} {
allow read: if request.auth.uid != null;
allow create, update, delete: if request.auth.uid == resource.data.author_id;
}
}
}

Resources