Skip to content

Instantly share code, notes, and snippets.

@katowulf
Created November 16, 2018 19:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save katowulf/713e0ce7e94ff571ecfd3c7921d6f872 to your computer and use it in GitHub Desktop.
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/
{
"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