All Hands, May 2016
tyranid-gracl
Motivation
Engineering
Product
A
B
C
Want to let Product access everyone in Engineering, except for user B
Motivation
Engineering
Product
A
B
C
old permissions model, basic user ACL
Motivation
Engineering
Product
A
B
C
New model - expressing same permissions more concisely
Motivation
Inheritance based on database model structure
Allow / deny at different levels of specificity
What we basically want
tyranid-gracl
tyranid-gracl
Subject:
"The thing / person doing the accessing"
Accessing
Sandy can view twitter, Sandy is the subject
Resource:
"The thing / person being accessed"
Accessing
Everyone in twitter can view Sandy's tweets, Sandy is now the resource
Subject Hierarchy
Sandy has access to twitter (resource) through the subject hierarchy, because her organization, and transitively her team, was given access to twitter.
Organization (subject)
Teams (subjects)
Sandy
This allows access to cascade to lower level subjects
Users (subjects)
Twitter (resource)
Resource Hierarchy
Sandy has access to Bill's photos (resources) through the resource hierarchy, because she can access Bill.
Bill (resource)
This means that access to an "upper level" resource implies access to "lower level" resources.
Bill's Photos (resources)
Sandy (subject)
Permissions Hierarchy
own
edit
view
implies
implies
own
own
Bill owns his photos, so he can implicitly edit and view them
CRUD / Abstract
own
edit
view
delete
view_network_map
view_communications_metadata
Permissions that are relevant to Tyranid collections (without needing to specify the collection) -- used internally by Tyranid for automated authentication
Permissions that aren't related to specific collections, but refer to concepts
CRUD / Abstract
[
// generic crud permissions for filtering (not abstract)
{ name: 'view', abstract: false, parents: [ 'edit', 'delete' ] },
{ name: 'edit', abstract: false, parent: 'own' },
{ name: 'delete', abstract: false, parent: 'own' },
{ name: 'own', abstract: false },
// view-org -> view-group -> view-user
{ name: 'view-user', collection: true, parents: [ 'view-group' ] },
{ name: 'view-group', collection: true, parents: [ 'view-organization' ] },
// network permissions
{ name: 'view_network_map_people', abstract: true },
{ name: 'view_network_map_plans', abstract: true },
{ name: 'view_network_ona_team', abstract: true, parent: 'view_network_ona_individual' },
{ name: 'view_network_ona_individual', abstract: true },
]
Permission Document
/**
* (Permissions model can be accessed via Tyr.secure.permissionsModel)
Example Document
*/
{
"_id" : ObjectId("572e75b37b81c2399c1033bc"),
"resourceId" : "o00560c1d577a9c381622ea7b7b",
"subjectId" : "u005567f2ab387fa974fc6f3a70",
"subjectType" : "user",
"resourceType" : "organization",
"access" : {
"edit-user" : true,
"edit-organization" : false,
"edit-group" : true,
"edit-dataAdapter" : true,
"edit_permissions" : true
}
}
Subject hierarchy
Resource hiearchy
Users
Teams
Organizations
Organizations
Teams
Users
Items
view?
What if we want to check if a user can view a given component?
const canView = await item.$isAllowed('view-triangleLayerItem', user);
view?
Subject hierarchy
Resource hiearchy
Check 1 / ?
First check, does a permission document exist directly relating the user to the component?
view?
Subject hierarchy
Resource hiearchy
Check 2 / ?
Next, recurse up the subject Hierarchy, checking if the users teams have a relevant permission, returning if deny or allow is found.
view?
Subject hierarchy
Resource hiearchy
Check 3 / ?
Recurse again to the users organization if no deny or allow has been found
view?
Subject hierarchy
Resource hiearchy
Check 4 / ?
If no permissions were found
anywhere in the subject hierarchy for the component, advance one step up the resource hierarchy and recurse up the subject hierarchy again if necessary,
view?
Subject hierarchy
Resource hiearchy
Check 5 / 5
Finally! we found a permission, which gave the users team access to the components owner via an explicit allow.
allow
Note, if we didn't find any permissions set anywhere in the hierarchy, we would start the whole process again with a higher permission in the permissions hierarchy -- "edit" for example.
Thus, for p permissions, R resources, and S subjects in the chain we get worst case time complexity = O(pRS)
Subject hierarchy
Resource hiearchy
deny
Note, the order of recursion allows lower level denies to take precidence
In this case, even though the users team is allowed, the user does not have access, as their org is denied.
const filteredItems = await Tyr
.byName
.triangleLayerItem
.findAll({
query: {
groupId: {
$in: desiredGroups
}
},
auth: req.user
});
What if we want to find all components a user can view?
findAll( )
findAll( )
query(col, perm, auth) {
permissions = <subjectId in auth SH and resourceType in col RH>
resources = Map<resourceType, permissions>
finalQuery = {}
for ([resourceType, permissions] of resources) {
path = findPathBetween(col, resourceType)
docs = Tyr.byName[resourceType].findAll(map(permissions, resourceId))
for (intermediate_col of path) {
// walk documents through link path
docs = intermediate_col.findAll(docs.link_to_intermediate_col_field);
}
if (permissions.allow) addInclude(finalQuery, map(docs, 'id'));
if (permissions.deny) addExclude(finalQuery, map(docs, 'id'));
}
return finalQuery
}