I'm developing an app using Firebase's Realtime Database and need to allow multiple users to access the same data, but I'm having trouble figuring out a security rule that makes this work.
The database looks like this:
teams: {
teamID3ic3kic9w3jkck : {
userIDs: ["11111", "22222", "33333", "44444"]
teamData { ....}
}
}
where I want to allow users with an ID matching any of the IDs in the "userIDs" array to access "teamData". Would really appreciate help figuring this out.
Every time you're looking to do array.contains(), you're likely using the wrong data structure. For example, this seems more like a mathematical set to me: an unordered collection of unique items. In Firebase you'd model that as:
teams: {
teamID3ic3kic9w3jkck : {
userIDs: {
"11111": true,
"22222": true,
"33333": true,
"44444: true"
]
teamData { ....}
}
}
Now you can secure this with:
{
"rules": {
"teams": {
"$teamid": {
".read": {
".read": "data.child('userIDs').child(auth.uid).exists()"
}
}
}
}
}
Related
Here's my data structure:
folders(collection): {
folderA: {
members: { user123: { role: "author" } }
folders(collection): {
folderB: {
parent(reference): /folders/folderA
members: { userABC: { role: "author" } }
folders(collection): {
folderC: {
parent(reference): /folders/folderA/folders/folderB
fieldA: { content: 'x' }
}
}
}
}
}
}
users: {
user123 :{
name: John
}
userABC :{
name: Bob
}
}
My goal is to allow access to a document by a user that is a member of any parent above the currently requested document. The hope is to allow access to fieldA by both user123 (John) and userABC (Bob) without writing rules specific to folderA, folderB, and folderC. The idea is that potentially there could be folders at least 20 deep.
Even though I know rule functions are limited, this is how I wish I could write the rules for this case:
match /databases/{database}/documents {
match /{path=**} {
allow get: if resource.data.members[request.auth.uid].role == 'author';
allow get: if isParentAuthor(resource);
function isParentAuthor(resource){
if(exists(resource.data.parent) {
if( get(resource.data.parent.path).data.members[request.auth.uid].role == 'author' ) {
return true;
} else {
return isParentAuthor( get(resource.data.parent.path) );
}
} else {
return false
}
}
}
}
Right now I have many levels deep of access to the folders hard coded into my rules. But it would be great to be able to do this recursively. Is that possible? I know that this would create a situation where there could potentially be quite a few document requests, but I think I'm generally okay with that. But maybe there's a more Firebase-y way to pull this off with fewer requests?
There are limitations with security rules that make it impossible to do what you're saying here.
First, there is a limit of 10 document gets per rule invocation. This is a hard limit.
Second, there is no recursion allowed. The rule will immediately fail if a function calls itself.
As suggested in comments, flatten your collections. Put a field in the document to indicate where it's logically nested compared to other documents. Having large collections is not a big deal with Cloud Firestore. You will be able to filter as much as you need with queries.
These are my firestore security rules:
service cloud.firestore {
match /databases/{database}/documents {
match /collectionA/{someID} {
function checkA() {
return get(/databases/$(database)/documents/collectionA/$(someID)/users/$(request.auth.uid)).data.deleteFlag != true
}
function checkB() {
return get(/databases/$(database)/documents/collectionB/$(request.auth.uid)/companies/$(someID)).data.deleteFlag != true
}
allow read, write: if checkA() || checkB()
}
}
}
And this is my database objects:
project {
collectionA {
companyA {
users {
r9Myn4TfzAVpSZGzyaet {
deleteFlag: false
}
}
}
}
collectionB {
aAzUlfztdYdEIXT3Tva73kCiuy93 {
companies {
companyA{
deleteFlag:false
}
}
}
}
}
And I tried Simulator :
simulation type : get
location : collectionA/companyA
provider : password
Firebase UID : aAzUlfztdYdEIXT3Tva73kCiuy93
I expected this security rules return "true".
Because I thought checkA returned "false" and checkB returned "true".
But they returned "false".
Please let me know if you have any ideas or suggestions.
I heard that the simulator sometimes does not work properly.
I threw the query from the client and verified it and it worked correctly.
So I thought it was a bug in the simulator.
However, I received the following response from Developer Platform Support.
FYI
The get function will get an error if trying to get a value for an
object that does not exist. When an error occurs in the condition
judgment, the security rule is not applied in the security rule.
Because of the specifications of security rules, it is impossible to
control on nonexistent objects
I would like to give permissions to users based on their usergroups.
Simply checking if a user is a member of that group wouldn't work though, as I want to be able to dynamically create usergroups and set their permissions (without having to change the Firebase Rules).
That's how my data is structured:
users: {
user_1: {
name: "John Smith",
creation_date: "1234567890000"
usergroups: {
usergroup_1: true,
usergroup_4: true
}
}
...
}
usergroups: {
usergroup_1: {
name: "admin",
creation_date: "1234567890000"
permissions: {
manage_users: true,
manage_chat: true,
manage_calendar: true,
...
}
}
usergroup_2: {
name: "moderator",
creation_date: "1234567890000"
permissions: {
manage_chat: true
}
}
...
}
Note that users may have more than one usergroup, each with a different set of permissions (might be the same).
An example: users will only be able to manage the chat if they are in at least one usergroup that allows them to do so.
Any idea on how I could accomplish this? Maybe by structuring the data on a different way?
I have a Firebase data structure that looks like:
-messages
-myFirstUsername
-5-2-16 04:02:23 AM
-message: 'messageOne',
-direction: 'outgoing'
-5-3-16 04:07:23 AM
-message: 'messageTwo',
-direction: 'outgoing'
-mySecondUsername
-5-4-16 04:02:23 AM
-message: 'messageOne',
-direction: 'outgoing'
-5-5-16 04:02:23 AM
-message: 'messageTwo',
-direction: 'incoming'
I would like to index all data using the message and direction nested child keys. Since the username keys under the messages object are dynamic, I'll need to set up my index on the messages object itself.
Will the following rule set up indices two levels deep?
{
"rules": {
"messages": {
".indexOn": ["message", "direction"]
}
}
}
You can specify dynamic .indexOn like this:
{
"rules" : {
"messages" : {
"$userId": {
".indexOn" : ["message", "direction"]
}
}
}
}
I can't seem to find it in the new docs, but here's the old docs description: https://www.firebase.com/docs/security/api/rule/path.html
I'm new using Firebase and NoSQL databases. I wonder if it's possible to do this:
Having an user database I want to fetch data by email or UID. Is it optimal to mix both fields as ID and later filter with queryStartingAtValue or queryEndingAtValuemethods?
{
"users": {
"user1#gmail.comDjDJSADfgg": {
dataUser1: ----- },
"user2#gmail.comfmadaDkK": {
dataUser2: ----- },
"user3#gmail.com3Dkdjakdja4": {
dataUser3: ----- },
"user4#gmail.comKdsadASD": {
dataUser4: ------ },
}
}
Although you can use queryStartingAtValue and queryEndingAtValue, this solution is not so clear, imagine that someone else reads your code (or even you after 6 months), would he/she knows what you meant by queryStartingAtValue? Probably not.
I think is clearer to use orderByChild and equalTo
e.g.
var ref = new Firebase("yourdb");
ref.orderByChild("email").equalTo("blahblah#gmail.com")
.on( anEvent, function(snapshot) {
console.log(snapshot.key());
});