firebase security rule for nested data - firebase

I have the following data structure
{
"users": {
"uid": {
"priv": {
"eid": "eid"
}
}
}
}
{
"entity": {
"eid": {
"priv": {},
"pub": {}
}
}
}
Now my requirement is - any user can create an entity if it does not have one entity_id in the "priv" child.
Once an user create an entity, it gets the entity_id under it's priv field.
And the same user can modify the same entity which he has under it's priv field.
One entity can have multiple users!
I have tried with the following rule but it allows any one to modify any entity if it does not have an enity_id(i.e. eid) in priv field.
"entity":{
".write": "root.child('users/'+auth.uid+'/priv/eid').val() == null",
"$eid":{
".read": "root.child('users/'+auth.uid+'/priv/eid').val() == $eid",
".write": "!data.exists() || root.child('users/'+auth.uid+'/priv/eid').val() == $eid"
}
}
How should I re-write the rules?

Related

How to fetch all resources from Firebase database with nested security rules?

In firebase, is it possible to configure a rule so that when a parent resource is fetched, it only returns child items that the user as access to?
I have a data structure like so:
{
application_access_request: {
app_id_1: {
access_key_1: {
email: "abc#b.com"
},
},
app_id_2: {
access_key_2: {
email: xyz#c.com
},
}
}
}
And then I have rules like so:
{
"application_access_request": {
"$appId": {
"$accessId": {
".read": "data.child($accessId).child('email').val() === auth.token.email",
},
}
},
}
As a user logged in with email abc#b.com,
When I request all resources from application_access_request/,
Then I want app_id_1 and it's children to be accessable / returned to the user,
Is it possible to allow reading of all application_access_request but only return apps that the auth'd user has access to?
No, security rules cannot be used to selectively return information (see rules are not filters). You may, however, be able to use querying to solve this use case. For example, if your data was structured:
{
application_access_request: {
app1: {
access_key: "user#example.com"
},
app2: {
access_key: "user2#example.com"
}
}
}
You can use query-based rules to limit querying:
"application_access_request": {
".read": "auth.uid != null && auth.token.email_verified &&
query.orderByChild == 'access_key' &&
query.equalTo == auth.token.email"
}

Firebase rules for comment on a post

What should be the firebase rules for comment on post which is similar to facebook.
There are two things:
first, only authenticated user can comment.
Second, only the user who has commented can delete the comment. The user who has commented his id is saved in username.
I strongly suggest using Firebase Bolt for writing/compiling Firebase Database Security rules. Data structure can get big and complicated. Using Bolt language you'll be able to easily write complex access and structure rules that can be re-used for other db patterns.
Your rules would look something like this:
path /comment/{postUid}/{commentUid} is Comment {
read() { true }
write() { isAuthor(this) || isAuthor(prior(this)) }
}
type Comment {
text : String,
username : String
}
isAuthor(value) { auth != null && value.username == auth.uid }
Pay attention to isAuthor(prior(this)) call. This is the way to make sure only author can delete a comment. prior function returns data as it was saved before current event (create, update or delete).
After using firebase-bolt tool to compile rules to JSON format you'll get:
{
"rules": {
"comment": {
"$postUid": {
"$commentUid": {
".validate": "newData.hasChildren(['text', 'username'])",
"text": {
".validate": "newData.isString()"
},
"username": {
".validate": "newData.isString()"
},
"$other": {
".validate": "false"
},
".read": "true",
".write": "auth != null && newData.child('username').val() == auth.uid || auth != null && data.child('username').val() == auth.uid"
}
}
}
}
}

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.

How to structure data with Firebase to allow data to be user specific and also public?

We are building a platform using Firebase Realtime Database and I'm having a bit of a struggle to find the best way to structure our data for private and public access.
Today we have
database: {
items: {
$userUid: {
$itemUid: {
poster_link: "..."
format: "..."
title: "..."
}
}
}
}
All our items are stored under each user in order to make it fast and secure to load.
Our rules are set up like this
{
"rules": {
"items": {
"$userId": {
"$itemId": {
".read": "auth !== null,
".write": "auth !== null"
}
}
}
}
}
So only an authorised user can read and write the data. I could create something like this to allow items to be public if the value is true:
".read": "auth !== null || data.child('public').val() == true"
But this will still be under $userUid
So I was wondering if you have any suggestion on how to structure this example to allow items to be under a user and also seen publicly, not necessary under this user, a bit like Dropbox does when you share something.
You chosen data structure does not take advantage of the flat data principles of Firebase. This will make it very difficult for you to query items of multiple users. For example, how do you get all the public items without drilling into each user?
Similarly, a boolean called public is also not good because you can't extend it to other ACL scenarios. Much better is an ACL object that can be extended in the future.
For example:
items: {
itemsUid: {
[...],
userId: ...,
ACL: { public: true }
}
}
Now you can write the rule:
auth !== null && (root.child(items/ACL/public).exsists() || data.userId === auth.UID)
If in three months you add a concept of friends that can see you posts or followers that can see you items you can simply add friends: true, followers: true to the ACL object and adjust the rule.
You can structure like this
database: {
items: {
$itemUid: {
poster_link: "..."
format: "..."
title: "..."
user: "userid"
}
}
}
now set the rules as
{
"rules": {
"items": {
"$itemId": {
".read": "auth !== null || data.child('public').val() == true,
".write": "auth !== null"
}
}
}
}

How to add index rules for Firebase database?

I keep getting firebase messages on console regarding to add indexOn for user 4321 at chat rules. Below is my database.
And my rules in firebase is like:
{
"rules": {
".read": "auth != null",
".write": "auth != null",
"users": {
".indexOn": ["name", "email","uid"],
"$uid": {
".write": "$uid === auth.uid"
}
},
"chats": {
"$key": {
".indexOn": ["uid"]
}
}
}
}
I'm not sure why the indexing not working. And how can I improve indexing for overall database?
Your current data structure only allows to easily list all members of a chatroom, not the other way around. That may be the reason you get that message, because if you want to list all chats that user belongs to, you have to search through all /chats records.
You probably need to duplicate the chat room membership data both at /chat/<chat-id>/members and /users/<uid>/groups. Your case is almost identical to the one in the Firebase guide here -- read particularly the description below code in the section linked, but it's best to read the whole guide, it really helped me to understand how the database works.
Btw: your rule in chats: ".indexOn": ["uid"] doesn't do anything with the sample data you posted. It says to "index chat rooms by their uid attribute", but your chat rooms don't have an uid key inside (meaning uid: 'someid', not 'someid': true). See the indexing guide on more info how indexing works.
There are two types of indexing orderByValue: and orderByChild
Indexing with orderByValue::
{
"rules": {
"scores": {
".indexOn": ".value"
}
}
}
JSON Tree
{
"employee": {
<"employee1Key">: {
"empName": Rohit Sharma,
"Designation": SW Developer,
"empId": EMP776,
"branchDetails": {
"branch": Software,
branchLocation: Pune,
branchId: BR556
}
},
<"employee2Key>": {
"empName": Vira tKholi,
"Designation": Accountant,
"empId": EMP908,
"branchDetails": {
"branch": Finance,
branchLocation: Dheli,
branchId: BR557
}
},
<"employee3Key">: {
"empName": MS Dhoni,
"Designation": Sales Manager,
"empId": EMP909,
"branchDetails": {
"branch": Sales and Marketing,
branchLocation: Pune,
branchId: BR556
}
}
}
}
Indexing with orderByChild:
{
"rules": {
"employee": {
".indexOn": ["empName", "empId", "designation"]
}
}
}
Multilevel Indexing:
{
"rules": {
"employee": {
".indexOn": ["empName","empId","branchDetails/branch",
"branchDetails/branchLocation","branchDetails/branchId"]
}
}
}
You can also refer to this link for the Firebase Real-Time Database Data Indexing.
Firebase Real-Time Database Data Indexing

Resources