Say I have a database with the following structure:
Permissions
$pId (auto generated id from push)
userId
roomId
permission
User
$uId
Name
Room
$rId
Name
Is it possible to write a rule that says "allow user to modify room if there exists a permission p where p.userId = auth.uid and p.roomId = $rId and p.permission = 'admin'"?
As far as I can tell it isn't possible without nesting permission information under each room.
You'll need to change your structure a bit. Instead of using a push id to identify permissions, use a more controlled structure. For example:
"Permissions": {
"roomId": {
"userId": "role"
}
}
Now you can secure the room as you want with:
{
"rules": {
"Room": {
"$rId": {
".write": "root.child('Permissions').child($rId).child(auth.Id).val() == 'admin'"
}
}
}
}
An added advantage is that you don't have to scan all the permissions if you every want to revoke a user's permission.
Related
A super admin is an user with special uid which is able to access of everyone to edit their profile and publish new content.
How to make a super admin?
Consider using custom user attributes. It is more efficient and cheaper than using Real Time Database to lookup if a user is an Admin on every authenticated request:
https://firebase.google.com/docs/auth/admin/custom-claims
You would set the Admin role on the user upon creation:
admin.auth().setCustomUserClaims(uid, {admin: true})
You can propagate it to the client after ID token refresh.
currentUser.getIdToken(true)
Then you can simply enforce the rule:
{
"rules": {
"adminContent": {
".read": "auth.token.admin === true",
".write": "auth.token.admin === true",
}
}
}
If you don't use rules or Firebase RTDB, then enforce it on your backend by parsing it from the ID token via Firebase Admin SDK:
// Verify the ID token first.
admin.auth().verifyIdToken(idToken).then((claims) => {
if (claims.admin === true) {
// Allow access to requested admin resource.
}
});
Basically, it's all about data structure and the belonging security rules.
To get started, build a data structure where you have some kind of user roles in it.
For example like this:
{
"data" : {
"user1id" : {
"name" : "MisterX"
},
"user2id" : {
"name" : "John Doe"
}
},
"users" : {
"user1id" : {
"role" : "admin"
},
"user2id" : {
"role" : "member"
}
}
}
Each user has a property called role.
Now you can define your security rules and make use of the role property to define the right access rights:
"data" : {
"$userid" : {
".read" : true,
".write" : "root.child('users').child(auth.uid).child('role') === 'admin'",
}
}
In the case above just admins are able to write to the data/userid node. You can apply this to all the different nodes you want to.
Was going through some articles for understanding Rules & Permissions in Firebase and then came across conditions like this, for write operation :
{
"rules": {
"users": {
"$uid": {
".write": "$uid === auth.uid"
}
}
}
}
I went with an understanding that $uid stands for Users Push ID and it applies to all dynamic ID's that are generated for Users node.
Then saw this rule else were :
{
"rules": {
"articles": {
"$article": {
"title": {
".write": "auth != null",
".validate": "newData.isString() && newData.val() != ''"
}
}
}
}
}
If $article stands for Push ID for articles node then Push ID for users node also should have been $user. Isn't it? What is the standard naming convention for declaring Push ID, when configuring rules, so that Firebase parses/understands them correctly.
Lastly, what does auth.uid stands for?
Lets start with auth.uid, this stands for the uid of the authenticated user.
Next up are $user and $article, these wildcard paths so they can be anything, not only push id's. Check out the docs for more info.
In your first example $uid is a wildcard for user id's. And with the write rule you check that the authenticated user can only write to his own location so it will be something like this (using names instead of uid's for clarity):
"users" : {
"Henk": {//Only Henk can write here
},
"John": {//Only John can write here
}
}
As for naming of wildcard paths there is no convention as far as i know. Personally i use descriptive names so i know what it is. Always $uid when using the users uid as a path and for the rest something like $objectID for object id's. (These can be push generated or something homebrew)
For the rest I suggest you take some time to read all the docs about security rules.
I've a users database on firebase realtime database.
My structure is:
Users
-> uid
-> user_infos (in this level I've a property called: 'cpf')
So I created a .validate rule for cpf:
{
"rules": {
"users": {
"$user_id": {
"$cpf": {
".validate": "!data.exists()"
}
}
}
}
}
The problem is when I try to save user, I receive a PERMISSION_DENIED error.
I'm using firebase javascript lib, 3.4.1
the method that I use for save user is:
firebase.database().ref('users/' + uid).set(userDb);
My intention is that cpf property to be unique on database.
Thank you.
I fixed it, doing manually a verification to firebase. It's not so great solution, but worked.
I'm writing a note sharing app and I'm trying to find the best approach for the data structure to allow adding collaborators to user notes, while at the same time having sensible security rules for the structure in question.
What I have now is the following:
"users": {
"johndoe": {
"notes": {
"note1Key",
"note2Key"
}
},
"jane": {
"notes": {
"note3Key",
"note4Key",
"note5Key"
}
}
...
},
"notes": {
"note1Key": {
// actual note data
},
"note2Key": {
// actual note data
},
"note3Key": {
// actual note data
},
...
},
"shared": {
"johndoe" : {
"note5Key" : true,
"note3Key" : true
},
"jane" : {
"note1Key" : true
}
...
}
When "John Doe" creates a note, the note is stored in notes/noteKey with read/write access granted to owner and collaborators added by the owner. Additionally the note's key is stored in user/johndoe/notes/noteKey, which can be read and written to only by him. When this user wants to add a collaborator ("Jane") to his note, this same note key is stored in shared/jane/noteKey which can be globally read & written to. This way, when listing each user's notes, I have to read from only 2 locations to list all notes a user has access to: user/johndoe/notes and shared/johndoe.
Is there a better approach? I don't like to have the shared index globally accessible, could I somehow limit it? Since one user can potentially collaborate with a big number of different users on different notes, I'm not really sure how to set the security rules, to limit the read/write access to this index.
I was thinking about reversing the shared node logic, to store note key's under it's respectful owners sub-nodes and including a list of collaborators like so: shared/jane/noteKey/collaborators/johndoe. This way I could have a global read rule and a more restrictive write rule (each user can only write in his own shared node), however this would greatly increase the complexity of listing all notes a user has access to.
You wanted to:
allow adding owner & collaborators to user notes.
list all notes a user owned.
list all notes a user has access to.
You should have added collaborators list to each notes as follows:
{"rules":{
"users": {
"$user_id": {
"profile_and_settings":{
".write":"auth != null && auth.uid == $user_id"
},
"owned_notes":{
".write":"auth != null && auth.uid == $user_id",
"$note_id":{}
},
"accesssible_notes": {
".write":"auth != null",
"$note_id":{}
}
}
},
"notes": {
"$note_id": {
// to edit this node: must authenticated, new entry or owner of this node.
".write":"auth != null && ( !data.exists() || data.child('owner').val() == auth.uid )",
"owner":{
".validate":"newData.val() == auth.uid"
},
"collaborators":{
"$user_id":{}
},
// ... other note data
}
//...
}
}}
See related question:
Firebase rule: Do we have better ways to manage object ownership?
I'm working on an app using Ionic Framework and Firebase. I have the following data structure on Firebase:
Users {
mary#fb,com: {
Group: {
group123: {
Contacts: {email1#gmail.com, email2#gmail.com, etc. }
}
group456: {
Contacts: {email3#gmail.com, email4#gmail.com, etc. }
}
}
}
leo#fb.com: {}
wayne#fb.com: {}
etc.
}
Users on the app can create groups and invite their friends to a group.
I'm trying to figure out how to give "email1#gmail.com", "email2#gmail.com" etc. access to the path Users/mary#fb,com/Group/group123 using Firebase rules. I'm also having trouble giving mary#fb,com permissions to read and write. How do I use rules like below for using a custom Unique ID like the the User's email?
{
"rules": {
"Users": {
"$user_id": {
".read": "$user_id === auth.uid",
".write": "$user_id === auth.uid"
}
}
}
}
Circumventing use of the user's auth uid as the unique identifier should be discouraged and probably is only going to make you sad. I'd rethink this approach and encourage others not to follow suit.
Assuming you can't avoid this, then the following will be necessary:
implement your own auth schema
sign your own tokens
include email as part of the token data or, depending on your use case (it helps a great deal to share this in the question, see XY Problem) maybe just use an escaped email as the uid
refer to auth.email in place of auth.uid in your security rules
Thus, in a server/node/etc script:
// after some auth process to verify
// the user and obtain the email
var FirebaseTokenGenerator = require("firebase-token-generator");
var tokenGenerator = new FirebaseTokenGenerator("<YOUR_FIREBASE_SECRET>");
var token = tokenGenerator.createToken({uid: uniqueIdOrMaybeEscapedEmail, email: escapedEmailAddress});
And in your rules:
".read": "$user_id === auth.email",