Firebase-Database: deny in general but allow for specific path - firebase-realtime-database

I try to generate my Firebase-DB Rules and want to deny writing without Authentication in general and only allow it for a specific path. It gets published correctly but it does not work.
http://myproject.firebaseio.com/test/foo
{
"rules": {
".read": true,
".write": "auth != null",
"foo":{
".write": "true"
}
}
}
UPDATE 1
OK, I found out that Rule-Cascading is making it impossible how I did it above, because
".write": "auth != null"
is preventing me from writing. How can I avoid that

The firebase rules cascade from the top down, so there's no way to block writing at a higher level, but allow it at a lower one. Here's a note right from the docs:
Note: Shallower security rules override rules at deeper paths. Child
rules can only grant additional privileges to what parent nodes have
already declared. They cannot revoke a read or write privilege.
You may need to restructure your data so that the foo object is at the top-level, which would let you have a different write rule on it.
Another option is to simply remove the write rule at the root and add it to any other root-level items that require an authenticated user.

Related

Using OR condition in OPA rego

I want to use an OR operation to combine the following conditions:
the count of my arr is not equal to 0
my email does not contain "test.com"
Currently I am using the built-in function any():
any([count(arr) != 0, not contains(email, "test.com")])
However my rule is producing an error.
How can I achieve and improve this in one line?
More generally, Rego does not allow OR-ing statements in the same function. Using any() works well for simple cases, but can become unwieldy for complex ones so it is considered an antipattern.
Instead, Rego uses incremental rules, where every statement within each Rule is AND-ed together, and rules of the same name are OR-ed together.
Consider the following deny rule. In short, it says that we will deny the request if at least one of:
The user is not an admin, or
Today is Sunday.
deny {
input.role != "admin"
}
deny {
time.weekday(time.now_ns()) == "Sunday"
}
This would only allow requests to the admin role on days other than Sunday. If instead we said:
deny {
input.role != "admin"
time.weekday(time.now_ns()) == "Sunday"
}
We would then only deny requests from non-admin roles on Sunday. Requests from admin would always be allowed.
The not keyword is a bit of a special case in that it can't be used inside of many contexts such as the array of your example. I believe this could be improved in OPA, but until then you can trivially avoid it for at most cases, like the one you provided:
any([count(arr) != 0, contains(email, "test.com") == false])

Pattern for Using an Access Code with Firebase

I'm currently using Firebase for my app, and I'd like to make parts of my application to not only require user authorization but to also be private and require a user to input an access code to gain entry.
I've already got the authorization piece working with a few different providers and anonymous authentication. My question is, is there a proper pattern already existing for above functionality? Or do I need to write a separate web service to handle this requirement?
If you want to add security to your database you better check this out the Firebase documentation about security rules
In the database console under the Rules you will find the section to add security to your database. Basically it's a JSON object like this:
{
"rules": {
".read": "auth != null",
".write": "auth != null"
}
}
In the example above I just declare that for the complete database to read and write there should be a logged in user.
In the same way I recommend you to read the auth section of the documentation and how to implement it here.
EDITED
Because the example above wasn't clear enough I made another one in my Firebase database. In this example I have a path https://*****.firebaseio.com/users. In the example below you can see that I assigned true to the .read property and auth != null to the .write property, it means that everyone can read this path but only authenticated users can write in this path.
{
"rules": {
"users":{
".read": "true",
".write": "auth != null"
}
}
}
Using the Firebase simulator you can see that read is enable when you are not authenticated.
But if I want to write without authenticate myself I got an error.
Now if I use authentication in the simulator I can perform a write operation.
EDITED VERSION 2 "ACCESS TOKEN"
Ok I see what you mean. I assume you are going to store this "access code" somewhere in your Firebase database. If you want to validate data within the database to allow read or write you can do it as the documentat says here.
{
"rules": {
"users": {
".read": "root.child('access_token').child(auth.uid).exists()"
}
}
}
In the example above I have a path in the database called access_token, in this path I stored a key pair user_uid-accessToken and then using the root property make a query to validate if you have a value for the authenticated user in this path. I'm using the exists() but there are many others methods you can use to make this validation. For example if the access code is hardcoded then you can use the val() method to compare for an exact value.

Firebase rules to access specific child's value

The childByAutoId would be useful if you want to save in a node multiple children of the same type, that way each child will have its own unique identifier.
List:{
KJHBJJHB:{
name:List-1,
owner:John Doe,
user_id:<Fire base generated User_id>
},
KhBHJBJjJ:{
name:List-2,
owner:Jane Lannister,
user_id:<Fire base generated User_id>
},
KhBHJZJjZ:{
name:List-3,
owner:John Doe,
user_id:<Fire base generated User_id>
}
}
I am trying to access the List with the help of the following code:
let ref = FIRDatabase.database().reference(withPath: "/List")
The current user logged into the app is John Doe. When the user accesses the list, I want all the List child whose owner is John Doe(i.e. List-1 & List-3) and ignore the other child values.
Do I have to do this in my application or can this be achieved via Firebase Security rules?
My current rule definition is:
"List":{
".read": "root.child('List/'+root.child('List').val()+'/user_id').val() === auth.uid" }
But this rule is not giving me any success. Any idea how to achieve the desired result?
You're trying to use security rules to filter the list. This is not possible and one of the common pitfalls for developers coming to Firebase from a SQL background. We commonly refer to it as "rules are not filters" and you can learn more about it in:
the Firebase documentation
this answer
our new video series Firebase for SQL developers
and many previous questions mentioning "rules are not filters"
The solution is almost always the same: keep a separate list of the keys of posts that each user has access to.
UserLists:{
JohnUid: {
KJHBJJHB: true,
KhBHJZJjZ: true
},
JaneUid: {
KhBHJBJjJ: true
}
}
This type of list is often referred to as an index, since it contains references to the actual post. You can also find more about this structure in the Firebase documentation on structuring data.

Firebase one to one chat security rules

From numerous other posts, it seems that the recommendation for creating the structure for one to one chat seems to be to combine two user IDs, taking into account their natural ordering. For example:
root{
messages:{
user1_user2:{
//messages here
},
user1_user3:{
//messages here
}
}
}
Seems fine and is much more efficient than creating a personal message topic for each user. So my question now is what would be the best way to secure this? Right now I have the following:
"messages":{
"$channel":{
".read": "$channel.beginsWith(auth.uid) || $channel.endsWith(auth.uid)"
}
}
Is this sufficient? Can't shake the thought that it's possible for a different but longer user id that starts or ends the same way will get past this check

Firebase Commit/Rollback for complex writes

I'm writing a financial app with Firebase and for an receipt to be submitted, a number of other objects also need to be updated. For the data to be valid, all data updates need to be completed successfully. If there's an error in one of the writes, all updates must be rolled back.
For example:
If the user submits a receipt, the receipt object must be updated as well as an invoice object as well as other general ledger objects.
If the update started but the user lost internet connection half way through, all changes should be rolled back.
What's the best way to achieve this in Firebase?
First, let's chat for a minute about why someone might want to do commit/rollback on multiple data paths...
Do you need this?
Generally, you do not need this if:
you are not writing with high concurrency (hundreds of write opes per minute to the SAME record by DIFFERENT users)
your dependencies are straightforward (B depends on A, and C depends on A, but A does not depend on B or C)
your data can be merged into a single path
Developers are a bit too worried about orphaned records appearing in their data.
The chance of a web socket failing between one write and the other is probably trivial and somewhere on the order of collisions between
timestamp based IDs. That’s not to say it’s impossible, but it's generally low consequency, highly unlikely, and shouldn’t be your primary concern.
Also, orphans are extremely easy to clean up with a script or even just by typing a few lines of code into the JS console. So again,
they tend to be very low consequence.
What can you do instead of this?
Put all the data that must be written atomically into a single path. Then you can write it as a single set or a transaction if necessary.
Or in the case where one record is the primary and the others depend on this, simply write the primary first, then write the others in the callback. Add security rules to enforce this, so that the primary record always exists before the others are allowed to write.
If you are denormalizing data simply to make it easy and fast to iterate (e.g. to obtain a list of names for users), then simply index that data in a separate path.
Then you can have the complete data record in a single path and the names, emails, etc in a fast, query/sort-friendly list.
When is this useful?
This is an appropriate tool to use if you have a denormalized set of records that:
cannot be merged practically into one path in a practical way
have complex dependencies (A depends on C, and C depends on B, and B depends on A)
records are written with high concurrency (i.e. possibly hundreds of write ops per minute to the SAME record by DIFFERENT users)
How do you do this?
The idea is to use update counters to ensure all paths stay at the same revision.
1) Create an update counter which is incremented using transactions:
function updateCounter(counterRef, next) {
counterRef.transaction(function(current_value) {
return (current_value||0)+1;
}, function(err, committed, ss) {
if( err ) console.error(err)
else if( committed ) next(ss.val());
}, false);
}
2) Give it some security rules
"counters": {
"$counter": {
".read": true,
".write": "newData.isNumber() && ( (!data.exists() && newData.val() === 1) || newData.val() === data.val() + 1 )"
}
},
3) Give your records security rules to enforce the update_counter
"$atomic_path": {
".read": true,
// .validate allows these records to be deleted, use .write to prevent deletions
".validate": "newData.hasChildren(['update_counter', 'update_key']) && root.child('counters/'+newData.child('update_key').val()).val() === newData.child('update_counter').val()",
"update_counter": {
".validate": "newData.isNumber()"
},
"update_key": {
".validate": "newData.isString()"
}
}
4) Write the data with the update_counter
Since you have security rules in place, records can only successfully write if the counter does not move. If it does move, then the records have been overwritten by a concurrent change, so they no longer matter (they are no longer the latest and greatest).
var fb = new Firebase(URL);
updateCounter(function(newCounter) {
var data = { foo: 'bar', update_counter: newCounter, update_key: 'myKey' };
fb.child('pathA').set(data);
fb.child('pathB').set(/* some other data */);
// depending on your use case, you may want transactions here
// to check data state before write, but they aren't strictly necessary
});
5) Rollbacks
Rollbacks are a bit more involved, but can be built off this principle:
store the old values before calling set
monitor each set op for failures
set back to old values on any committed changes, but keep the new counter
A pre-built library
I wrote up a lib today that does this and stuffed it on GitHub. Feel free to use it, but please be sure you aren't making your life complicated by reading "Do you need this?" above.

Resources