Firebase - User Based Rule Access On Another Path - path

I'm looking to allow read access to a device list in firebase only if a user has this device id listed under their account.
As recommended by Anant in his answer found here: Security Rules - lists of data that can be read?
I implemented this firebase data structure:
{
devices: {
111: {status: 10},
222: {status: 5}
},
users: {
aaa: {
name: 'Jim',
devices: {
111: {name: 'Test Device'}
}
}
bbb: {
name: 'Jane',
devices: {
111: {name: 'Test Device'},
222: {name: 'New Device'}
}
}
}
}
To clarify the outputs, User "aaa" named Jim should only be able to read device id "111" from the device list and get it's children elements. User "bbb" named Jane can see both devices "111" and "222" and their children elements
So I implemented several rules trials and ended with the following:
{
"rules": {
"users":{
"$user_id":{
".read": "auth.uid == $user_id",
"devices": {
"$devices_id":{
".read":true
}
}
}
},
"devices":{
"$devices_id":{
".read": "root.child('users').child(auth.uid).child('devices').hasChild('$devices_id')",
".write": false
}
}
}
}
When reading device data this rule should look at the /root/users//devices/$devices_id and see if that device_id exists under the user making the request. Then allow read access to that record. According to the post linked above this should be possible, but the post does not express what permissions should be set for the main list of "conversations" (devices in my case)...Anant only says "Make sure to set the permissions on the top-level conversations list appropriately". Can someone clarify how to do this?
I only get permission denied for these requests under the rules above. I have also tried:
"root.child('users').child(auth.uid).child('devices').child('$devices_id').exists()"
And this does not work either...
Any guidance on this process would be greatly appreciated. I am also open to other data structures and rules structures that make this happen. It is a fresh project and very modifiable at this stage.
Thanks!

Thanks to #Kato! The implementation above is correct except for the string literal surrounding $device_id.
Changing
.hasChild('$devices_id')
To
.hasChild($devices_id)
Fixed the problem and allowed only the assigned devices under a user to be readable from the top level devices structure.

Related

Firebase Database index : information and effects

I m using firebase Database .Planning to have quite a large data collection , I'm trying to use indexes . Making some tests, i don t know if rules i implement are correct let alone improve the queries .Is there a way to have a feedback on those indexations (correct or not, improve or not)
right now i have one class as described in the picture and i have the created the following rule
{
"rules": {
".read": true,
".write": true,
"users": {
"$user_id": {
".indexOn": ["user_id", "username"]
}
},
}
Since i get no feedback from firebase, i don t know if this is correct and improves anything .
Firebase's server-side rules are meaningless without seeing the code that exercises them.
But at a first glance it seems you're defining your indexes too low in the tree: indexes need to be defined at the location where you run the query. So if you want to run a query on /users, you need to define the indexes on /users:
{
"rules": {
".read": true,
".write": true,
"users": {
".indexOn": ["user_id", "username"]
}
}
}

IOS: How to get specific changed child from list in Firebase Realtime Database?

I have List of objects in firebase DB. how to get specific child who changed in the list?
Do I have made connection with every child?
Actually yes you have to do that. If you e.g. have users in there, you'll end up with something like this:
{
users: {
"user-0": {
"firstName": "Max",
"lastName": "Powers"
},
"user-1": {
"firstName": "Maxime",
"lastName": "Powers"
}
}
}
Now, from your apps perspective, the user is signed in and therefore the app knows the user-id. With this information you can subscribe to changes on the whole user.
If you go one step further and nest e.g. rides below each user, as a mobility app will do, you end up with a JSON like this:
{
users: {
"user-0": {
"rides": {
"ride-0": {
"updated_at": ...
}
}
}
}
}
When in this mobility app a user creates a ride, the app will will receive a ride-id from the backend which it can use to subscribe to the related ride in firebase. The path would be e.g. users/<user-id>/rides/<ride-id>. The app will then get updates each time this ride is updted in the realtime database.
In the end each user has multiple subscriptions. We do it for a couple of years this way and it works quite well 👍

Firebase Realtime Database Rules - Using a variable as a key in the data key value pair when writing

This is a rough example of what my database looks like.
"userA": {
"uf": {
"userB": "0"
}
},
"users": {
"userA": "0",
"userB": "0",
"userC": "0"
}
And this is a rough example of the rule I am trying to write.
//USER ID
"$uid": {
//USER FRIENDS
"uf": {
//FRIEND USER ID
"$fuid": {
".write": "$uid === auth.uid &&
root.child('users').hasChild($fuid)",
}
},
},
And this is what I am trying to get working in the simulator
//Location /userA/uf/
//Data { "userC": "0" }
It seems that the security rule will always deny a write when the "key" for a data key value pair is a variable in my rules, in this case "$fuid". The simulator will return the messages "Simulated set denied" and "Write denied" but won't give me any additional details. I could get around this by writing the following.
//Simulation Method set
//Location /userA/uf/userC/
//Data { "0": "0" }
But this feels like it's writing unnecessary data to my database. What is the best practice here? Thanks.
Your rules give access to {uid}/uf/{fuid} but you're trying to write at {uid}/uf.
That {"0": "0"} is indeed unnecessary, you can just write "0".
If you want to write multiple friends at once, you can perform a multipath update, or modify your rules to allow writing directly at {uid}/uf and ".validate" the children.
Side note: if your users can be deleted, if user A has user B as a friend and user B is deleted, your rules won't allow user A to remove user B from the friends list. You should take care of that by changing the rules to allow the deletion of friends that do not exist, or by setting up an onDelete() triggered cloud function that would do the cleanup.

Query in firebase Database in iOS

I work with firebase database, I have the following data,
I need to get all groups names (GName) of a user by his phoneNum, i.e. all groups of specific user, How can I get that in swift 4?
You should consider restructuring your data. If a user belongs to more than one group in your application then you'll probably have to duplicate your user node for every group the user belongs to in your data structure. You can create another JSON object that holds all of the groups that a user belongs to. Here is a sample JSON for you:
{
"users": [{
"xyz123": {
"userId": "xyz123",
"username": "user1",
"phoneNum": "123456",
"groups": [{
"groupId": 1,
"groupName": "aaa"
}, {
"groupId": 2,
"groupName": "bbb"
}]
}
}]
}
As for filtering with the phone number, you can get all users inside a list and filter the result with the phone number criteria
result = result.filter({item.phoneNum == "123456"})
or get phone number of the user to a upper level, call .child() method with the phone number criteria and fetch the specific user.
Also take a look at structuring data part at firebase documentation.
https://firebase.google.com/docs/database/ios/structure-data
Hope that helps.

Returning a custom field where value depends on the query in elasticsearch

I have some data like so in elasticsearch:
account (http://localhost:9200/myapp/account/1)
========
name
state
groups //groups is an array containing objects like so {id: 1, starred: true}
//the id is the id of the group type
email (http://localhost:9200/myapp/email/1?parent=1)
========
email
primary //just a boolean to denote whether this email is primary or not
group
========
name
description
emails are children of account.
Based on imotov's excellent answer, I am able to run a search on account.name and email.email and return ALL accounts where the searched prefix matches the above 2 fields:
{
"query": {
"bool": {
"must": [
{
"term": {
"statuses": "active"
}
}
],
"should": [
{
"prefix": {
"name": "a"
}
},
{
"has_child": {
"type": "email",
"query": {
"prefix": {
"email": "a"
}
}
}
}
],
"minimum_number_should_match" : 1
}
}
}
What I would like to do now is to return 2 custom fields for each result:
For each result return a field called email, if the search was matched on the email type (which is a child of account), return that email, otherwise return the primary email linked to the account, if none, null can be returned.
For each result return a field called group. The value of the field should contain the name of the starred group whose id is stored in the groups array. Essentially: Find group.id where group.starred is true in each account.groups, then return the matching group.name from the group type base on the id we found.
I have been looking at script fields, but I am not sure if it is able to return fields for each hit. I am also not sure if the above is actually accomplishable in ES.
Could someone provide some pointers as to whether this is possible and how to get started?
Currently, there is simply no way to get to access the data in a has_child or nested clause.
The only solution is to get some data, make some decisions on the client and then get more data.
Here's what I did to achieve it:
Run the query above and get back the data.
To deal with showing the match emails or the primary email (run on the email type):
{"query":
{"bool":{
"should":{
{
"prefix":{
"email" : "the query"
}
},
{
"terms":{
"_parent" : ["list", "of", "account", "ids"]
}
}
}
}
}
}
Base on the above query, we can get any email addresses that were matched by the search term. Remember to set the fields in the above query to include _parent.
We can then use array_diff() or a similiar function in languages other than PHP to diff the parent ids from above and and original query. This should then give us a list of accounts where no email was matched. Simply then send another request to get the primary emails for those accounts.
For the groups, send a query to type account and:
Constrain _id to the list of account ids.
Constrain group.starred to true.
This should get you a list of starred groups. To get more information (name etc), send a query to type group and:
Constrain _id to the group ids above.
Finally, do some client side processing to put it together so that it is easier for the next part of your program to use.

Resources