Firebase security rule with read and write at separate locations - firebase

I have my Realtime database schema designed like this.
"roles" : {
"uid-1" : "user",
"uid-2" : "moderator",
"uid-3" : "user" // ... and so on
}
"photos" : {
"uid-1" : {
"photo" : "..."
"date" : "..."
}
// ... and so on
}
And my security rules are defined like this. Only relevant part is shown.
"photos" : {
"$key" : {
".read" : "auth.uid == $key || root.child('roles/'+auth.uid).val() == 'moderator'",
".write" : "auth.uid == $key"
}
}
As it is clear from the above snippet I want the users to be able to read or write to their own key under photos node. As for the moderators I want them to be able to read any users data.
At first it seems to work as expected but there is a small catch. According to fire-base if no rule is specified at a node it will be considered as false. That means moderator can now read every users data but only if he explicitly asks for certain UID. In other words reading photos/<uid> is allowed but photos/ as a whole is not allowed.
A potential solution was to move read statement to make it direct child of photos node. But there I cant enforce $key for other users.
One might try to split the rule and define at both locations but that would not work because according to firebase docs shallow rules will override the deeper rules.

You seem to have two requirements:
Each user can read and write their own photos.
A moderator can read photos of all users.
That can be enforced in server-side security rules like this:
"photos" : {
".read" : "root.child('roles/'+auth.uid).val() == 'moderator'",
"$key" : {
".read" : "auth.uid == $key",
".write" : "auth.uid == $key"
}
}
The permission that an moderator gets from /photos carries down onto each user's photos. This permission cannot be revoked at a lower level. But you can definitely give users *additional** permission at a lower level, which is what these rules do.

Related

Firebase rules, deny edits after initial write, write depend on value

I got an "object" on my realtime database.
-MHHP5ZSmLKG_xeA9SLJ
0NThxhcHIPgOJGZC1MyE3Fg0NUc2
XCEI5dQxP8YxaChrF5O061eyFv32
creatorID: "7Pao7pDRaFUVWM9c234Qq44UwoE3"
message: "I wish I knew, why the world is is getting craz..."
messageID: "-MHHP5ZSmLKG_xeA9SLJ"
timestamp: 1600184270739
timestampReverse: 998399815846742
Two questions.
Is there a way for me to make it, such that creatorID: can't be written to after the original initialization.
"creatorID" : {
.write : "false"
}
Does this work, or would it also block initial write? Essentially I'm looking to make it unable to be edited.
And is there a way to writea rule such that only if auth.uid and creatorID.value is equal, can you edit the message:
"message" : {
.write : "auth.uid == creatorID.value"
}
Best regards,
Sam
If you want the creatorID field to only be writeable upon creation, you can do:
"creatorID" : {
.write : "!data.exists()"
}
If you want the message field to only be writeable by its original author, that'd be:
"message" : {
.write : "auth.uid == data.child('creatorID').val()"
}

Firebase Rules - Nested ifs

I want to do a few nested if statements to achieve that every user's email is verified befor he gets read write access.
But I dont know how to do that because if I write it in
"rules" : {
"read" : "auth.token.email_verified",
"write" : "auth.token.email_verified"
"other_location" :{
"read" : true,
"write": false
}
}
The user will get write in other_location even through I set write to false in other_location.
I don't know why its like that but my simulation showed that.
Can someone help me?
Firebase RTDB Rules cascade from higher tiers down to the more specific ones. If you allow the ability to perform read/write operations on a given key, which in your case is the root of your database, any key nested under that one will share the same allowed read/write permissions even if your nested rule says otherwise.
To overcome this, you can use variables in the key's path to match unnamed locations. By convention, these "other keys" variables are called "$other".
{
"rules": {
"restricted-location": {
".read": true,
".write": false
},
"$other": { // any key not named above at this level
".read": "auth.token.email_verified",
".write": "auth.token.email_verified"
}
}
}

Firebase add users but disable any modification

got stuck on this problem: I want to allow user to register on my webpage using firebase, for that reason I planned the following structure:
users : {
user0 : { //user specific data },
user1 : { //... }
...
}
Everything works fine (writing, reading...) until I change the security rules. I want to change them because I want the users only to register and not to have the power to delete their or potentially other user accounts. But I couldn't find anything very helpful on that. Below is what I'm currently left with:
{
"rules": {
"users" : {
".read": true,
"$uid" : {
".write": "auth !== null && auth.uid === $uid"
}
}
}
}
I'm wondering how to set up the rules such that users can only add new accounts.
I would appreciate any help!
Thaanks :)
Edit: Here's what I wanted to achieve with the rules above, maybe the below example using the simulator will make my point clear.
The first thing, I want to do is, is to let a user register at the /users node. Therefore, I chose the Custom Auth point in the simulator:
{ provider: 'anonymous', uid: '002a448c-30c0-4b87-a16b-f70dfebe3386' }.
Now, if I choose "simulate write" and give the path /users and the following key-value pair:
{ "002a448c-30c0-4b87-a16b-f70dfebe3386": { "1" : {"a" : "b"}}}
I get the below message (see Result_2), which tells me, that I cannot write to /users because there's no write rule allowing it, which I totally understand in the above security rules configuration, but I don't know how to change them such that I am able to write key-value pairs as the above one while still allowing each user to write on there entry only. I.e. the user with the uid: 002a448c-30c0-4b87-a16b-f70dfebe3386 would be allowed to write on the corresponding value with the rules above as long as he is authenticated (see Result_1).
E.g. Custon Auth authenticated user writing ON HIS ENTRY: (WORKS PERFECTLY AS EXPECTED)
{ provider: 'anonymous', uid: '002a448c-30c0-4b87-a16b-f70dfebe3386' }.
As the previous time. Now, "simulate write" on path:
/users/002a448c-30c0-4b87-a16b-f70dfebe3386
Result_1:
Attempt to write {"4":{"name":"fred"}} to /users/002a448c-30c0-4b87-a16b-f70dfebe3386 with auth={"provider":"anonymous","uid":"002a448c-30c0-4b87-a16b-f70dfebe3386"}
/
/users
/users/002a448c-30c0-4b87-a16b-f70dfebe3386:.write: "auth !== null && auth.uid === $uid" => true
Write was allowed.
Result_2: Writing the user onto the /users nodes fails, i.e. no registering is possible. And I want here to be able to add a user to /users but not be able to modify/delete user from /users. See simulator output below.
Attempt to write {"002a448c-30c0-4b87-a16b-f70dfebe3386":{"1":{"a":"b"}}} to /users with auth={"provider":"anonymous","uid":"002a448c-30c0-4b87-a16b-f70dfebe3386"}
/
/users No .write rule allowed the operation.
Write was denied.
Permissions cascades - once you give a user a permission on /users you cannot remove that permission on /users/$uid anymore.
The solution is to only grant permission on the lowest level, so in your case:
{
"rules": {
"users" : {
".read": true,
"$uid" : {
".write": "auth !== null && auth.uid === $uid"
}
}
}
}

Workaround for Firebase's "Rules Are Not Filters" constraint

I’d like a security rule that lets anyone get a list of users and read their names, but only allows logged in users to view their own email.
Here’s an example data structure:
"User" : {
"abc123" : {
"name" : "Bob",
"email" : "bob#hotmail.com"
}
}
A naive approach to a security rule might be to do the following:
"User" : {
"$user" : {
"name" : {
".read" : true
},
"email" : {
".read” : "auth.uid === $user"
}
}
}
However because there is no read rule at the User level, requests to read the list will be denied. But adding a read rule at the User level will override the email rule, and make every child node readable (see Rules Cascade in Firebase's Security Guide).
The Security guide does point out that Rules Are Not Filters, but doesn’t offer much guidance as to what to do about it.
Should I just split my User entity up into PrivateUser and PublicUser?
To let anyone get a list of users and read their names. AND to allow logged in users to view their own email.
Zac says: first think about access, and then to model objects that are either completely public or completely private.
Type 1:
{"rules":{
"user_publicly":{"$user:{
".read":true,
"name":{}
}},
"user_privately":{"$user:{
".read":"auth != null && $user == auth.uid",
"email":{}
}}
}}
Type 2:
{"rules":{
"user":{"$user:{
"public":{
".read":true,
"name":{}
},
"private":{
".read":"auth != null && $user == auth.uid",
"email":{}
}
}}
}}
A "workaround" would be to use Firestore (has a lot of the good things from Firebase Realtime Database, but adds more querying options, etc).
There is no "rules are not filters" restriction in Firestore!
EDIT:
Thanks to #DougStevenson for making me be more specific.
In Firestore, rules are still not filters, but they are compatible with filtering, unlike in Firebase Realtime DB.
Though, you have to construct your query in such a way, as to only return objects for which you have read permission (otherwise you get a security exception).
Here are some starting point docs:
Security rules:
https://firebase.google.com/docs/firestore/reference/security/
Queries: https://firebase.google.com/docs/firestore/query-data/queries

Firebase: Permission denied on demo example [duplicate]

I am trying to display a list of messages based on the recipient but for now, let's keep it simple. I am just trying to display a list of messages.
My rule looks like this
{
"rules": {
"communications" : {
"$communication":{
".read" : true,
".write": true
}
}
}
For some reason though, my application does not want to read it
fireRef = new Firebase(url);
fireRef.auth(MY_TOKEN);
commsRef = fireRef.child('communications')
$scope.communications = $firebase(commsRef)
It only works if I have a rule looking like
{
"rules": {
"communications" : {
".read" : true,
".write": true
}
}
But that will cause problem as I would like to add condition on the children node of my communication. Something like:
{
"rules": {
"communications" : {
".read" : true, ### I would like to get rid of this line as well and have the child handling it
".write": true,
"$communication":{
".read" : "data.child('to').val() == auth.uid"
}
}
}
I am assuming that is because I have a $firebase on the communications and it needs some read or write rules but how do I get the event when a new message is added otherwise
Thanks
With respect to security rules, Firebase operations are all-or-nothing.
That means that lists of data sent to the client will never be incomplete, or filtered views of the complete server data. As a result, attempting to load all of the data at /communications will fail when using your first set of security rules, even though you do have permission to read some of the data there as governed by the child rule at /communications/$communication.
To handle this use case, consider restructuring your data such that each communication is indexed by recipient, i.e. /communications/$recipient/$communication, which will simplify your security rules.
Additionally, you could even make that bucket read-only by the recipient (i.e. .read: auth.id == $recipient) while allowing anyone to send a message to that user (i.e. .write: auth != null && !data.exists()). That last rule ensures that the sending client is authenticated and writing to a location that does not yet exist, such as a new push id.

Resources