User Sharing

Sharing is a complicated process. There are many separate Data Hierarchies to consider, with varying degrees of granularity in permissions.

Permissions Matrices (Old Style)

Unless noted, permissions are granted on an ‘OR’ basis - only one permission needs to match to grant access, so the first successful test will short-circuit all remaining tests. (For example, permission is granted if either the user has been granted ‘Project Read’ or the user owns the Project.)

Basic Permissions

  • Read: Grants read-only access to the data
  • Write: Grants access to create, edit, or delete data

These are the Base Permissions granted to Users. Permissions for all other access are derived from these settings.

Basic Permissions Matrix

Permission IsOwner Project Read Project Write Notes
Project Read
   
Project Write
 
 
Library Read
     
Library Write
     

Derived Permissions

Derived Permissions Matrix

Permission IsOwner Project Read Project Write Library Read Library Write MediaFile Read MediaFile Write Notes
MediaFile
MediaFile Read
      N/A N/A If in Multiple Projects, grant access if ANY of the Projects grants permission
MediaFile Write
 
    N/A N/A
Tag Read  
           
Tag Write    
         
MediaFile User MetaData
Library UserMetaData Fields Read N/A If the Project contains any file from the Library  
      Can list/add UserMetaDataFields in a Library.
Library UserMetaData Fields Write N/A   If the Project contains any file from the Library  
   
MediaFile UserMetaData Values Read N/A        
   
MediaFile UserMetaData Values Write N/A          
 

Library Sharing (New Style)

Sharing permissions are explicitly granted to users or groups of users for access to either a Library, Project, or TagSet, with other objects deriving permissions from these.

Four distinct Permissions are available:

  • Read
    Grants access to a resource to view.
    • Visualize Files
    • View Tags
  • Write
    Grants access to modify, add or delete.
    • Add Tags
    • Add File Metadata
    • Remove MediaFile from Project.
  • Launch Job
    Grants access to run Jobs (not applicable to all objects, e.g. TagSets)
    • Supervised Search
    • Unsupervised Search
  • Admin
    Grants Admin access - typically this grants all other permissions, plus allows
    • adding or removing other users’ permissions
    • Delete the entire resource (Library, Project, TagSet)
    • Add / remove users from Groups

Permissions are not exclusive, so for example Read and Launch Job can both be granted in the same Permission.

At a high level, Permissions grants consist of the following:

  • bearer
    The User or Group to which these Permissions Apply.
  • target
    The Library, Project, or TagSet to which the User is granted permissions.
  • permissions
    A combination of Read, Write, Launch Job, or Admin.

In addition, these terms may be used:

  • FULL ACCESS
    An internal alias used to describe all permissions as set True.
  • DENY
    An internal alias used to describe all permissions as set False.

Default Permissions

Libraries and Projects are special objects that have Default Permissions that can be set. These control permissions for users who don’t have any other matching rules. By default, all default permissions will be False.

This can be used, for example, to grant read-only access to a Library for all users of ARLO (though note they would still need to be an authenticated user - there is no anonymous access). For example, this could be used to allow everyone access to a Demo Project.

Note: Default permissions do not apply if the User matches another permission on the object. So for example, if a Library has default permissions to allow FULL ACCESS, but a User has a permission set to only Read, then that User will receive only read access.

Admin Permissions

A User granted Admin permission will (usually) implicitly receive all other permissions, regardless of whether they’ve been explicitly granted.

Staff Override

Django’s Staff Override is used to create system Admin user’s for ARLO. These users have the capability to create or modify users as well as access all data.

Note that when logged in as a Staff user, most views in ARLO will act much like a regular user. However, additional views exist for adding/removing users, performing system level tasks, and most permissions checks are bypassed when accessing data.

Within the API, most data lists will behave as for a regular user (e.g., listing projects will show just the user’s associated projects), but Staff users can also directly access most object details regardless of their association with the data.

Note: Admin permissions should not be confused with the staff override feature. This is enabled by default in most places throughout ARLO. Where enabled, staff override grants ALL permissions to system Staff users (ARLO Admins / Django Superusers) to all objects, including system permissions outside of this Permissions structure.

Permissions Hierarchy

Permissions are determined via a hierarchy of checks against the user. In general, the first permission found that matches a User will be used (so for example, a User granted read-only access will not receive the default permissions, even if they would grant read-write).

Most of the objects will inherit permissions from their parent containers, so for example TagClass permissions are derived from the Project that contains them.

Library and Project permissions do not inherit, and are controlled explicitly by their owners/admins. TagSet permissions may have explicit grants, though if not will inherit from the Project.

Library / Project Permissions

digraph {

    {
        rank=same;

        owner [shape=box,style="rounded,filled",fillcolor=lightblue,group=g1,label="1. Owner?"];
        full [shape=box,style="rounded,filled",fillcolor=goldenrod,rank="same,owner",group=g2,label="FULL ACCESS"];
    }

    {
        userPerm [shape=box,style="rounded,filled",fillcolor=lightblue,group=g1,label="2. Permissions Granted to the User?"];
        groupPerm [shape=box,style="rounded,filled",fillcolor=lightblue,group=g1,label="3. Permissions Granted to the Group?"];
    }

    {
        rank=same;

        defaultPerm [shape=box,style="rounded,filled",fillcolor=goldenrod,group=g1,label="4. Use Default Permissions"];
        perms [shape=box,style="rounded,filled",fillcolor=palegreen,group=g2,label="Library/Project\nPermissions"];
    }

    owner -> userPerm [label="No"];
    userPerm -> groupPerm [label="No"];
    groupPerm -> defaultPerm [label="No"];

    owner -> full [label="Yes"];

    full -> perms;
    userPerm  -> perms [label="Yes",tailport=se];
    groupPerm  -> perms [label="Yes",tailport=se];
    defaultPerm -> perms;

}

When a User tries to access a Library or Project, the following checks are made.

  1. Is the User the Owner of the Library/Project? If so, grant FULL ACCESS.
  2. Is there an explicit Permissions entry granted to this User. If so, use these permissions.
  3. Is there an explicit Permissions entry granted to any Groups this User is a part of. If so, use these permissions. (Note: If the User is part of multiple applicable Groups, the resulting permissions are a union of the permissions assigned in all Groups.)
  4. Use the Default Permissions assigned to the Library/Project.

TagSet Permissions

digraph {

    {
        rank=same;

        owner [shape=box,style="rounded,filled",fillcolor=lightblue,group=g1,label="1. Owner?"];
        full [shape=box,style="rounded,filled",fillcolor=goldenrod,rank="same,owner",group=g2,label="FULL ACCESS"];
    }

    {
        projAdmin [shape=box,style="rounded,filled",fillcolor=lightblue,group=g1,label="2. Project Admin?"];
        userPerm [shape=box,style="rounded,filled",fillcolor=lightblue,group=g1,label="3. Permissions entry for the User?"];
        groupPerm [shape=box,style="rounded,filled",fillcolor=lightblue,group=g1,label="4. Permissions entry for the Group?"];
    }

    {
        rank=same;

        proj [shape=box,style="rounded,filled",fillcolor=goldenrod,group=g1,label="5. Use Project Permissions"];
        perms [shape=box,style="rounded,filled",fillcolor=palegreen,group=g2,label="TagSet Permissions"];
    }

    owner -> projAdmin [label="No"];
    projAdmin -> userPerm [label="No"];
    userPerm -> groupPerm [label="No"];
    groupPerm -> proj [label="No"];

    owner -> full [label="Yes"];
    projAdmin -> full [label="Yes"];

    proj -> perms;
    full -> perms;
    projAdmin -> perms [label="Yes",tailport=se];
    userPerm -> perms [label="Yes",tailport=se];
    groupPerm -> perms [label="Yes",tailport=se];

}

When a User tries to access a TagSet, the following checks are made.

  1. Is the User the Owner of the TagSet? If so, grant FULL ACCESS.
  2. Is the User an Admin for the containing Project? If so, grant FULL ACCESS.
  3. Is there an explicit Permissions entry granted to this User. If so, use these permissions.
  4. Is there an explicit Permissions entry granted to any Groups this User is a part of. If so, use these permissions. (Note: If the User is part of multiple applicable Groups, the resulting permissions are a union of the permissions assigned in all Groups.)
  5. No checks matched, so inherit the same User’s Permissions for the Project this TagSet is in.

Other Permission Inheritances

All other Models will inherit permissions from one of the above objects.

Note

MediaFile currently pulls permissions from both Library and Project, granting access if either provides the necessary permissions. This is left over from the old-style ProjectPermissions.

digraph {

    {
        mediafilemetadata [shape=box,style="rounded,filled",fillcolor=lightblue,label="MediaFileMetaData"];
        mediafileusermetadata [shape=box,style="rounded,filled",fillcolor=lightblue,label="MediaFileUserMetaData"];
        sensor [shape=box,style="rounded,filled",fillcolor=lightblue,label="Sensor"];
        mediafileusermetadatafield [shape=box,style="rounded,filled",fillcolor=lightblue,label="MediaFileUserMetaDataField"];
        jobparameters [shape=box,style="rounded,filled",fillcolor=lightblue,label="JobParameters"];
        joblog [shape=box,style="rounded,filled",fillcolor=lightblue,label="JobLog"];
        jobresultfile [shape=box,style="rounded,filled",fillcolor=lightblue,label="JobResultFile"];
    }

    {
        rank=same;

        mediafile [shape=box,style="rounded,filled",fillcolor=lightblue,label="MediaFile"];
        sensorarray [shape=box,style="rounded,filled",fillcolor=lightblue,label="SensorArray"];
        jobs [shape=box,style="rounded,filled",fillcolor=lightblue,label="Jobs"];
    }

    {
        rank=same;

        tagclass [shape=box,style="rounded,filled",fillcolor=lightblue,label="TagClass"];
    }

    {
        rank=same;

        tag [shape=box,style="rounded,filled",fillcolor=lightblue,label="Tag"];
    }

    {
        rank=same;

        library [shape=box,style="rounded,filled",fillcolor=goldenrod,group=g1,label="Library"];
        project [shape=box,style="rounded,filled",fillcolor=goldenrod,group=g1,label="Project"];
        tagset [shape=box,style="rounded,filled",fillcolor=goldenrod,group=g1,label="TagSet"];
    }

    jobparameters -> jobs;
    joblog -> jobs;
    jobresultfile -> jobs;
    mediafilemetadata -> mediafile;
    mediafileusermetadata -> mediafile;
    sensor -> sensorarray;

    mediafileusermetadatafield -> library;
    mediafileusermetadatafield -> project;

    mediafile -> library;
    mediafile -> project;

    sensorarray -> library;

    tagclass -> project;
    jobs -> project;

    tag -> tagset;

}

ArloUserGroup Permissions

ArloUserGroups have a special permission scheme when compared to the typical data objects. Only two of the permissions are applicable:

  • Read
    Whether the User can see the Group and it’s list of users.
  • Admin
    Whether the User can add / remove Users, as well as add other Admins.

Note that being a member of a Group and being assigned permissions to a Group are separate. For example, a User may be assigned Admin privileges for a group, but not be a member of the Group and receive it’s privileges.

digraph {

    owner [shape=box,style="rounded,filled",fillcolor=lightblue,group=g1,label="1. Owner / Creator?"];
    {
        rank=same;

        userAdmin [shape=box,style="rounded,filled",fillcolor=lightblue,group=g1,label="2. Admin Assigned to the User?"];
        admin [shape=box,style="rounded,filled",fillcolor=goldenrod,rank="same,owner",group=g2,label="Admin"];
    }
    groupAdmin [shape=box,style="rounded,filled",fillcolor=lightblue,group=g1,label="3. Admin Assigned to a Group the User is in?"];
    userInGroup [shape=box,style="rounded,filled",fillcolor=lightblue,group=g1,label="4. Is the User in the Group?"];
    {
        rank=same;

        userRead [shape=box,style="rounded,filled",fillcolor=lightblue,group=g1,label="5. Read Assigned to the User?"];
        read [shape=box,style="rounded,filled",fillcolor=goldenrod,rank="same,owner",group=g2,label="Read"];
    }
    groupRead [shape=box,style="rounded,filled",fillcolor=lightblue,group=g1,label="6. Read Assigned to a Group the User is in?"];
    {
        rank=same;

        deny [shape=box,style="rounded,filled",fillcolor=goldenrod,group=g1,label="7. DENY"];
        perms [shape=box,style="rounded,filled",fillcolor=palegreen,group=g2,label="ArloUserGroup Permissions"];
    }

    owner -> userAdmin [label="No"];
    userAdmin -> groupAdmin [label="No"];
    groupAdmin -> userInGroup [label="No"];
    userInGroup -> userRead [label="No"];
    userRead -> groupRead [label="No"];
    groupRead -> deny [label="No"];

    owner -> admin [label="Yes",tailport=e];
    userAdmin -> admin [label="Yes"];
    groupAdmin -> admin [label="Yes",tailport=ne];
    userInGroup -> read [label="Yes",tailport=e];
    userRead -> read [label="Yes"];
    groupRead -> read [label="Yes",tailport=ne];

    admin -> perms;
    read -> perms;
    deny -> perms;

}

The following checks are made to determine Permissions on an ArloUserGroup.

  • Admin
    1. Is the User the Owner of the ArloUserGroup? If so, grant Full Access (Read, Admin).
    2. Has the User been assigned explicit Admin permissions to the Group.
    3. Is the User a member of any Groups that are assigned Admin permissions.
  • Read
    1. Is the User a member of the Group?
    2. Has the User been assigned explicit Read permissions to the Group.
    3. Is the User a member of any Groups that are assigned Read permissions.
  • Deny
    1. If the above fail, Deny access.

Dev Details

Permissions are explicitly granted to users via the ArloPermission model. If not granted by permissions (as outlined below), the Default Permissions are used (for Library / Project) or permission is denied.

An ArloPermission is granted to either an individual User or to an ArloUserGroup, which defines a list of users.

Models

Two Models are used for the Sharing / Permissions scheme.

ArloUserGroup

This Model defines “User Groups” specific to ARLO which may be used to organize permissions.

  • name
    Name of this Group, ideally meaningful to the Users (e.g., ProjectAdmins or ReadOnlyStudents).
  • users
    A list of Users who are in this Group
  • creationDate
    When this Group was created.
  • createdBy
    The User who created this Group.
ArloPermission

Permissions are assigned to object with entries in this Model.

Note that this Model makes extensive use of Django’s ContentType framework.

  • bearer

    The User or ArloUserGroup to which these permissions are granted. This is comprised of:

    • bearer_type
      A ContentType reference to either a User or ArloUserGroup type.
    • bearer_id
      The database id (pk) of the User or ArloUserGroup

    or

    • bearer_object
      An alias field which combines the above two fields into the actual bearer object, either a User or ArloUserGroup
  • target

    The target object to which these permissions apply, either a Library, Project, TagSet, or ArloUserGroup. This is comprised of:

    • target_type
      A ContentType reference to either a Library, Project, TagSet, or ArloUserGroup.
    • target_id
      The database id (pk) of the target object.

    or

    • target_object
      An alias field which combines the above two fields into the actual target object, either a Library, Project, TagSet, or ArloUserGroup.
  • creationDate

    When the Permission was created.

  • createdBy

    The User who created the Permission.

  • Permissions

    Boolean fields that grant:

    • read
    • write
    • launch_job
    • admin

Background

My goal with this Library Sharing scheme was to make sharing as simple as possible while still covering every practical edge-case - as we found with the prior Project Permissions, we quickly ran up against several issues that we initially thought would be quite rare. As such, we want this to be much more flexible and granular than before.

There was consideration as to whether we should break up Write into Create / Update / Delete. For example allowing a user to add files or create tags, but not modify or delete existing items? In the end, the additional complexity didn’t immediately seem to justify these cases, for which there are probably fairly practical workarounds.

ARLO System Users (i.e., Django Staff or Superusers) were purposefully ignored from this management. The functions that these privileges grant (e.g., Creating Users) are unrelated to user sharing features.