Created
November 16, 2018 19:20
-
-
Save katowulf/713e0ce7e94ff571ecfd3c7921d6f872 to your computer and use it in GitHub Desktop.
Group inheritance and doc sharing in Firebase rules. See https://roles-example-rtdb.stackblitz.io/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"rules": { | |
".read": false, | |
".write": false, | |
"roles-example": { | |
// grant global read to our admin tools | |
".read": "auth.uid === 'ACCESS_MANAGER'", | |
// The documents which we are restricting access to | |
"docs": { | |
// I can query for a list of documents I own | |
".read": "auth !== null && query.orderByChild === 'owner' && query.equalTo === auth.uid", | |
"$docId": { | |
// I can read a document if... | |
// 1. I own the document | |
// 2. I exist in the access table (accessMap/$groupId/{auth.uid} === true) | |
// | |
// My uid will exist in the access table when... | |
// 1) I am a member of a group which is allowed to view this document | |
// (docs/$docId/groups/$groupId => groups/$groupId/members/$uid) | |
// 2) I am a member of a subgroup allowed to view this document | |
// (docs/$docId/groups/$groupId => groups/$groupId/groups/$subGroupId => groups/$subGroupId/members/$uid) | |
".read": "auth !== null && (data.child('owner').val() === auth.uid || data.parent().parent().child('accessMap/members/'+$docId+'/'+auth.uid).val() === true)", | |
"allowed": { | |
"groups": { | |
"$groupId": { | |
".write": "auth.uid === 'ACCESS_MANAGER'", | |
".validate": "newData.val() === true" | |
} | |
} | |
} | |
} | |
}, | |
// group memberships, we keep this separate from groups/ so that | |
// if the members list is long (e.g. 10k members) we can still | |
// query and download groups without a big performance hit | |
"members": { | |
// world readable, we could restrict this so that | |
// one only sees groups they are already a part of | |
// but that's too complicated for demo app | |
// world readable, we could restrict this as well so | |
// one can only see groups they are already a part of | |
// but that's too complicated for the demo | |
".read": true, | |
"$groupId": { | |
// group must exist in the groups/ path to update it here (can't create non-existing groups) | |
".validate": "data.parent().parent().child('groups').child($groupId).exists()", | |
"$memberId": { | |
".write": "auth !== null", | |
// alternative: can only change my own memberships | |
// ".write": "auth.uid === $memberId" | |
".validate": "newData.val() === true" | |
} | |
} | |
}, | |
// group description and inherited groups | |
"groups": { | |
// world readable, we could restrict this as well so | |
// one can only see groups they are already a part of | |
// but that's too complicated for demo app | |
".read": true, | |
"$groupId": { | |
// cannot create new groups | |
".validate": "data.exists()", | |
"name": { ".validate": "newData.isString()" }, | |
"inherit": { | |
"$subGroupId": { | |
// alternative: I have to be a member of the group to modify this | |
// ".write": "root.child('members').child($groupId).child(auth.uid).exists()" | |
".write": "auth.uid !== null", | |
// child id must not be parent id (can't be a child of itself) | |
// enforced by $groupId !== $subGroupId | |
// | |
// child group must exist | |
// enforced by ...parent().child($subGroupId).exists() | |
// | |
// but we aren't really worried about recursive groups; | |
// we handle that when we compile access | |
// | |
// the only valid value is true | |
// enforced by newData.val() === true | |
".validate": "newData.val() === true && $groupId !== $subGroupId && data.parent().parent().parent().child($subGroupId).exists()" | |
} | |
} | |
} | |
}, | |
// Read only access privileges, maintained by Functions and compiled whenever | |
// docs/$docsId/groups, groups/$groupId/members, or groups/$groupId/groups is updated. | |
"accessMap": { | |
// Provides a map of documents to uids allowed to read that document | |
// See /docs/$docsId to learn how this path is used | |
"members": { | |
// I cannot list all documents in this tree or download the entire tree | |
// The following read rule would allow querying for a list of docs I'm allowed to read | |
// ".read": "auth.uid != null && query.orderByChild == auth.uid && query.equalTo == true", | |
"$docId": { | |
// If I have access to a document, I can get a list of the members | |
".read": "data.child(auth.uid).exists()", | |
// Allow our Functions uid access | |
".write": "auth.uid === 'ACCESS_MANAGER'", | |
"$userId": { | |
// I can see if I have access to a specific document | |
".read": "auth.uid === $userId", | |
".validate": "newData.val() === true" | |
} | |
} | |
}, | |
// Provides a map of groups to documents they grant access to. This is for | |
// reverse lookups when deleting groups, in order to efficiently remap | |
// the accessMap/membership mappings | |
"groups": { | |
"$groupId": { | |
"$rootGroup": { // where "root" is a group listed with access to a doc | |
"$docId": { | |
// no read access to this index; it's purely for Functions to use | |
// allow our Functions uid access | |
".write": "auth.uid === 'ACCESS_MANAGER'", | |
".validate": "newData.val() === true" | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment