Developer Tip: Querying Permissions in Postgres

If you are having trouble grasping how various restrictTo annotations work, it might be helpful to play around with queries against the permissions tables to get a clearer picture of how we determine what actions a user can take on a particular object. Here's an example:


Permissions query

SELECT
    m.read_element,
    m.edit_element,
    m.delete_element
FROM
    xdat_field_mapping m
    LEFT JOIN xdat_field_mapping_set s ON m.xdat_field_mapping_set_xdat_field_mapping_set_id = s.xdat_field_mapping_set_id
    LEFT JOIN xdat_element_access a ON s.permissions_allow_set_xdat_elem_xdat_element_access_id = a.xdat_element_access_id
    LEFT JOIN xdat_usergroup g ON a.xdat_usergroup_xdat_usergroup_id = g.xdat_usergroup_id
    LEFT JOIN xdat_user_groupid i ON g.id = i.groupid
    LEFT JOIN xdat_user u ON i.groups_groupid_xdat_user_xdat_user_id = u.xdat_user_id
WHERE
    m.field = 'xnat:projectData/ID' AND
    m.field_value IN (:project, '*') AND
    u.login = :username;

Each table between the user and field mapping connects to permissions:

  • The user is the user, obviously
  • xdat_user_groupid joins the user to groups
  • xdat_usergroup defines the user groups, so with that join you have all of the groups to which the user belongs
  • xdat_element_access connects each security principal (which, in modern XNAT, means groups: xdat_element_access can also connect to users and does on CNDA and Central, but those are the effect of having been around a long time) to each secured data type. Effectively this means that you'll have as many rows in this table as you have rows in xdat_usergroup times the number of rows in xdat_element_security where secure = 1 (which is where the list of secured data types comes from).
  • xdat_field_mapping_set is basically nothing but a join between xdat_element_access (there should always be the same number of sets as there are element access entries) and xdat_field_mapping (there's also a method column, but I'm not quite clear what that does)
  • xdat_field_mapping includes an XFT field and a value for that field. This has a many-to-one relationship with xdat_field_mapping_set and xdat_element_access, but the number of entries per set is always one or two:
    • If the corresponding xdat_element_access entry is xnat:projectData, there's only one field mapping entry with the field set to xnat:projectData/ID
    • For all other data types, there are two field mapping entries, one for the experiment's project field, the other for the shared experiment project field, so, e.g. for xnat:mrSessionData you'd have xnat:mrSessionData/project and xnat:mrSessionData/sharing/share/project
    • The field_value for each field mapping entry indicates the project to which that applies, with the exception being "*", which matches all projects

A nice illustration of how permissions resolve for group is this query:


Group access to project

SELECT
    g.id,
    m.field,
    m.read_element,
    m.edit_element,
    m.delete_element
FROM
    xdat_field_mapping m
    LEFT JOIN xdat_field_mapping_set s ON m.xdat_field_mapping_set_xdat_field_mapping_set_id = s.xdat_field_mapping_set_id
    LEFT JOIN xdat_element_access a ON s.permissions_allow_set_xdat_elem_xdat_element_access_id = a.xdat_element_access_id
    LEFT JOIN xdat_usergroup g ON a.xdat_usergroup_xdat_usergroup_id = g.xdat_usergroup_id
WHERE
    m.field_value = :project
ORDER BY
    g.id,
    m.field;

This gives results like this (I removed a bunch of secured data types so it's easier to read:

id                    field                                     read_element edit_element delete_element
XNAT_01_collaborator  xnat:mrSessionData/project                1            0            0
XNAT_01_collaborator  xnat:mrSessionData/sharing/share/project  1            0            0
XNAT_01_collaborator  xnat:petSessionData/project               1            0            0
XNAT_01_collaborator  xnat:petSessionData/sharing/share/project 1            0            0
XNAT_01_collaborator  xnat:projectData/ID                       1            0            0
XNAT_01_collaborator  xnat:subjectData/project                  1            0            0
XNAT_01_collaborator  xnat:subjectData/sharing/share/project    1            0            0
XNAT_01_member        xnat:mrSessionData/project                1            1            0
XNAT_01_member        xnat:mrSessionData/sharing/share/project  1            0            0
XNAT_01_member        xnat:petSessionData/project               1            1            0
XNAT_01_member        xnat:subjectData/project                  1            1            0
XNAT_01_member        xnat:subjectData/sharing/share/project    1            0            0
XNAT_01_owner         xnat:mrSessionData/project                1            1            1
XNAT_01_owner         xnat:mrSessionData/sharing/share/project  1            0            0
XNAT_01_owner         xnat:petSessionData/project               1            1            1
XNAT_01_owner         xnat:petSessionData/sharing/share/project 1            0            0
XNAT_01_owner         xnat:projectData/ID                       1            1            1
XNAT_01_owner         xnat:subjectData/project                  1            1            1
XNAT_01_owner         xnat:subjectData/sharing/share/project    1            0            0

And that's how project-based data-type-scoped permissions work in XNAT.

$label.name