Permissions - QutEcoacoustics/baw-server GitHub Wiki

Permissions

This page is a formal specification of possible values for project permissions and authorization for the site in general. If behaviour does not match this document it should be considered a bug.

Projects are the only model/entity that are assigned permissions directly. All authorization happens via one of three paths:

  • First a user's role is checked
  • Then any special conditions are processed
  • Finally, the permissions table is checked. All entities check authorization through relationships to projects. In most cases this means traversing the relational hierarchy up through parent nodes until the Project model is reached.

With the exceptions of the special cases in Role abilities and Special cases, all permissions are stored in the permissions table. All dynamic permissions (ones that can be changed by the web application) are stored in the permissions table.

Permission types

Anonymous and logged-in access

Anonymous and logged-in user access allow for the concept of public projects: projects that users can participate in without being granted explicit access. The permissions table stores information for anonymous and logged in access. Read access for anonymous users does not imply read access for logged in users (but each of those permissions can be explicitly set).

Per-user access

Access specified per user via a permission can be read, write, or own. Per user access cannot be none; this is implied through the lack of a specified permission. Per-user access is standard and entries are stored in the the permissions table.

Permission levels

Permissions are like ACLs for the project-entity hierarchy. They are not the same as roles.

The available access levels are, in order of abilities granted are: none, read, write, own.

none

No access to view or modify any part of the project, or any child entity, or other secured entity.

The existence of the Project should not be available to a user with the none access level, except for enabling a user to request additional permissions. This requires the name and id attributes of the project model to be exposed publicly - this exception is listed in the special cases section, see here.

read

read allows all non-modifying abilities/actions to be performed on the project and all descendants. This includes viewing lists, viewing details, and viewing/downloading audio/spectrograms/annotations/comments.

write

write includes all the read abilities and also includes modifying actions, except for the actions listed in the own level. Allowed actions include modifying details for Sites in the Project, creating datasets/annotations/comments.

own

own includes all abilities in the write level and these special actions:

  • Change the details of the Project
  • Add, change, and remove Sites for the Project
    • NB: breaking change
  • Change Permissions for the Project
    • NB: breaking change
  • Change a project's public status (either allow anonymous or allow logged in)
  • Delete the Project

Roles

Roles are special groups users can be a part of. The user's role will determine if the policies set in the permissions table are ignored or not. Roles based permissions are not dynamic and are not stored in the permissions table.

Roles are named lists of special abilities a user has access to.

The available roles are: admin, user, harvester, and guest.

User & Guest

The user role is the standard role. The user role has few special abilities; the bulk of authentication done for the user role is done through the permissions subsystem.

Harvester

The harverster roles has special access to a small subset of abilities related to ingesting audio data.

Admin

The admin role has access to all abilities including special abilities reserved only for the admin role.

Formal specification

The following section will define formal specifications for the permissions table and model. These specifications can be used for table UNIQUE constraints, model validations, and unit tests.

Database Schema

Permissions

CREATE TABLE permissions (
    id integer NOT NULL,
    creator_id integer NOT NULL,
    level character varying NOT NULL,
    project_id integer NOT NULL,
    user_id integer,
    updater_id integer,
    created_at timestamp without time zone,
    updated_at timestamp without time zone,
    allow_logged_in boolean DEFAULT false NOT NULL,
    allow_anonymous boolean DEFAULT false NOT NULL,
    CONSTRAINT permissions_exclusive_cols CHECK ((((((user_id IS NOT NULL) AND (NOT allow_logged_in)) AND (NOT allow_anonymous)) OR (((user_id IS NULL) AND allow_logged_in) AND (NOT allow_anonymous))) OR (((user_id IS NULL) AND (NOT allow_logged_in)) AND allow_anonymous)))
);

The allow_logged_in and allow_anonymous fields represent a permission that is relevant when a permission for a specific user id cannot be found.

Users

CREATE TABLE users (
    id integer NOT NULL,
    user_name character varying NOT NULL,
    ...
    roles_mask integer,
    ...
);

Invariants

Each of these invariants should be unit-tested. Most should be part of the model validations.

Permissions

(A) Level

¬(level = ∅)(level IS NOT NULL)

(A2) Level (in database)

level ∈ {own, write, read}

(B) Project Id

¬(project_id = ∅)(project_id IS NOT NULL)

(C) Combination of User Id, Allow Logged In, and Allow Anonymous

This invariant should be a uniqueness constraint applied to the permissions table.

  • Let A := allow_anonymous
  • Let L := allow_logged_in
  • Let U := ¬(user_id = ∅)

A ⊕ L ⊕ U(A ∧ ¬L ∧ ¬U) ∨ (¬A ∧ L ∧ ¬U) ∨ (¬A ∧ ¬L ∧ U)
(A AND (NOT L) AND (NOT U)) OR ((NOT A) AND L AND (NOT U)) OR ((NOT A) AND (NOT L) AND U)

Produces this truth table:
Truth table for invariant C

Users

(D) Role

¬(roles_mask = ∅)(roles_mask IS NOT NULL)

(D2) Role

roles_mask ∈ {admin, user, harvester, guest}

Note: the guest value is not a valid value in database. Guest users are not stored in the database. MARK: unsure about previous statement

Allowed permission combinations

This section defines which permission levels are allowed for each type of user. Types of users are:

  • Let C1 := Anonymous users ⇔ (A ∧ ¬L ∧ ¬U)
  • Let C2 := Any logged in user ⇔ (¬A ∧ L ∧ ¬U)
  • Let C3 := A specific user ⇔ (¬A ∧ ¬L ∧ U)

The above definitions are taken from the 3/8 valid combinations from the truth table of Invariant C Combination of User Id, Allow Logged In, and Allow Anonymous)

Reminder: if a permission is in the permission table, it must be associated with a project_id.

Truth table: Permission levels vs types of users

O W R N (implied)
C1 F F T T
C2 F T T T
C3 T T T T

The result of this table is that 9/12 combinations are possible.

Summarised:

  • none is
    • a valid permission level for all user types
    • is not stored in the database
    • is implicit if a record is not found
  • Anonymous users
    • can have read access (only) to a specified project
    • do not have any write access
  • Logged in users can have write or read access (only) to a specified project
  • A specific user can have own, write, or read access to a specified project
  • Clarification: if an anonymous user has read access, it does not automatically imply a logged in user has read access - those permissions can be set separately.
    • Justification: the implies part, while it makes sense, adds complexity to the rules of the system. We have a strong system already for adding both logged in and anonymous permissions, so we can just add one record for each case, without specifically programming the implied inheritance.

Applying authorization

Based on the definitions above there are 9 basic cases permissions cases to cover.

Additional logic is included for combinations of roles and special permissions.

  1. IF current_user is not defined
    1. Create guest user and set roles_mask to :guest
    2. ...continue
  2. IF current_user.roles_mask == :admin
    1. GRANT access to all :admin role abilities
  3. IF current_user.roles_mask == :harvester
    1. GRANT access to all :harvester role abilities
  4. IF current_user.roles_mask == :user OR current_user.roles_mask == :guest
    1. IF current_user.roles_mask == :user
      1. GRANT access to all [:user] role abilities
    2. GRANT access to all special cases
    3. EXECUTE permissions check if needed
      1. Determine entity's relationship to the projects table
        • NB: permissions table enties only apply to entities
        • NB: all models should define their relationship to the projects entity unless they are covered by a special case
      2. Query the permissions table for any entries relating to the projects determined by previous step
      3. IF no records are found
        1. RETURN DENY ACCESS (permission level :none)
      4. IF any records are found
        1. Choose the record with the highest permission level
        2. RETURN <<highest_permission_level>>
  5. ELSE DENY ACCESS

If GRANT or DENY is defined for an ability, apply authorization immediately. Note: a special case is just an ability that doesn't follow the rules for role based access or entity based access.

If a permission level is returned, apply logic from entity based access section (next section).

Entity based access

Entities are Ruby on Rails controllers. Controllers typically map 1:1 to an associated model.

There are seven standard actions for a controller. This section defines what the standard permission levels allow for each action.

Actions that are atypical are special cases. A list of special cases are defined in the special cases section.

Table: permission levels mapped to controller actions. T⇔GRANT, F⇔DENY

:own :write :read :none
index T T T F
show T T T F
new T T T T
create T T F F
update T T F F
destroy T T F F
filter T T T F

NB: everyone, always has access to the new action. This is because new only stubs the required fields for an entity that needs to be created. In RESTful terms, it is basically documentation. In Rails views terms, it shows an empty form - that should 403 when submitted. Just because new is accessible, it does not mean good UI has to link to it.

Special cases

For each of the special cases listed below, it should be assumed that the standard entity access rules defined in the previous section are in effect, unless overridden here.

Terminology:

  • has :permission is used extensively below. This should be read as: the current_user has the :permission permission.
  • is_guest should be read as: the current_user has the role :guest and is accessing the entity anonymously.
  • is_implicit should be read as: the current_user has the role :user and has implicit (public, no permission record exists for that user/project combo) access to the entity.
  • user_owned should be read as: the current user created this entity (current_user EQUAL to entity.creator_id).
    • Note: this definition does not extent to entity.updater_id

Rules

  • Projects
    • GRANT index and show for ANY request but LIMIT attributes to id and name
    • On project creation, assign :own to the creator
    • DENY access to update or destroy UNLESS has :own
      • NB: this is breaking behaviour
  • Permissions
    • DENY access to ALL actions UNLESS has :own
      • NB: breaking change
      • NB: includes changing permission.allow_anonymous or permission.allow_logged_in
  • Sites in Projects
    • DENY access to ALL actions
      • NB: essentially a private entity, no API exposed
  • Sites
    • IF NOT has :own
      • THEN OBFUSCATE site.latitude and site.longitude and set site.location_obfucated to TRUE
    • ALLOW upload_instructions, harvest IF has :own
    • DENY access to create, update or destroy UNLESS has :own
      • NB: this is breaking behaviour
  • Audio Recordings
    • DENY ALL access to create, update, and destroy
      • NB: the harvester role has special access
  • Audio Events
    • ALLOW index, show, and filter for ANY request if audio_event.is_reference is TRUE
      • NB: reference annotations
    • ALLOW download_annotations for IF has :read
  • Audio Event Tags
    • ALLOW index, show, and filter for ANY request if audio_event_tags.audio_event.is_reference is TRUE
      • NB: reference annotations
  • Media
    • ALLOW show for ANY request:
      • IF conditions:
        • audio_event_tags.audio_event.is_reference is TRUE
        • AND audio_event_id QSP is set
        • AND (audio_event WHERE audio_events.id = audio_event_id).is_reference is TRUE
      • Further restrictions
        • Allow only 5 seconds +/- audio_event start and end times
      • NB: reference annotations
  • Tags
    • ???
  • Tag Groups
    • ???
  • Audio Event Comments
    • DENY access to update and destroy UNLESS user_owned
  • Bookmarks
    • DENY access to ALL actions UNLESS user_owned
      • NB: bookmarks are essentially private
  • Analysis Jobs
    • DENY access to create IF is_implicit or is_guest
    • DENY access to update AND destroy UNLESS user_owned
  • Analysis Jobs Results
    • ???
  • Projects Saved Searches
    • DENY access to ALL actions
      • NB: essentially a private entity, no API exposed
  • Saved Searches
    • DENY access to update AND destroy UNLESS user_owned
    • NB: saved searches are public - they are linked to Projects via projects_saved_searches and as
      per standard permissions, anyone with :read can see saved searches.
  • Scripts
    • DENY access to create, update, AND destroy actions for ANY request
  • Users
    • DENY access to create, update AND destroy actions for ANY request
    • UNLESS is user_owned
      • Restrict access to sensitive data
        • White list
          • Username
          • Picture
          • Last seen at
          • <>
    • NB: all the special cases involving creating accounts and changing a profile here are dealt with via Devise and through the user role

Role abilities

This is an exhaustive list of abilities that are included in roles.

Roles are applied before permissions! Thus if any role rule matches here, the entire permission subsystem is skipped.

Admin

  • GRANT access to ALL abilities (everything!)
  • GRANT access to special admin only pages
    • Includes
      • Resque status
      • Script creation
      • ...

Harvester

  • GRANT access to ALL audio_recording abilities

User

  • IF entity is user_profile GRANT ACCESS (e.g. user_profile is user_owned)

Guest

  • ???
⚠️ **GitHub.com Fallback** ⚠️