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.
Related
I have a Firebase database that I want to only allow users who have access to that application to be able to read from and write to.
My data structure is like so:
{
"applications": {
"id_1": {
"feature": {
"a": true,
"b": false
},
"users": {
"user_id_1": true,
"user_id_2": true
}
}
}
}
As you can see, each application can have many users who have read/write access.
I only want users in the users object to be able to retrieve that application.
I have rules like so:
{
"rules": {
"applications": {
".read": "auth != null",
".write": "auth != null",
"$appId": {
".write": "data.child('users').child(auth.uid).val() === true",
".read": "data.child('users').child(auth.uid).val() === true"
}
}
}
}
".read": "auth != null", allows any user who is logged in to be able to retrieve all applications. I only want users user_id_1 or user_id_2 to be able to read that application.
In pseudo code, I would do something like this:
{
"rules": {
"applications": {
".read": "only users in `root.applications.$appId.users` can read", // I need to replace `$appId` some how
".write": "auth != null",
"$appId": {
".write": "data.child('users').child(auth.uid).val() === true",
".read": "data.child('users').child(auth.uid).val() === true"
}
}
}
}
How can I restrict it so when user user_id_1 fetches their applications, they only see apps they have access to?
You're hitting a few common problems here, so let's go through them one by one.
1. Security rules can't filter data
You're trying to control in your rule on /applications what application will be returned when a user tries to read that node. Unfortunately that is not possible, because security rules grant all-or-nothing access.
So either the user has access to /applications (and all data under it), or they don't have access to it. You can't set a rule on /applications to grant them access to some child nodes.
In the documentation, this is referred to as rules are not filters and the fact that permission cascades.
2. Avoid nesting data
You're trying to grant access to /applications, but then store two types of data under there for each application. In cases like that, it is usually better to store each type of data as its own top-level list.
So in your case, that'd be:
{
"application_features": {
"id_1": {
"a": true,
"b": false
},
},
"application_users": {
"id_1": {
"user_id_1": true,
"user_id_2": true
}
}
}
This allows you to grant separate access permissions for the application users and its features. While it means you'll have to read from both branches to get all information of each user, the performance difference there is negligible as Firebase pipelines those requests over a single socket
For controlling access and the most scalable data structure, Firebase recommends that you avoid nesting data and flatten your data structure.
3. Model the data in your database to reflect the screens of your app
Since granting anyone access on /applications gives them access to all data under that, you'll likely need another place to store the list of applications for each user.
I usually make this list explicit in my databases, as another top-level list:
{
...
"user_applications": {
"user_id_1": {
"id_1": true
},
"user_id_2": {
"id_1": true
}
}
}
So now when you want to show the list of applications for the current user, you load the IDs from /user_applications/$uid and then look up the additional information for each app with extra calls (which in turn can be pipelined again).
This one is not in the documentation, but a common pattern with NoSQL databases. I recommend checking out my answers to Many to Many relationship in Firebase and Firebase query if child of child contains a value.
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"
}
}
}
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.
Default setting gives full access:
{
"rules": {
".read": true,
".write": true
}
}
For the sake of testing my understanding of rule-writing from the Firebase guide and documentation, I'm (now retreating to) trying to achieve the same results by writing rules for the 4 parent nodes.
If it makes a difference, the first two nodes only have values, no children. **Sidequest: are they still called nodes?
The rules below cause the same behavior as when the rules above are changed to false read and write.
{
"rules": {
"myNode1": {
".read" : true,
".write" : true
},
"myNode2" : {
".read" : true,
".write" : true
},
"myNode3" : {
".read" : true,
".write" : true
},
"myNode4" : {
".read" : true,
".write" : true
}
}
}
What is wrong with my rules?
UPDATE/context:
I have an authDataCallback that stops running here (within the if (authData) { clause):
var ref = new Firebase("https://<my>.firebaseio.com")
ref.once("value", function(snapshot){
Found that if I change the ref var to something more specific: var ref = new Firebase("https://<my>.firebaseio.com/myNode1"), the authDataCallback runs in entirety.
Surely it won't be necessary to produce snapshots of the entire database; this was a confused way of getting the data I need. I've updated, but I'm still confused about why the rules held up the callback considering I gave read and write to the whole database.
I think this is the answer based on the info provided:
The two sets of rules you posted are different. The first set
{
"rules": {
".read": true,
".write": true
}
}
allows everyone read and write access to every node, including the parent node, within your firebase at
https://<my>.firebaseio.com
The second set of rules allows everyone access to specific nodes within your firebase reference but blocks access to all other nodes including access to the parent node. In the update the code is trying to read the parent node which has no rules defined for it so by default read and write are false.
So say you have the following structure:
https://whirlygig.firebaseio.com
Whirlygig
someRandomData: "3.141"
otherRandomData: "6.02"
myNode1
firstName: "first"
lastName: "last"
myNode2
first: "first"
last: "last"
With your first set of rules, everyone can access someRandomData as well as the myNode1, myNode2 etc.
With the second set of rules, everyone can access ONLY myNode1 and myNode2 but cannot access someRandomData
I have a firebase location with all of my app's stored messages as child objects.
I want clients to be able to get each message if they know the id of the message but not download the entire messages table.
What would the security rule for this look like?
Thanks.
You can disallow a read on the parent, but allow reads if the ID is known:
"rules": {
"messages": {
// Disallow enumerating list of messages
".read": false,
".write": false,
"$messageID": {
// If you know the messageID you can read the message.
".read": true,
// Cannot overwrite existing messages (optional).
".write": "!data.exists()"
}
}
}
See https://github.com/firebase/firepano for an example app that uses unguessable URLs for security.