Rules for a many to many (join object) association - firebase

I have an Assignment object that "joins" a Project to a Service. Many Users can work on any given Assignment (or a service for a project).
User has many Assignments
Project has many Assignments
Assignment has many Users
Assignment belongs to a Project and a Service
Here is a snapshot of some sample data for a visual.
users: {
1: {
name: 'John Smith',
assignments: {
1: true
}
},
2: {
name: 'Jane Doe',
assignments: {
1: true
}
}
}
projects: {
1: {
title: 'Project #1',
assignments: {
1: true
}
}
}
services: {
1: {
title: 'Service #1'
}
}
assignments: {
1: {
service: 1,
project: 1,
users: {
1: true,
2: true
}
}
}
I'm trying to write a security rule so that a project can only be read by users who are assigned to it. So far I can figure out how to get all the assignments for a given project.
root.child('assignments')[data.child('assignments')]
When nested under a "$project" this rule will return an array of assignments. How can I detect whether one of these assignments has my user (auth.id)?
Or am I thinking about this the wrong way?
Edit: The more I think about it and try to implement this rule, the more I believe it's not possible using Firebase auth rules. I really hope that's not the case.

There's not currently a way to search all children of a rule for a match. A simple answer here is to index users to projects, either by putting a list of valid projects under each user (just as you've done with assignments) or by maintaining a separate list:
/users/$userid/projects/$projectid/true
/project_assignments/$userid/$projectid/true
Either of these would allow you to write the security rule in question:
"projects": {
"$projectid": {
".read": "root.child('users/'+auth.uid+'/projects/'+$projectid).exists()"
// or
".read": "root.child('project_assignments/'+auth.uid+'/'+$projectid).exists()"
}
}

Related

Is it possible to allow access to a doc if the user is a member of a parent document, or parent's parent, recursively?

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.

Firebase: allow anyone from an array of UIDs to access read / write

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()"
}
}
}
}
}

Can I use wildcard to build paths when getting references of firebase database

If I have the following database structure in firebase:
{
level1 : {
0: {
prop1: {},
prop2: {}
},
1: {
prop1: {},
prop2: {}
},
2: {
prop1: {},
prop2: {}
}
}
}
If I wanted to catch only the list of prop1 inside level1, could I build a reference path with wildcards like so: level1/*/prop1?
I don't think this is possible, but I am asking just to confirm, since I didn't find mentions to it in the documentation.
The call to the list of prop1, theoretically, would look like this: firebase.database().ref('level1/*/prop1').
Yes, I am trying to avoid to split those props into different nodes and then having to reference one to the other. Lazy programmer here.
I moved on to test it. It is not possible to use wildcards when building paths to be used to catch firebase database references. Instead, do fetch just prop1, the database structure has to be modified. The new structure will look like the following:
{
level1 : {
prop1level: {
0: {prop1: {}},
1: {prop1: {}},
2: {prop1: {}}
},
prop2level: {
0: {prop2: {}},
1: {prop2: {}},
2: {prop2: {}}
}
}
}
With this new database structure I am able to set different rules for the different properties.

Group-based permissions on Firebase

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?

Filtering Firebase lists based nested child keys?

In the Firebase docs for Structuring Data, they give the follow data structure as an example of mapping users to groups.
{
"users": {
"alovelace": {
"name": "Ada Lovelace",
// Index Ada's groups in her profile
"groups": {
// the value here doesn't matter, just that the key exists
"techpioneers": true,
"womentechmakers": true
}
},
...
},
"groups": {
"techpioneers": {
"name": "Historical Tech Pioneers",
"members": {
"alovelace": true,
"ghopper": true,
"eclarke": true
}
},
...
}
}
With that structure, how would I go about querying only groups with a specific member? So only groups where alovelace is a member, for example.
Would I do this with a rule? If so, what would that rule look like?
OrderByChild works in this case - below the query object
angularfire.database.list('.../groups', {
query: {
orderByChild: 'members/alovelace'+,
startAt: true
}
});
Not sure how the performance is compared to the answer by Frank van Puffelen - might be worse even since it's another list query rather than just a few direct object lookups.
That information is already in the data model. Right under /users/alovelace/groups you have a list of the groups she's a member off.
The reason for recommending this model is that it doesn't even require a query to load the list of groups, it just requires a direct-access read of /users/alovelace/groups.

Resources