Flutter Firebase Upload with Authentication - firebase

I am trying to upload a profile photo as a logged in user with the following however I get error
E/flutter (32619): [ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: [firebase_storage/unauthorized] User is not authorized to perform the desired action.
My code
User? user = await authenticationService.getCurrentUser();
// Create a Reference to the file
Reference ref =
FirebaseStorage.instance.ref().child(user!.uid).child('/profile.jpg');
final metadata = SettableMetadata(
contentType: 'image/jpeg',
customMetadata: {'picked-file-path': s!.path});
UploadTask uploadTask = ref.putFile(File(s.path), metadata);
var result = await uploadTask;
print(result);
My rule
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read;
allow write: if request.auth.uid == userId
&& request.resource.contentType.matches('image/.+');
}
}
}
What am I missing here ?

I understand that you want to upload a file to the following reference
FirebaseStorage.instance.ref().child(user!.uid).child('/profile.jpg');
and check, in the security rules, that the id of the user executing the upload corresponds to the value of user!.uid.
In your security rules, you have a check as request.auth.uid == userId but nowhere in the rules you define what is userId. You need to use a wildcard, as follows:
service firebase.storage {
// The {bucket} wildcard indicates we match files in all Cloud Storage buckets
match /b/{bucket}/o {
match /{userId}/{imageId} {
allow read;
allow write: if request.auth.uid == userId
&& request.resource.contentType.matches('image/.+');
}
}
}
If you only upload images named profile.jpg in this "folder", you could add a check like imageId == 'profile.jpg' but you could also define the path as:
match /{userId}/profile.jpg {
allow read;
allow write: if request.auth.uid == userId
&& request.resource.contentType.matches('image/.+');
}

Related

How to manage rights in firebase to allow differents users to read/upate/write?

I've a firestore database and I now need to add a new collection.
Each entry of this collection should contain:
Which userId is the owner(field admin)
Which userId has been allowed to edit this element(field writer)
Which userId has been allowed to only read(field reader).
I'm currently only at the first step, and already strugling:
I was hoping to be able to query my collection( /trips/) and get only the one that I'm allowed to access, but I get an error:
FirebaseError: Missing or insufficient permissions.
Here is my rules file:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false;
}
match /users/{userId} {
allow read, update, delete: if request.auth != null && request.auth.uid == userId;
allow create: if request.auth != null;
}
match /trips/{trip} {
allow read, update, delete: if request.auth != null && request.auth.uid == resource.data.admin;
allow create: if request.auth != null;
}
}
}
So my questions:
Is this the correct way of managing resource that must be acceeded by multiple people(meaning, I cannot just have the userId in the path since there are multiple users)
How should I query only the documents list that I'm allowed to see?
Thank you very much for your help
As you will read in the doc, "All match statements should point to documents, not collections".
With
service cloud.firestore {
match /databases/{database}/documents {
match /trips {
// ....
}
}
}
you don't point to a document. You should use a wildcard to point to any document in the specified path, as follows:
service cloud.firestore {
match /databases/{database}/documents {
match /trips/{trip} {
// ....
}
}
}
Therefore the following should correctly implement your requirements:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /trips/{trip} {
allow read: if request.auth != null &&
(request.auth.uid == resource.data.admin
|| request.auth.uid == resource.data.writer
|| request.auth.uid == resource.data.reader
);
allow update: if request.auth != null &&
(request.auth.uid == resource.data.admin
|| request.auth.uid == resource.data.writer
);
allow create: if request.auth != null;
}
}
}
Then, for the two questions:
Is this the correct way of managing resource that must be acceeded by multiple people (meaning, I cannot just have the userId in the path
since there are multiple users)
If the admin, writer and reader are specific for each document, yes this is the correct way. If those roles would be more global (e.g. all the trips to Europe can be edited by the same user), you could use a role based approach with Custom Claims.
How should I query only the documents list that I'm allowed to see?
It is important to note that rules are not filter. So your query for getting docs needs to be aligned with the rules. In your specific case, you could have an additional field of type Array which contains three values; the uids of the admin, writer and reader, and use the array-contains operator. Something like:
const user = firebase.auth().currentUser;
const query = db.collection("trips").where("authorizedReaders", "array-contains", user.uid);
match /{document=**} {
allow read, write: if false;
}
You don't need the above code as it will apply to all routes of the database, because of the above line you are getting the below error as it does not allow you to read and write to the database
FirebaseError: Missing or insufficient permissions.
Now, if you want to assign privileges to users then you should add the Role field to users collections which would have a value such as Admin, Editor, Reader
Then, you can check in routes something like below
match /users/{userId}/trips/{tripId} {
allow read, delete: if request.resource.data.role == "Admin";
allow create, update: if request.resource.data.role == "Admin || request.resource.data.role == "Editor";
}
If you want to know more about how to create a route check out this video for the best explanation

Firebase Security Rules - Undefined Resource Error

I have collection Users which has documents with the same Id as the user.uid. I want to allow logged in users to create documents and only update, delete and read their documents which is specified with the same UID as mentioned.
I tried this but it keeps failing.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{document} {
allow create, : if request.auth != null;
allow update, delete, read: if request.auth != null && request.auth.uid == request.resource.data.UID;
}
}
}
In this code i am trying to compare the uid of the logged user with a document field called UID
Have a look at the documentation, it shows exactly the response to your question.
service cloud.firestore {
match /databases/{database}/documents {
// Make sure the uid of the requesting user matches name of the user
// document. The wildcard expression {userId} makes the userId variable
// available in rules.
match /users/{userId} {
allow read, update, delete: if request.auth != null && request.auth.uid == userId;
allow create: if request.auth != null;
}
}
}
The key point is to use the {userId} wildcard expression to match the ID of the document being read/updated/deleted with the uid of the user (i.e. request.auth.uid);

Firebase Security Rules: Allow Read + Write to everything except a single collection

I need to restrict read access to a single collection, keys, in firestore to only authenticated users with certain uids. All other authenticated users can view any other collection/document in the database.
First try is this set
service cloud.firestore {
match /databases/{database}/documents {
match /{col}/{doc}/keys/{key} {
allow read: if request.auth.uid == "A" ||
request.auth.uid == "B" ||
request.auth.uid == "C";
}
match /{document=**} {
allow read, write: if request.auth.uid != null && !("key" in resource.data);
}
}
}
Unfortunately users get this error message when trying to create a new collection in /{col}/{doc} - i.e. a collection sibling to 'keys'
io.grpc.StatusException: PERMISSION_DENIED: Missing or insufficient permissions.
If this were Java I would just run an indexOf on the path, deny if it contained 'keys' and allow otherwise. However, according to Firebase Rules Reference, a Path object does not support indexOf.
Here is the code the results in the error. In this case, the "boxes", and "CFI" collections do not yet exist in FireStore, so they would normally be created when the code runs.
final DocumentReference firstDocRef = firestore.document("test/laksdfjasdf/programCounters/hjg65ffgjj");
CF cfNew = Utils.getCF();
firestore.runTransaction(transaction -> {
DocumentSnapshot snapshot = transaction.get(firstDocRef);
HashMap<String, Object> updatedMap = new HashMap<>();
updatedMap.put("num", cfNew.getNum());
transaction.update(firstDocRef, updatedMap);
Box newBox = createNewBox(cfNew);
final DocumentReference newBoxRef = firestore.collection(
"test/laksdfjasdf/boxes")
.document("" + newBox.getBoxNumber());
transaction.set(newBoxRef, newBox);
final DocumentReference newCFIRef = firestore.collection("test/laksdfjasdf/CFI")
.document("" + cfNew.getCreated().hashCode());
transaction.set(newCFIRef, cfNew);
return newBox;
}).addOnSuccessListener(box -> {
Log.d(TAG, "SUCCESS");
}).addOnFailureListener(e -> {
Log.d(TAG, "Failure");
});

How to set Firestore security rules? resource.data: Null value error

I need some help making my security rules for firestore work.
These are my firestore rules:
service cloud.firestore {
match /databases/{database}/documents {
match /orders/{orderID} {
allow read, update: if request.auth.uid == resource.data.buyerId || request.auth.uid == resource.data.sellerId;
}
}
}
my orders collection:
orders: {
sellerId: 'some-id',
createdAt: timestamp,
buyerId: 'some-id'
}
It should return all documents from orders collection which has either buyerId or sellerId equal to authorised user (request.auth.uid).
but the above rule is not working as expected.
firestore collections screenshot
firebase simulator output
That error message is suggesting that the requested document was not actually present in the database. You entered "orders/{orderId}", which looks like you put a wildcard in the Location field in the simulator. That's not going to work. You need to enter the path to an actual document that exists if you want to test your rule that uses its field values.
resource.data: Null - this error happens when you try to create a new entity.
Split write rule, on create and update.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /user/{userId} {
allow read: if request.auth.uid == userId;
function authed() {
return request.auth.uid == userId;
}
allow create: if authed() && request.resource.data.keys().hasOnly(['name']);
allow update: if authed() && request.resource.data.diff(resource.data).changedKeys().hasOnly(['name']);
allow delete: if authed();
}
}
}

Firestore security rule not working

I have the following rule in my Firestore
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId}/{documents=**} {
// Only the authenticated user who authored the document can read or write
allow read: if request.auth.uid == userId;
allow write;
}
}
}
which doesn't seem to work and i am using Rest API to get the data
For authentication I call:
https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key=[API_KEY]
Once authenticated we get the idToken and pass as Authorization header for the next URL
https://firestore.googleapis.com/v1beta1/projects//databases/(default)/documents/users
The users collection has the id as the document name and the value is just a bunch of dummy keys.
When I run the client the error I get is
{u'status': u'PERMISSION_DENIED', u'message': u'Missing or insufficient permissions.', u'code': 403}
If i hardcode the value of the userid it works. So the value returned in {userid} does not seem to match the UID for some reason.
Can someone please help decode why this is happening?
Thanks
Rams
you don't need the document=** selector
service cloud.firestore {
match /databases/{database}/documents {
// dissallow all access
match /{documents=**} {
allow read, write: if false;
}
// Make sure the uid of the requesting user matches name of the user
// document. The wildcard expression {userId} makes the userId variable
// available in rules.
match /users/{userId} {
allow read, update, delete: if request.auth.uid == userId;
allow create: if request.auth.uid != null;
}
}
}
https://firebase.google.com/docs/firestore/security/rules-conditions

Resources