Is it possible, and recommended, to define write rules at Firebase server which needs to consider calculations on timestamps?
Example
Scenario: User is trying to add new Comment to existing Thread.
Write Rule: Comments can only be added to existing Thread if current time is between Thread.openAtTimestamp and Thread.closesAtTimestamp.
This would be fairly easy to solve with use of momentjs, for instance. But I guess that momentjs lib is not available in Firebase rules?
Say you have this data structure:
threads
$threadid
openAtTimestamp: 1453820367233
closesAtTimestamp: 1454425139712
comments
$threadid
$commentid
author: "Ismar Slomic"
timestamp: 1454425139711
Then you can only have a comment in a thread if its timestamp is between the openAtTimestamp and closesAtTimestamp of that thread.
{
"rules": {
"comments: {
"$threadid": {
"$commentid": {
"timestamp: {
".validate": "
newData.val() > root.child('threads').child($threadid).child('openAtTimestamp') &&
newData.val() < root.child('threads').child($threadid).child('closesAtTimestamp')
}
}
}
}
}
This is just a rough outline to get you started. The Firebase documentation on security rules has tons more information.
Related
I face an issue : I want the user of my web program to write and delete data in firebase only if he knows where it is and I don't understand what rules have to apply to that.
For instance, my user has an id which is '785475' and I want to only give him access to '/data/785475'. How do I do that please?
Do I have to use authentification?
Thanks in advance and please have a nice day!
In Firebase security rules permission cascades. So in your model and requirement, it is important that the user doesn't have read access to /data, as that means they could read /data and all nodes under it.
To the trick is to only grant them access to the individual child nodes of /data:
{
"rules": {
"data": {
"$any": {
".write": true
}
}
}
}
With the above nodes, a user can delete any existing node that the know the key of, but they can't read (or query) the entire /data node to determine those keys.
To only allow them to overwrite existing data, and not create any new nodes, you can do:
{
"rules": {
"data": {
"$any": {
".write": "data.exists()"
}
}
}
}
For more on this, and other examples, check out the Firebase documentation on new vs existing data.
I am struggling with what seems to be a very trivial task. I am receiving the following warning from Firebase when doing a simple query:
Query:
admin.database().ref('/dispatch/')
.orderByChild('shipmentKey')
.equalTo(shipmentKey)
.once('value')
.then(
Warning:
FIREBASE WARNING: Using an unspecified index. Consider adding ".indexOn": "shipmentKey" at /dispatch to your security rules for better performance
My database looks like the following:
And my database.rules.json looks like this:
{
"rules": {
"dispatch": {
".indexOn": "shipmentKey"
}
}
}
and have also tried:
{
"rules": {
"dispatch": {
".indexOn": ["shipmentKey"]
}
}
}
I have already read all the other stackoverflow questions on this topic and cannot seem to rid our project of this warning, any help would be greatly appreciated.
UPDATE 06/22/2017
I have solved the problem, and it was very simple. It turns out that firebase deploy wasn't actually deploying the database rules so the database.rules.json was not being uploaded. I simply ran the following command:
firebase deploy --only database
And everything works great now! Thanks to Bob Snyder for helping point this out. Hope this helps someone else out there!
You need to put all the indexes in square brackets, separated by a comma if you have more than one.
{
"rules": {
"dispatch": {
".indexOn": ["shipmentKey"]
}
}
}
I'm struggling to come up with the best way to structure part of my database and its associated security rules.
I have chat groups, and users can be added to those groups at any point. When users are added to a group, they should be able to retrieve only the messages sent after that. It shouldn't be possible for them to retrieve any messages that were sent before they (the users) were added to the group.
My first approach wrongly assumed that security rules would apply only to the data being queried.
Simplifying it for this question, I had the following structure:
{
"groups": {
"-Kb9fw20GqapLm_b8JNE": {
"name": "Cool people"
}
},
"groupUsers": {
"-Kb9fw20GqapLm_b8JNE": {
"3JzxHLv4b6TcUBvFL64Tyt8dTXJ2": {
"timeAdded": 1230779183745
},
"S2GMKFPOhVhzZL7q4xAVFIHTmRC3": {
"timeAdded": 1480113719485
}
}
},
"groupMessages": {
"-Kb9fw20GqapLm_b8JNE": {
"-KbKWHv4J4XN22aLMzVa": {
"from": "3JzxHLv4b6TcUBvFL64Tyt8dTXJ2",
"text": "Hello",
"timeSent": "1358491277463"
},
"-KfHxtwef6_S9C5huGLI": {
"from": "S2GMKFPOhVhzZL7q4xAVFIHTmRC3",
"text": "Goodbye",
"timeSent": "1493948817230"
}
}
}
}
And these security rules:
{
"rules": {
"groupMessages": {
".indexOn": "timeSent",
"$groupKey": {
".read": "root.child('groupUsers').child(auth.uid).child($groupKey).child('timeAdded').val() <= data.child('timeSent').val()"
".write": "!data.exists() && root.child('groupUsers').child(auth.uid).child($groupKey).exists() && newData.child('from').val() === auth.uid",
}
}
}
}
With that, I figured I could retrieve the messages for a particular group like so:
var myTimeAdded = /* already retrieved from the database */;
firebase.database()
.ref('groupMessages/-Kb9fw20GqapLm_b8JNE')
.orderByChild('timeSent')
.startAt(myTimeAdded)
.on('child_added', /* ... */);
But like I said, that was a wrong assumption. Any suggestion on how I could achieve this?
Read rules are enforced at the location where you attach a listener.
So in your case that is groupMessages/-Kb9fw20GqapLm_b8JNE. If your user has read permission there the listener is allowed. If the user does not have read permission, the listener is rejected/cancelled.
This means that rules cannot be used to filter data. We often refer to this as "rules are not filters" and it's one of the most common pitfalls for developers who are new to Firebase's security model. See:
the section rules are not filters in the Firebase documentation
previous questions about Firebase that mention "rules are not filters"
By themselves your rules are not wrong: they only allow access to each specific child if it's not too old. They just don't allow you to run a query on groupMessages/-Kb9fw20GqapLm_b8JNE anymore.
The common way to work around this is to have a separate structure (commonly called an "index") with the keys of the items that your query would otherwise return. In your case it looks like that might turn into a index for each user with the keys of all messages after they joined.
But I'll be honest, it sounds like you're trying to use security rules in a SQL way here. It seems unlikely that the user isn't allowed to see older messages. More likely is that you don't want the user to be bother by the older messages. In that case, I'd just solve it with a query (as you already have) and remove the ".read" rule.
I have been using Firebase for a little while in a project under development, but haven’t worried too much about security up to now. From now on I would like to implement a few Security rules. I have read the QuickStart tutorial on the subject on Firebase web site, but I am not yet sure how it all fits together.
Here the structure of my data:
myApp
- DataList
- Contents
- randomKey_One
value: "grgrsgs;jj…data…data.."
- randomKey_Two
value: "43efdsd7gs;jj…data…data.."
- randomKey_Three
value: "8dfsvshj…data…data.."
…….
- Names
- randomKey_One
- authorID: "PeterLogID"
- name: "RecordOne_Peter"
- randomKey_Two
- authorID: "JohnLogID"
- name: "RecordStar_byJohn"
- randomKey_Three
- authorID: "PeterLogID"
- name: "RecordTwo_Peter"
…….
There is a one-to-one correspondance between Contents and Names, which is established through the values of randomKey_One, randomKey_Two, ….etc..
Those keys are automatically generated when a new record is created. I store the login ID of the user in the Names section, in the authorID field.
What I want is:
1) To have read access for the whole world to all the data (possibly with the exception of authorIDs).
2) To give write(and delete) access to a record, only if the authorID field matches auth.uid (i.e. the logged in user).
I have already figured out part 1), forgetting the “exception of authorIDs”.
How do I go with part 2)?
What I have tried at this point did not work.
One issue I have is that I don’t know how to access the authorID field within the security rule script, since I do not have the name of its parent.
For those who may one day hit the same problem and read this.
Here I put the solution I came up with, after a few hours. Since this is my first time to deal with Firebase Security Rules, any expert on the subject is welcome to comment.
{
"rules": {
".read": true,
"DataList": {
"Names": {
"$Name": {
".write": "newData.child('authorID').val() === auth.uid || data.child('authorID').val() === auth.uid"
}
},
"Contents": {
"$Content": {
".write": "root.child('DataList/Names/'+$Content).exists() && root.child('DataList/Names/'+$Content).child('authorID').val() === auth.uid"
}
}
}
}
}
Having spent literally days trying the different, various recommended ways to do this, I've landed on what I think is the most simple and promising. Also thanks to the kind gents from this SO question: Get the index ID of an item in Firebase AngularFire
Curent setup
Users can log in with email and social networks, so when they create a record, it saves the userId as a sort of foreign key.
Good so far. But I want to create a rule so twitter2934392 cannot read facebook63203497's records.
Off to the security panel
Match the IDs on the backend
Unfortunately, the docs are inconsistent with the method from is firebase user id unique per provider (facebook, twitter, password) which suggest appending the social network to the ID. The docs expect you to create a different rule for each of the login method's ids. Why anyone using >1 login method would want to do that is beyond me.
(From: https://www.firebase.com/docs/security/rule-expressions/auth.html)
So I'll try to match the concatenated auth.provider with auth.id to the record in userId for the respective registry item.
According to the API, this should be as easy as
In my case using $registry instead of $user of course.
{
"rules": {
".read": true,
".write": true,
"registry": {
"$registry": {
".read": "$registry == auth.id"
}
}
}
}
But that won't work, because (see the first image above), AngularFire sets each record under an index value. In the image above, it's 0. Here's where things get complicated.
Also, I can't test anything in the simulator, as I cannot edit {some: 'json'} To even authenticate. The input box rejects any input.
My best guess is the following.
{
"rules": {
".write": true,
"registry": {
"$registry": {
".read": "data.child('userId').val() == (auth.provider + auth.id)"
}
}
}
}
Which both throws authentication errors and simultaneously grants full read access to all users. I'm losing my mind. What am I supposed to do here?
I don't think you want to store user-specific data under a non-user-specific index. Instead of push()ing to your firebase reference, store the user data behind a meaningful key.
e.g.
auth.createUser(email, password, function(error, user) {
if (!error) {
usersRef.child(user.id).set(stuff);
}
});
Now you can actually fetch user data based on who is authenticated.
The custom Auth in the forge's simulator isn't the greatest but if you hit the tab key after selecting the input, it lets you paste or edit the field. At which point you can add {"provider":"facebook","id":"63203497"} or {"provider":"twitter","id":"2934392"} and hopefully get some useful debug out of it.
Assuming your firebase is something like:
{"registry":{
"0":{
"id":"abbacadaba123",
"index":"0",
"name":"New Device",
"userId":"facebook63203497"},
"1":{
"id":"adaba123",
"index":"1",
"name":"Other Device",
"userId":"twitter2934392"}
}
}
This may work for security rules:
{
"rules": {
"registry":{
"$registryId":{
".read":"data.child('userId').val() === (auth.provider + auth.id)",
".write":"(data.child('userId').val() === (auth.provider + auth.id))||(auth != null && !data.exists())",
".validate": "newData.hasChildren(['id', 'index', 'name', 'userId'])",
"id": {
".validate":"newData.isString()"
},
"index": {
".validate":"newData.isNumber()"
},
"name": {
".validate":"newData.isString() && newData.val().length >= 1"
},
"userId": {
".validate":"newData.val() === (auth.provider + auth.id)"
}
}
}
}
}
Your read rule tested as expected. The facebook user read-tests true on registry 0 and false on 1. The twitter user is false on 0 and true on 1.
I did a couple quick tests on the .write and .validate rules and they seem to work.
Hope this helps at least rule out the firebase security rules portion of things, so you can focus on the AngularFire binding part.