Firebase validate with newData - firebase

I'm working thru the excellent example on how to structure data as shown by Kato in this post:
Firebase user account security with firebaseSimpleLogin
I'm not having any luck getting validate to work properly.
The data structure is :
accepted_invites
game1
desc "fun game"
invites
game1
uuidAAA true
uuidBBB true
here's a screen shot:
Firebase data
If I try and write the following
ref.child("accepted_invites").child("game1").child("userTwo").child("uuidBBB").setValue(true);
it will make an entry in accepted_invites with this rule :
".validate": "root.child('invites/'+$game_id+'/uuidBBB').exists()"
but not
".validate": "root.child('invites/'+$game_id+'/'+newData.val()).exists()"
I tried using the simulator but I'm getting
Type Error: + only operates on numbers and strings.
Here's the complete rules as I have them:
{
"rules": {
".write" : true,
".read" : true,
"accepted_invites": {
"$game_id": {
"$user_id": {
//This validate rule fails
//".validate": "root.child('invites/'+$game_id+'/'+newData.val()).exists()"
//This one works
".validate": "root.child('invites/'+$game_id+'/uuidBBB').exists()"
}
}
}
}
}

The newData keyword is a special attribute referring to all incoming data, but it is not permitted in path expressions because it may not be a string. i.e., it could very well be an object.
If you're interested in using some portion of that data within the path, I would recommend just including another validation rule at a deeper path, such as:
{
"rules": {
".write" : true,
".read" : true,
"accepted_invites": {
"$game_id": {
"$accepting_user_id": {
"$sending_user_id": {
".validate": "root.child('invites').child($game_id).child($sending_user_id).exists()"
}
}
}
}
}
}

Related

How to fix security rules for newData on set function

Im working on an ionic app, i would like to send data to my firebase realtime-database as an object and compare the data with something, however on the server side which is my security rules for the newData, the write rule seems to be incorrect.
This is my sending code
var firebaseRef = this.afd.database.ref();
firebaseRef.child('Download/01/1').set({password:2431,name:'john'});
my database node should be written here
Download: {
01 : {
1: { // the data will be written here
}
}
}
and this is my security rules
"Download": {
"$id": {
".write": "newData.child('password').val() === 4321"
}
}
As you can see, i have purposely written the password on my set function '2431' in order to be process as incorrect during the validation of write rules, however the write rule still proceeds in writing the database even though the password it receive is incorrect, can you advise what should correct in my code? Thanks
Your rules and query don't match. You're writing to "Download/01/1", and password is set at "Download/01/1/password". However, your rules are set at "Download/XX", and newData.child('password') is referring to "Download/XX/password" (note that the "1" is missing in the path). Perhaps you meant something like:
"Download": {
"$id": {
"$i": {
".write": "newData.child('password').val() === 4321"
}
}
}
Or maybe:
"Download": {
"$id": {
".write": "newData.child('1').child('password').val() === 4321"
}
}

Firebase Database Rules: can't write to database

I tried to set some important write persmissions but I can't solve my problem. I got told that, if I add a write-rule to room, then I overwrite my room/$roomID/ingame rule.
What I'm trying to do is
Creating a room by auth users.
Set/update ingame of a room only by the creator of the room. (That works)
Rules:
{
"rules": {
".read": true,
"user": {
".indexOn": "displayname"
},
"room": {
"$roomID": {
"ingame":{
".write": "data.parent().child('creatorUid').val() == auth.uid"
}
}
}
}
}
How I call to create a new room:
let user = firebase.auth().currentUser
dbRoomRef.push().then((room) => {
room.set({
creatorUid: user.uid,
ingame: false,
})
}).catch(function(err) {
console.log(err.message)
}
)
Error message (as expected):
FIREBASE WARNING: set at /room/-L572bnuRv0_vntko-Bd failed: permission_denied
Thank you.
The error messages says that you're trying to write /room/-L572bnuRv0_vntko-Bd and have no permission to write there. That is correct, since your rules only give permission to write to /room/-L572bnuRv0_vntko-Bd/ingame.
If creatorUid is already set when you create the room, you don't have to include it in your write statement and can just do:
room.child("ingame").set(false);
If you're trying to allow everyone to create a new room (or write to an existing room) as long as they are the owner, you need to set your rules one level higher:
"room": {
"$roomID": {
".write": "newData.child('creatorUid').val() == auth.uid"
}
}

Firebase rules verify two values from the same data (unique) [duplicate]

I'm creating an application which lets users create items and then allow other users to subscribe to those items. I'm struggling to craft a rule that will prevent users from subscribing more than once to an item.
Here is an example of my data structure (anonymized, hence the "OMITTED" values):
{
"OMITTED" : {
"name" : "Second",
"body" : "this is another",
"userName" : "Some User",
"userId" : "OMITTED",
"created" : 1385602708464,
"subscribers" : {
"OMITTED" : {
"userName" : "Some User",
"userId" : "OMITTED"
}
}
}
}
Here are my Firebase rules at present:
{
"rules": {
".read": true,
".write": "auth != null",
"items": {
"$item": {
".write": "!data.exists()",
".validate": "newData.hasChildren(['name', 'body', 'userId', 'userName']) && newData.child('userId').val() == auth.id",
"subscribers": {
"$sub": {
".validate": "newData.hasChildren(['userId', 'userName']) && newData.child('userId').val() != data.child('userId').val()"
}
}
}
}
}
}
How can I prevent users from subscribing more than once? What is the rule I need to prevent duplicate users within the subscribers list based on userId?
Since security rules can't iterate a list of records to find the one containing a certain bit of data, the trick here is to store the records by an ID which allows for easy access. There is a great article on denormalization which offers some good insights into this practice.
In this case, if your use case allows, you may simply want to switch your data structure so that records are stored by the user's id, rather than storing the ID as a value in the record, like so:
/users/user_id/items/item_id/subscribers/user_id/
In fact, as you'll see in denormalization, you may even benefit from splitting things out even farther, depending on the exact size of your data and how you'll be reading it later:
/users/user_id
/items/user_id/item_id
/subscribers/item_id/user_id
In either of these formats, you can now prevent duplicates and lock down security rather nicely with something like this:
{
"users": {
"$user_id": { ".write": "auth.id === $user_id" }
},
"subscribers": {
"$subscriber_id": { ".write": "auth.id === $subscriber_id" }
}
}

How to constraint a child property when reading a parent list

I have the following database schema:
{
"events": {
"$eventId": {
"eventTitle": "Go shopping",
"participants": {
"0": {
"id": "0",
"name": "John Smith"
},
"1": {
"id": "1",
"name": "Jason Black"
}
}
}
}
}
It's an array of events, where each event has a list of participants. How to make a database rule, where:
everyone can get event or list of events,
when getting an event, a full list of participants can only by visible by admin,
when getting an event, if a user is a participant of the event, the list of participants would retrieve only him, noone else,
when getting an event, if a user is not a participant, the participant list would be empty
Here is my try in rule scheme:
{
"rules": {
"events": {
".read": true,
"$eventKey": {
"eventTitle": {
".validate": "newData.isString() && newData.val().length < 100"
},
"participants": {
".read": "root.child('users/'+auth.uid+'/role').val() === 'ADMIN'",
".validate": "newData.hasChildren()",
"$participantKey": {
".read": "($participantKey === auth.uid || root.child('users/'+auth.uid+'/role').val() === 'ADMIN')",
"id": {
".validate": "newData.val() === $participantKey"
},
"name": {
".validate": "newData.isString() && newData.val().length < 100"
}
}
}
}
}
}
}
It does not work, because when I read events list it doesn't respect .read constraint in participants and $participantKey fields. It just retrieves full list of participants all the time.
#edit
In other words. I have this simplified rules:
{
"events": {
".read": true,
"$eventKey": {
"participants": {
".read": false
}
}
}
}
When I query for: events/{eventKey}/participants I still get an object with participants even though the participants read flag is set to false.
But, when I remove .read flag from events, then retrieving data respects .read flag in participants.
#edit2
From documentation:
A .read rule which grants permission to read a location will also allow reading of any descendants of that location, even if the descendants have their own .read rules which fail.
My question is now, how to omit this rule?
Firebase permissions cascade downwards. Once you've given a user a permission on a certain level in the JSON tree, you cannot revoke that permission on a lower level in the tree.
That means that these rules will not work:
{
"events": {
".read": true,
"$eventKey": {
"participants": {
".read": false
}
}
}
}
The ".read": false is ignored by Firebase.
Instead you will have to structure your data in a way that allows your security requirements. This is done by completely separating the types of data that have different security requirements.
{
"events": {
".read": true,
"$eventKey": {
"participants": {
".read": false
}
}
}
"eventparticipants": {
".read": false
"$eventKey": {
/* This is where you store the participants */
}
}
}
So you have two top-level lists: events and eventparticipants. The lists use the same keys for the objects under them: the event id. But since these are two top-level lists, one can be publicly readable while the other is more restricted.
Firebase documentation recommends against using arrays when adding data to the database. The main problem in your code is that you use an array, which is an anti-pattern when it comes to Firebase.
One of the many reasons Firebase recommends against using arrays is that it makes the security rules impossible to write and this is your case.
Because Firebase is a NoSQL database and becase it is structured as pairs of key and valeu, the solution is to use a Map and not an array. Change the way in which you add data in your database and your problem will be solved.

Ho to get all fields from firebase except one? [duplicate]

I have a collection of signatures where each signature has a few properties: public: fullname, city and then email.
I want to keep the email property private and I've been struggling with writing the correct rules to only return fullname and city. Here is what my rules.json looks like so far:
{
"rules": {
"signatures": {
"$signatureID": {
"public": {
".read": true
},
"email": {
".read": false
}
}
}
}
}
When I go to the /signatures end point, I would like to receive an array of signatures with the public data and not receive the email addresses.
So far I haven't had any luck getting this to work the way I want it to. Am I doing something wrong? Should I structure my data differently?
With respect to security rules, Firebase operations are all-or-nothing.
As a result, attempting to load all of the data at /signatures will fail because your client does not have permission to read all of the data at that location, though you do have permission to read some of the data there. Similarly, writing to a location behaves the same way, and full permission is required before your operation will continue.
To handle this use case, consider restructuring your data like this:
{
"rules": {
".read": false,
".write": false,
"signatures-public": {
".read": true,
"$signatureID": {
// ... public data here
}
},
"signatures-private": {
"$signatureID": {
// ... private data here
}
}
}
}

Resources