Skip to end of metadata
Go to start of metadata

The @XapiRequestMapping annotation provides the restrictTo attribute to allow developers to control access to particular REST API calls. The user's access level and contingent ability to call a function depends on the context of the call itself, specifically the user's access to the resource or resources identified through the parameters to the call. 

As an example, the following annotation requires that a user have "Read" access to a project's data (i.e. subjects and experiments) in order to return a 200 result:

Example RestrictTo=Read setting in a project context
@XapiRequestMapping(value = "sample/{projectId}", produces = MediaType.TEXT_PLAIN_VALUE, method = RequestMethod.GET, restrictTo = Read)
public String sampleMethod(@PathVariable @Project final String projectId) {

Note: Permissions Changes in XNAT 1.7.6

The restrictTo permissions definitions have been refactored and rationalized in XNAT 1.7.6, particularly with respect to projects. In XNAT 1.7.5.x, "restrictTo = Read" on a project referred to the user's ability to read a project's metadata. This would return true for a "Protected" project, whether or not the logged-in user had any role on that project. This led to some unexpected data exposure, particularly for plugin developers that did not expect this behavior.

Defining Access Levels

There are three levels of operational access a user might have to a resource and somewhat different implications for what a particular access level means based on the type of object–project, subject, or experiment–that the user is trying to access (access to particular subjects and experiments is always qualified by the context of the project in which the user is trying to access them).

"Experiment" in the table below doesn't necessarily apply to all experiments for a project or subject, since access to experiments can be controlled by data type so that, within the context of a particular project, a user may be able to delete experiments of type A, edit experiments of type B, read experiments of type C, and have no access to experiments of type D. However, what it means to read, write, and delete is the same for all data types.

Also, note that a user's access level for resources is the same as the user's access level for the project, subject, or experiment with which the resources are associated.


LevelProjectSubjectExperiment

Read

Example: Project Collaborator

View the contents of the project, including at least the ability to read subjects within the project, if not necessarily experiments

List and view subjectsList and view experiments

Write

Example: Project Member

Modify project settings and configurations

Create new and edit existing subjectsCreate new and edit existing experiments

Delete

Example: Project Owner or Site Admin with All Data Access

Delete project

Delete subjectsDelete experiments

Permissions related to "Protected" Projects

Protected projects don't fit neatly into the table above: users that aren't members of a group with access to the project–such as site administrators, data administrators, and collaborators, members, and owners of the project–can "read" the project in the sense that they can see that it exists, as well as a subset of the project's metadata. However they can't read any contents of the project. An analogy would be a directory on a standard Linux file system: all users can see that particular folders exist at the top level of the file system, but users without read access to a particular folder can't list the contents of the folder or see anything about it other than the fact that it's there. Because the use cases in which the difference between protected and private projects are so limited, the XAPI framework provides no specific access level or control for them. Attempting to read a protected project to which a user does not have read permissions should result in denial of access.

Annotation Use Cases: Determining Access Level In Context

Setting the restrictTo attribute to ReadWrite, or Delete requires providing a context whereby the XapiAuthorization implementation can determine the object or objects to evaluate. This context is defined by developers in response to the use case they want to support, and is coded using parameters provided to the REST API call itself. This table details a matchup of use cases with annotations.

Use CaseAnnotation

Required Value1

Evaluation
To set a restriction based on access to project data@ProjectProject IDTests whether the user has the requested access level to the project indicated by the @Project-annotated parameter. The parameter value must contain a project ID.
To set a restriction based on access to a specific subject

@Subject

or

@Project @Subject

Subject ID

or

Project ID and Subject ID/label

Tests whether the user has the requested access level to the subject indicated by the @Subject-annotated parameter:

  • If an @Project-annotated parameter is present, the @Subject-annotated parameter can be a subject label or ID. User access is evaluated within the context of the specified project.
  • If no @Project-annotated parameter is present, the @Subject-annotated parameter must be a subject ID. User access is evaluated within the context of the subject's source project.

The project context is significant. For example, a user might be able to edit a subject in project A, but only read the subject when shared into project B. Note that the same subject data object is returned regardless of whether the subject is accessed through the source project or one into which the subject is shared, so the same rules apply for data objects as for IDs and labels.

To set a restriction based on access to a specific experiment

@Experiment

or

@Project @Experiment

or

@Project @Subject @Experiment

Experiment ID

or

Project ID and Experiment ID/label

or

Project ID and Subject ID/label and Experiment ID/label

Tests whether the user has the requested access level to the subject indicated by the @Experiment-annotated parameter:

  • If an @Project-annotated parameter is present, the @Subject- and @Experiment-annotated parameters can be subject and experiment labels or IDs. User access is evaluated within the context of the specified project.
  • If no @Project-annotated parameter is present, the @Experiment-annotated parameter must be an experiment ID. User access is evaluated within the context of the experiment's source project.

The project context is significant. For example, a user might be able to edit an experiment in project A, but only read the experiment when shared into project B. Note that the same experiment data object is returned regardless of whether the subject is accessed through the source project or one into which the subject is shared, so the same rules apply for data objects as for IDs and labels.


Developer Note

To help clarify what users can perform Create / Edit / Delete operations on which data while developing, you can query Postgres and parse the result.  


Practical Usages

Here is a mapping of how these usages are applied in combination with Edit and Delete permissions, and how XNAT interprets these combinations in various contexts.

ActionAnnotationsFurther ContextOutcome
Delete@Project + @Experiment

Experiment identifies an experiment that was created in the project identified by Project.

The experiment will be removed from the XNAT database.

If the experiment was shared into any other project, that experiment is no longer visible in those projects.

Delete@Experiment

See above

Delete@Subject + @Experiment

See above

Delete@Project + @Experiment

In this context, the project identified by Project is not the original project for the experiment identified by Experiment.

That is, the experiment has been shared into this project and was not originally created in this project.

The experiment will be removed from the project.

The experiment will still exist in the original project and in any other projects in which it was shared.

Edit@Project + @ExperimentExperiment identifies an experiment that was created in the project identified by Project.Experiment will be edited
Edit@Experiment
See above
Edit@Subject + @Experiment
See above
Edit@Project + @Experiment

In this context, the project identified by Project is not the original project for the experiment identified by Experiment.

That is, the experiment has been shared into this project and was not originally created in this project.


Edit@Project + @SubjectSourceEasy
Edit@Project + @Subjectsharedwonky

Examples

The following examples illustrate uses of the restrictTo attribute in conjunction with the @Project@Subject, and @Experiment annotations. In each case, the result of the authorization operation will be one of three outcomes:

  • An exception if the method does not provide enough information to determine the security context
  • HTTP 403 if the user doesn't have sufficient access to the indicated resource
  • The result of the operation implementation

Note that this doesn't mean that the user might get, e.g., a 404 when the REST endpoint isn't exposed to unauthenticated users or similar circumstances. However, once the higher-level security filters have allowed the call to proceed to the actual endpoint, the authorization implementation should only produce one of the three outcomes above.

  1. Allow access if the user can read the project (including at least subjects):

    Example code for doing something like this
    @XapiRequestMapping(value = "sample/{projectId}", produces = MediaType.TEXT_PLAIN_VALUE, method = RequestMethod.GET, restrictTo = Read)
    public String sampleMethod(@PathVariable @Project final String projectId) {
  2. Allow access if the user can edit a project and/or any of its contents.

    Example code for doing something like this
    @XapiRequestMapping(value = "sample/{projectId}", produces = MediaType.TEXT_PLAIN_VALUE, method = RequestMethod.GET, restrictTo = Edit)
    public String sampleMethod(@PathVariable @Project final String projectId) {
  3. Allow access if the user can delete a project and/or any of its contents.

    Example code for doing something like this
    @XapiRequestMapping(value = "sample/{projectId}", produces = MediaType.TEXT_PLAIN_VALUE, method = RequestMethod.GET, restrictTo = Delete)
    public String sampleMethod(@PathVariable @Project final String projectId) {
  4. Allow access if the user can delete a subject in a project.

    Example code for doing something like this
    @XapiRequestMapping(value = "sample/{projectId}/{subjectId}", produces = MediaType.TEXT_PLAIN_VALUE, method = RequestMethod.GET, restrictTo = Delete)
    public String sampleMethod(@PathVariable @Project final String projectId, @PathVariable @Subject final String subjectId) {
  5. Allow access if the user can edit an experiment in a project.

    Example code for doing something like this
    @XapiRequestMapping(value = "sample/{projectId}/{experimentId}", produces = MediaType.TEXT_PLAIN_VALUE, method = RequestMethod.GET, restrictTo = Edit)
    public String sampleMethod(@PathVariable @Project final String projectId, @PathVariable @Experiment final String experimentId) {
  6. Allow access if the user is a site admin.

    Example code for doing something like this
    @XapiRequestMapping(value = "sample", produces = MediaType.TEXT_PLAIN_VALUE, method = RequestMethod.GET, restrictTo = Admin)
    public ResponseEntity<String> sampleMethod() {
  7. Allow access if the user is authenticated. Unauthenticated users are blocked from calling these methods even on open XNATs.

    Example code for doing something like this
    @XapiRequestMapping(value = "sample", produces = MediaType.TEXT_PLAIN_VALUE, method = RequestMethod.GET, restrictTo = Authenticated)
    public String sampleMethod() {
  8. Restrict user-modifying method to site admins and the users themselves (check whether the username passed in via the @Username annotated parameter belongs to the user executing the request or whether the user executing the request is an admin).

    Example code for doing something like this
    @XapiRequestMapping(value = "sample/{username}", produces = MediaType.TEXT_PLAIN_VALUE, method = RequestMethod.GET, restrictTo = User)
    public ResponseEntity<String> sampleMethod(@PathVariable("username") @Username final String username) {
  9. Check whether user has one of the specified roles.

    Example code for doing something like this
    @AuthorizedRoles({"Dqr", "Administrator"})
    @XapiRequestMapping(value = "sample", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET, restrictTo = Role)
    public ResponseEntity<String> sampleMethod() {
  10. Check whether the user has enough permissions to add the specified user to the specified groups

    Example code for doing something like this
    @XapiRequestMapping(value = "{username}", produces = APPLICATION_JSON_VALUE, method = PUT, restrictTo = Authorizer)
    @AuthDelegate(UserGroupXapiAuthorization.class)
    public ResponseEntity<String> sampleMethod(@PathVariable("username") @Username final String username, @RestUserGroup @RequestBody final List<String> groups) {

    This example uses restrictTo=Authorizer, which is used for custom permissions checks. These will often be specified in Authorization classes in plugins, but in the cases discussed here, the Authorization classes are in core XNAT itself.

  11. Check whether the user is logged in (if the XNAT is not an open XNAT). Only exclude the guest user if the site is not open. 

    Example code for doing something like this
    @XapiRequestMapping(value = "sample", produces = APPLICATION_JSON_VALUE, method = GET, restrictTo = Authorizer)
    @AuthDelegate(GuestUserAccessXapiAuthorization.class)
    public ResponseEntity<String> sampleMethod() {
  12. Check whether the user can access all the site configuration preferences in a given list. 

    Example code for doing something like this
    @XapiRequestMapping(value = "{preferences}", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET, restrictTo = Authorizer)
    @AuthDelegate(SiteConfigPreferenceXapiAuthorization.class)
    public ResponseEntity<String> sampleMethod(@PathVariable final List<String> preferences) {
  13. Check whether the user can access information about the users on the site. 

    Example code for doing something like this
    @XapiRequestMapping(value = "", produces = APPLICATION_JSON_VALUE, method = GET, restrictTo = Authorizer)
    @AuthDelegate(UserResourceXapiAuthorization.class)
    public ResponseEntity<String> sampleMethod() {

Footnotes
Ref Notes
1 For XNAT 1.7.6, the annotated parameters always indicate a string containing an object ID. Subsequent releases may also indicate a deserialized data object, although the data objects may not require an annotation, since the context of the object is inherent in the data type or object class, e.g. an XnatProjectdata object is obviously a project.

  • No labels