Permissions - QutEcoacoustics/baw-server GitHub Wiki
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.
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).
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.
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
.
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
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
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
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 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
.
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.
The harverster
roles has special access to a small subset of abilities related to ingesting audio data.
The admin
role has access to all abilities including special abilities reserved only for the admin
role.
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.
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.
CREATE TABLE users (
id integer NOT NULL,
user_name character varying NOT NULL,
...
roles_mask integer,
...
);
Each of these invariants should be unit-tested. Most should be part of the model validations.
¬(level = ∅)
⇔ (level IS NOT NULL)
level ∈ {own, write, read}
¬(project_id = ∅)
⇔ (project_id IS NOT NULL)
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)
¬(roles_mask = ∅)
⇔ (roles_mask IS NOT NULL)
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
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.
-
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
- can have
- Logged in users can have
write
orread
access (only) to a specified project - A specific user can have
own
,write
, orread
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.
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.
- IF
current_user
is not defined- Create guest user and set
roles_mask
to:guest
- ...continue
- Create guest user and set
- IF
current_user.roles_mask == :admin
- GRANT access to all
:admin
role abilities
- GRANT access to all
- IF
current_user.roles_mask == :harvester
- GRANT access to all
:harvester
role abilities
- GRANT access to all
- IF
current_user.roles_mask == :user
ORcurrent_user.roles_mask == :guest
- IF
current_user.roles_mask == :user
- GRANT access to all [
:user
] role abilities
- GRANT access to all [
- GRANT access to all special cases
- EXECUTE permissions check if needed
- 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
- Query the
permissions
table for any entries relating to theproject
s determined by previous step - IF no records are found
- RETURN DENY ACCESS (permission level
:none
)
- RETURN DENY ACCESS (permission level
- IF any records are found
- Choose the record with the highest permission level
- RETURN
<<highest_permission_level>>
- Determine entity's relationship to the
- IF
- 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).
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.
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.
-
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 toentity.creator_id
).- Note: this definition does not extent to
entity.updater_id
- Note: this definition does not extent to
- Projects
- GRANT
index
andshow
for ANY request but LIMIT attributes toid
andname
- On project creation, assign
:own
to the creator - DENY access to
update
ordestroy
UNLESS has:own
- NB: this is breaking behaviour
- GRANT
- Permissions
- DENY access to ALL actions UNLESS has
:own
- NB: breaking change
- NB: includes changing
permission.allow_anonymous
orpermission.allow_logged_in
- DENY access to ALL actions UNLESS has
- Sites in Projects
- DENY access to ALL actions
- NB: essentially a private entity, no API exposed
- DENY access to ALL actions
- Sites
- IF NOT has
:own
- THEN OBFUSCATE
site.latitude
andsite.longitude
and setsite.location_obfucated
to TRUE
- THEN OBFUSCATE
- ALLOW
upload_instructions
,harvest
IF has:own
- DENY access to
create
,update
ordestroy
UNLESS has:own
- NB: this is breaking behaviour
- IF NOT has
- Audio Recordings
- DENY ALL access to
create
,update
, anddestroy
- NB: the
harvester
role has special access
- NB: the
- DENY ALL access to
- Audio Events
- ALLOW
index
,show
, andfilter
for ANY request ifaudio_event.is_reference
is TRUE- NB: reference annotations
- ALLOW
download_annotations
for IF has:read
- ALLOW
- Audio Event Tags
- ALLOW
index
,show
, andfilter
for ANY request ifaudio_event_tags.audio_event.is_reference
is TRUE- NB: reference annotations
- ALLOW
- 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
- Allow only 5 seconds +/-
- NB: reference annotations
- IF conditions:
- ALLOW
- Tags
- ???
- Tag Groups
- ???
- Audio Event Comments
- DENY access to
update
anddestroy
UNLESSuser_owned
- DENY access to
- Bookmarks
- DENY access to ALL actions UNLESS
user_owned
- NB: bookmarks are essentially private
- DENY access to ALL actions UNLESS
- Analysis Jobs
- DENY access to
create
IFis_implicit
oris_guest
- DENY access to
update
ANDdestroy
UNLESSuser_owned
- DENY access to
- Analysis Jobs Results
- ???
- Projects Saved Searches
- DENY access to ALL actions
- NB: essentially a private entity, no API exposed
- DENY access to ALL actions
- Saved Searches
- DENY access to
update
ANDdestroy
UNLESSuser_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.
- DENY access to
- Scripts
- DENY access to
create
,update
, ANDdestroy
actions for ANY request
- DENY access to
- Users
- DENY access to
create
,update
ANDdestroy
actions for ANY request - UNLESS is
user_owned
- Restrict access to sensitive data
- White list
- Username
- Picture
- Last seen at
- <>
- White list
- Restrict access to sensitive data
- NB: all the special cases involving creating accounts and changing a profile here are dealt with via Devise and through the
user
role
- DENY access to
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.
- GRANT access to ALL abilities (everything!)
- GRANT access to special
admin
only pages- Includes
- Resque status
- Script creation
- ...
- Includes
- GRANT access to ALL
audio_recording
abilities
- IF entity is
user_profile
GRANT ACCESS (e.g.user_profile
isuser_owned
)
- ???