Gracl

All Hands, May 2016

An attempt at graph-based permissions

  1. Motivation
  2. tyranid-gracl
    1. Jargon
    2. Algorithm

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 

: Jargon

  • Subject / Resource
  • Subject / Resource Hierarchy
  • Permissions Hierarchy
  • CRUD / Abstract Permissions

Subject:

 

"The thing / person doing the accessing"

Accessing

Sandy can view twitter, Sandy is the subject

Jargon

Resource:

 

"The thing / person being accessed"

Accessing

Everyone in twitter can view Sandy's tweets, Sandy is now the resource

Jargon

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)

Jargon

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)

Jargon

Permissions Hierarchy

own

edit

view

implies

implies

own

own

Bill owns his photos, so he can implicitly edit and view them

Jargon

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

Jargon

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 },

]

Jargon

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
    }
}

Jargon

Subject hierarchy

Resource hiearchy

Users

Teams

Organizations

Organizations

Teams

Users

Items

Algorithm

view?

What if we want to check if a user can view a given component?

const canView = await item.$isAllowed('view-triangleLayerItem', user);

Algorithm

Algorithm

view?

Subject hierarchy

Resource hiearchy

Check 1 / ?

First check, does a permission document exist directly relating the user to the component?

Algorithm

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.

Algorithm

view?

Subject hierarchy

Resource hiearchy

Check 3 / ?

Recurse again to the users organization if no deny or allow has been found

Algorithm

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,

Algorithm

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

Algorithm

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)

Algorithm

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.

Automated Security within Tyranid

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

Automated Security within Tyranid

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
}

Conclusions

  • Inheritance is powerful, but can be dangerous
  • Try to use inheritance when possible instead of adding more permissions
  • TypeScript is amazing