Adding a token to Firebase Storage for group security - firebase

For our app we need to be able to offer groups access to files. Each user can have a large number of groups so it does not make sense to use the "custom token" solution (which is anyways very awkward.
As I discovered, Firebase is very limited with its Storage security rules. The main issue is that we keep Group definitions in Firestore which the Storage security rules do not have access to.
To overcome this we decided to include in the metadata of each uploaded file a "token" which anyone in the group has access to. When they download a file, they need to provide this token in the request params (e.g. /groups/xxx/filename.jpg?token=abc).
So I went ahead and wrote these rules:
match /groups/{groupId}/{filename} {
allow read: if request.auth != null && request.params.token == resource.metadata.token;
allow write: if request.auth.uid == userId
&& request.resource.size < 1 * 1024 * 1024
&& request.resource.contentType.matches('image/.*')
&& (resource == null || request.resource.contentType == resource.contentType)
&& imageId.size() < 32
;
}
But when I run it in the simulator I get the error: "Error: simulator.rules line [23], column [43]. Property params is undefined on object." which points to the rule with "request.params.token"
The documentation specifically states that we have access to the params object from the request object: https://firebase.google.com/docs/storage/security/secure-files?authuser=0#request_evaluation

Due to the severe limitations of Firebase being able to query Firestore data in Storage and the incorrect documentation regarding request.param not being available, we had to use a different approach.
We decided to use the following URL when querying files of a group:
/groups/{groupId}/{token}/{filename}
The only security requirement is for the user to be logged in. Since the above token is a secret and only available to group members, we find it to be pretty secure. We do not allow listing of directories so it is not possible to simply list the /groups/{groupId} to find any tokens.
Note: another optimization could be to not include the {groupId} in the path but we felt it was better to include for debug and management purposes.
If anyone feels this is an insecure way, please let us know on the comments!
Thanks!

It sounds like you're trying to proxy some data from Cloud Firestore into a Cloud Storage request for use in security rules. This is currently not feasible. (On top of that request.params currently refers to APIs that aren't documented, so it can't really be used. I've brought this up with the team, and we feel request.params should likely be removed from security rules docs.)
If you want to assign a Firebase Auth user some group identity that can be verified across products (Firestore, Storage, and Realtime Database), you can use custom claims for this. It will require some work on your backend to assign, directly to the user account, the value(s) you want to check. If you do it correctly, the values you set in that JSON blob will show up in request.auth.token in both Firestore and Storage security rules.

Related

Why do my Firebase Realtime database rules break my HandleValueChanged?

Good morning!
I am working on a Firebase project in my Unity game and am working on securing the data in the database, however whenever I update the rules, it breaks my (otherwise working) handlevaluechanged function.
The data structure looks like this:
User_List ->
Firebase_User_ID ->
Health: 100,
Name: Foo,
etc ....
I want the rules (at a minimum, I'll add validation later) for this data to be:
"USER_LIST":
{
"$UID" :
{
".read": "auth.uid === $UID",
".write" : "auth.uid === $UID",
}
},
Locally in the game, I get a reference with this call:
FirebaseDatabase.DefaultInstance.GetReference("USER_LIST").Child(USER_ID).ValueChanged += HandleValueChanged;
If I set the rules to:
".read": true,
".write" : true
everything works as expected. The client can update the database, and handlevaluechanged does its job of keeping the local data synced. When I switch the to the rules above where I verify the auth ID, the client still works. It can update the database no issues, provided the correct user ID is signed in. However, my Handlevaluechanged gets a permission denied error from firebase, as if the handlevaluechanged listener does not supply the proper user ID when it attempts a read from the DB.
I'm puzzeled because the rules allow me to get the reference in the first place, and update the database from the client, but I can't update the client from the database? What am I missing?
I have also tried to GetReference at the USER_LIST node instead of the USER_ID node with the same result.
Any insight would be greatly appreciated! Thank you in advance.
After ignoring this issue for a while and working on other bugs, I discovered the true issue and wanted to post in case anyone else finds themselves in this pickle.
I had a Unity scene dedicated to signing in users that created the authenticated Firebase user, then jumped to a new scene. In that new scene, I searched for the local user object created by when the user logged in, and then I set up the database reference and the handleValueChanged listener.
I think the issue was that searching for the user was being done asynchronously (I have not done more research to confirm this, but my testing in this specific case seems to suggest this is the case), so the task was started, and while it was looking for the user credentials, it created the DB reference and added a listener with a null user. Afterwards, the task completes and the user is no longer null, but the listener is already listening.
Then, if I did a database read or write, the call for read or write was from an authenticated user and would execute provided the RTB rules were satisfied. Successful writes would trigger the handlevaluechanged listener and draw up a permission denied error due to the listener not having an authenticated user ID associated with it (again, I reached this conclusion based on observation and testing with this specific project, not research).
Simply adding the DB reference to my local user object after it was created instead of searching for the user and then establishing the reference solved all of my issues.
I hope this helps!

firestore security rules request.auth.uid == userId kept returning 'Simulated read denied'

I'm trying to get request.auth.uid == userId to be tested but I'm getting 'Simulated read denied'.
I checked all of the basic questions before inputting this issue -
Did you deploy security rules?
I deployed the rules via the fireconsole (clicking the publish button and waiting for a min).
Did you have loggedin using Firebase Authentication?
I haven't implemented this work, I'm trying to test the security rules via using simulator in console.
I tried basic rule below and it works fine.
if request.auth.uid != null;
Here are the screenshots of my testing -
When using the security rules simulator, you need to enter the exact, full path of the document to read. You can't use wildcards. Right now, you're trying to using a wildcard in the document path: "/users/{userId}" This isn't going to work. This makes the userId variable in your rules become literally the string "{userId}". What you need to do instead is paste the actual ID of the document you want to test for reading into the form. This is going to be the UID starting with "JoF".
BTW: You are not required to deploy rules in order to test them in the simulator. You can choose to deploy them only after you've tested them.

Firebase- how to restrict access permission in child nodes?

How could I set rules to restrict someone access child node form machine?
for example :
00041802000001,00041802000002,00041802000003,...etc
My rules are:
{
"rules": {
"machine":{
"$machine":{
".read": "data.child('giftIn').val() >10"
}
}
}
}
But it don't work, permission problem happened:
Listener at /machine failed: permission_denied
what rules could make someone access 00041802000002 only?
You're granting access to each specific individual machine. This means that you can access each machine once you know its key (e.g 00041802000002).
But you're then trying to read all machines: /machine. Since you don't have read permission there, the read is rejected.
You will either need to grant access to read all machines, or read a specific machine.
If you were expecting that reading /machine would filter the child nodes to only return the ones with a giftIn value of greater than 10, you're unfortunately in for a disappointment. Firebase security rules cannot be used to filter out data. The reason for this is that the rules are enforced when you attach a listener, not for every individual child node.
If this fact that rules cannot filter data is new to you, I recommend you study the documentation section on it as well as some of the many previous questions on the topic.

How do I use Firebase to handle automatic server-side calculations?

Perhaps my question should be restated as: how do I refactor those behaviours into CRUD, which is what Firebase excels at?
I get that CRUD works well. I also see how the Firebase declarative security model allows me to ensure proper security server-side, where it should exist.
Let's say I have a subscription service. Each time a person signs up for a service, they need to automatically have a "due" line item added to their account. In simple terms:
/users/john
/services/goodstuff
So john can sign up for goodstuff, I might let him in for 30 days without paying, but will remind him when 30 days is up, "hey, you need to pay or else you lose your subscription to goodstuff."
With a server back-end, I would POST to /services/goodstuff/members, e.g., have part of the POST handler add a "you owe" line item to john's account, ensuring that no one can join goodstuff without being marked as owing.
In a Firebase BaaS app, where those server-side logics don't exist, how would I refactor the app to get the same effective behaviour?
Update (March 10, 2017): While the architecture I outline below is still valid and can be used to combine Firebase with any existing infrastructure, Firebase just released Cloud Functions for Firebase, which allows you to run JavaScript functions on Google's servers in response to Firebase events (such as database changes, users signing in and much more).
One potential solution (untested, sorry; but it should be the right idea):
{
"rules": {
"users": {
"$user": {
/* When they create their user record, they must write a 'due' that's
* within the next 30 days. */
".write": "!data.exists() && newData.child('due').isNumber() && newData.child('due').val() < now + (30*24*60*60*1000)"
}
},
"services":
"$service": {
/* Must be authenticated and due date must not be passed. */
".read": "auth != null && now < root.child('users/' + auth.id + '/due).val()"
}
}
}
}
This would require that when somebody logs in for the first time and initializes their users/ entry, they'd have to write a due date in the next 30 days. And then when accessing any service, that due date would be verified to have not passed.
That said, another option is to just spin up a tiny backend service to handle this sort of business logic. Firebase excels at protecting, storing, and synchronizing data. But if you have complicated business logic, you might want to think about spinning up a tiny backend process. Firebase has a REST api as well as Node.JS and JVM clients, so it's really easy to run your own backend code that integrates with Firebase.

Possibility for only currently connected (not authenticated) and admin user to read and write on certain location

Is there any way to write a security rule or is there any other approach that would make possible only for currently connected (not authenticated) user to write/read certain location - admin should also be able to write/read?
Can a rule be written that disallows users to read of complete list of entries and let them read only entry that matches some identifier that was passed from client?
I'm trying to exchange some data between user and Node.js application through Firebase and that data shouldn't be able to read or write by anyone else other than user and/or admin.
I know that one solution would be that user requests auth token on my server and uses it to authenticate on Firebase and that would make it possible to write rule which prevents reads and writes. However, I'm trying to avoid user connecting to my server so this solution is not first option.
This is in a way session based scenario which is not available in Firebase but I have
some ideas that could solve this kind of problem - if implemented before session management:
maybe letting admin write into /.info/ location which is observed by client for every change and can be read only by active connection - if I understood correctly how .info works
maybe creating .temp location for that purpose
maybe letting admin and connected client could have more access to connection information which would contain some connection unique id, that can be used to create location with that name and use it inside rule to prevent reading and listing to other users
Thanks
This seems like a classic XY problem (i.e. trying to solve the attempted solution instead of the actual problem).
If I understand your constraints correctly, the underlying issue is that you do not wish to have direct connections to your server. This is currently the model we're using with Firebase and I can think of two simple patterns to accomplish this.
1) Store the data in an non-guessable path
Create a UUID or GID or, assuming we're not talking bank level security here, just a plain Firebase ID ( firebaseRef.push().name() ). Then have the server and client communicate via this path.
This avoids the need for security rules since the URLs are unguessable, or close enough to it, in the case of the Firebase ID, for normal uses.
Client example:
var fb = new Firebase(MY_INSTANCE_URL+'/connect');
var uniquePath = fb.push();
var myId = uniquePath.name();
// send a message to the server
uniquePath.push('hello world');
From the server, simply monitor connect, each one that connects is a new client:
var fb = new Firebase(MY_INSTANCE_URL+'/connect');
fb.on('child_added', newClientConnected);
function newClientConnected(snapshot) {
snapshot.ref().on('child_added', function(ss) {
// when the client sends me a message, log it and then return "goodbye"
console.log('new message', ss.val());
ss.ref().set('goodbye');
});
};
In your security rules:
{
"rules": {
// read/write are false by default
"connect": {
// contents cannot be listed, no way to find out ids other than guessing
"$client": {
".read": true,
".write": true
}
}
}
}
2) Use Firebase authentication
Instead of expending so much effort to avoid authentication, just use a third party service, like Firebase's built-in auth, or Singly (which supports Firebase). This is the best of both worlds, and the model I use for most cases.
Your client can authenticate directly with one of these services, never touching your server, and then authenticate to Firebase with the token, allowing security rules to take effect.

Resources