I am trying to add a simple rule to firebase database. the child node activities have proper access but the leads don't. if I sent the parent read true and then child rules are ignored. how can I set parent true to public but child restricted?
{
"rules": {
"leads": {
"$activity": {
".read":
"root.child('users_business_activities').
child(auth.uid).hasChild(data.child('category').val())",
".write" :
"root.child('users').child(auth.uid).child('isAdmin').val() == true"
}
},
"users": {
"$uid": {
".read": "auth.uid == true",
".write": "auth.uid == $uid"
}
},
"business_activities": {
".read": "auth.uid == true",
".write": "false"
}
}
}
In the Firebase Realtime Database permissions cascade downwards. Once you give a user read permission on /leads, you can't take that permission away lower in the JSON tree.
This has a few consequences:
Security rules cannot be used to filter data. This is known as rules are not filters in the Firebase documentation. I also recommend reading some of the many existing questions on this topic.
You will often need to create a lookup list of the activity IDs that a user has access to. Such a lookup list is often known as an "index" in Firebase terms. The documentation on creating scalable data structures has a good example of such a structure.
If part of the data for an activity/lead needs to be publicly readable, and part needs to remain private, you'll want to split the public and private parts into separate top-level nodes. This splitting is known as flattening you data structure in the docs. I also gave an answer here to show how to use this for user profile information.
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 am having trouble in writing firebase permissions. I want those users if authenticated only write to users section and Everyone else should be able to read or write to any section of the database. Is there any way that I can define rules for every table default to true and restrict only user section to be authenticated or I have to explicitly write rules for every table.
PS. It would be great if someone could guide me what rules should I implement for an app with features for sending and receiving a message with the following structure:
-Chat
-Friends
-Users
-message_notifications
-messages
-notifications
/* Visit https://firebase.google.com/docs/database/security to learn more about security rules. */
"rules": {
"Users":{
"$uid":{
".read": true,
".write": "auth.uid == $uid"
}
}
}
}
Is there any way that I can define rules for every table default to true and restrict only user section to be authenticated?
Once a user has access to data at a certain level in your database, they have access to all data under that level. You cannot revoke this permission on a lower level. So there's no way to give a user access to all data at the root, and then exclude one node.
What you can do is use the $ wildcard rules to create two types of top-level nodes:
{
"rules": {
"Users":{
"$uid":{
".read": true,
".write": "auth.uid == $uid"
}
},
"$others": {
".read": true,
".write": true
}
}
}
With the above rules, users can:
Only read the /Users/$uid node of a user if they know the UID of that user.
Can only write their own /Users/$uid node.
Can read and write all other data.
I'm having issues reading and writing data from my database on the client (my iOS app) using the following database rules:
// Checks auth uid equals database node uid
// In other words, the User can only access their own data
{
"rules": {
"posts": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
}
}
}
However, I have no issues reading and writing my data when using the following rule:
{
"rules": {
".read": "auth != null",
".write": "auth != null"
}
}
My goal is to have each user only have the ability to read/write their own data. Any suggestions/help would be appreciated.
EDIT:
When attempting to post I use the following (iOS):
let key = ref?.childByAutoId().key
let post = ["uid": key,
"title": titleField.text,
"description": descField.text]
ref?.child(key!).setValue(post)
When I want to retrieve those data entries, currently I am attempting to retrieve the data entries by looking at the reference point ("task") (in iOS, my database reference is the following):
ref = Database.database().reference().child("task")
ref.observe(.childAdded, with: { (snapshot) in
print(snapshot)
guard let dictionary = snapshot.value as? [String : AnyObject]
else {
return
There are a couple things going on here.
First as Doug answered, your rules have to match your query.
Second, when using childByAutoId() you are creating a random key. Instead you should be using the UID of the logged in user.
And third when reading from the database you have to make sure to read from the location where you have placed your rules. Currently you are trying to read the entire list were there are no rules defined. (When no rules are defined Firebase defaults to false) Instead you should read the child of the list.
You can also take a look at my answer here for some more explenation and links to relevant docs.
Your rules don't match your queries. Your query is accessing documents at a node called "task", but your rules are protecting a node called "posts". You should edit your rules to match the queries you intend to protect.
I am building a simple Firebase application with AngularJS. This app authenticates users through Google. Each user has a list of books. Anyone can see books, even if they are not authenticated. Only the creator of a book can edit it. However, individual users need to be able to record that they've read a book even if someone else added it.
I have rules.json like so:
{
"rules": {
".read": false,
".write": false,
"book": {
"$uid": {
".write": "auth !== null && auth.uid === $uid",
}
".read": true,
}
}
}
And I am trying to write a book simply with:
$firebaseArray(new Firebase(URL + "/book")).$add({foo: "bar"})
I get a "permission denied" error when trying to do this although I do seem to be able to read books I create manually in Forge.
I also think that the best way to store readers would be to make it a property of the book (a set of $uid for logged-in readers). ".write" seems like it would block this, so how would I do that?
"$uid": {
".write": "auth !== null && auth.uid === $uid",
"readers": {
".write": "auth !== null"
}
},
It seems like a validation rule would be appropriate here as well ... something like newData.val() == auth.uid, but I'm not sure how to validate that readers is supposed to be an array (or specifically a set) of these values.
Let's start with a sample JSON snippet:
"book": {
"-JRHTHaIs-jNPLXOQivY": { //this is the generated unique id
"title": "Structuring Data",
"url": "https://www.firebase.com/docs/web/guide/structuring-data.html",
"creator": "twiter:4916627"
},
"-JRHTHaKuITFIhnj02kE": {
"title": "Securing Your Data",
"url": "https://www.firebase.com/docs/security/guide/securing-data.html",
"creator": "twiter:209103"
}
}
So this is a list with two links to articles. Each link was added by a different user, who is identified by creator. The value of creator is a uid, which is a value that Firebase Authentication provides and that is available in your security rules under auth.uid.
I'll split your rule into two parts here:
{
"rules": {
".read": false,
"book": {
".read": true,
}
}
}
As far as I see your .read rule is correct, since your ref is to the /book node.
$firebaseArray(new Firebase(URL + "/book"))
Note that the ref below would not work, since you don't have read-access to the top-level node.
$firebaseArray(new Firebase(URL))
Now for the .write rules. First off is that you'll need to grant users write-access on the book level already. Calling $add means that you're adding a node under that level, so write-access is required.
{
"rules": {
"book": {
".write": "auth != null"
}
}
}
I leave the .read rules out here for clarity.
This allows any authenticated user to write to the book node. This means that they can add new books (which you want) and change existing books (which you don't want).
Your last requirement is most tricky. Any user can add a book. But once someone added a book, only that person can modify it. In Firebase's Security Rules, you'd model that like:
{
"rules": {
"book": {
".write": "auth != null",
"$bookid": {
".write": "!data.exists() || auth.uid == data.child('creator').val()"
}
}
}
}
In this last rule, we allow writing of a specific book if either there is no current data in this location (i.e. it's a new book) or if the data was created by the current user.
In the above example $bookid is just a variable name. The important thing is that the rule under it is applied to every book. If needed we could use $bookid in our rules and it would hold -JRHTHaIs-jNPLXOQivY or -JRHTHaKuITFIhnj02kE respectively. But in this case, that is not needed.
First off the "permission denied" error. You are getting this error because you are trying to write directly in the "book" node instead of "book/$uid".
Example of what you do now:
"book": {
"-JRHTHaIs-jNPLXOQivY": { //this is the generated unique id
"foo": "bar"
},
"-JRHTHaKuITFIhnj02kE": {
"foo": "bar"
}
}
In your rules you have a global rule for write set to false so that will be the default and next to that you have made a rule for the specific node book/$uid. So when trying to write directly in "book" it will take the default rule that was set to false. Have a look at Securing your data for more information about firebase rules.
And for the last part of your question i suggest you take a look at Structuring data for more information about the best ways to structure your data inside firebase.
So taka a good look at what and how you want to save and write in firebase and make sure your rules are structured accordingly.
I would like to know if it is possible to implement Firebase security in such a way as to allow a each user of my app full access to data in their own location, while enabling the user them self's to enable or disable the type of access from other users to their own data? Or basically, is it possible to implement simple sharing, Dropbox or Google Drive style, among the users of my app, in such a way that it is strictly enforced?
Since Firebase security rules allow you to reference data in Firebase, you can base the security rules on anything you can create data for. So yes, you could allow a user to share their own data in just about any way you could scheme up.
To contrive a simplified example based on the dropbox idea, I could have a "shares" folder under my data, and a security folder where I store access rights:
/security/$user_id/$friend/... // where I put the access rights
/folders/$user_id/shares/... // where I put the shared files
Now I could control access to it by putting user names and a list of folders they can access into my Firebase data:
/security/$user_id/$friend_id = /never/gonna/give/you/up = true
Now in my security rules, I can write something like this:
{
"security": {
"$user_id": { // only authenticated user may read/write his rules
"shares": {
".read": "auth.id === $user_id",
".write": "auth.id === $user_id"
}
}
}
"folders": {
"$user_id": {
// only authenticated user may read/write his folders
".read": "auth.id === $user_id",
".write": "auth.id === $user_id",
"shares": {
// but my friends can read data in shares
".read": "root.child('security/'+$user_id+'/'+auth.id+'/shares').val() === true"
}
}
}
}
Note that one limitation on this (for the time being) is that security rules cannot work recursively or in any nested manner. However, since the rules are permissive (if any parent of the path allows access, then it is allowed), you can work around this.
You could need to place a hard limit on the max number of child paths and manually declare them in the rules like so:
// allow sharing up to 3 levels deep
"shares": {
".read": "root.child('security/'+$user_id+'/'+auth.id+'/shares').val() === true",
"$child1": {
".read": "root.child('security/'+$user_id+'/'+auth.id+'/shares/'+$child1).val() === true",
"$child2": {
".read": "root.child('security/'+$user_id+'/'+auth.id+'/shares/'+$child1+'/'+$child2).val() === true",
"$child3": {
".read": "root.child('security/'+$user_id+'/'+auth.id+'/shares/'+$child1+'/'+$child2+'/'+$child3).val() === true",
}
}
}
}
Not the prettiest thing to look at, but a good temporary solution until Firebase gets some nesting features.