Advanced authorization plugin

This official plugin extends Orthanc with an advanced authorization mechanism. For each incoming REST request to some URI, the plugin will query an external Web service to check whether the access should be granted. If access is not granted, the HTTP status code is set to 403 (Forbidden).

The request must include either an HTTP header or a GET argument that may either identify a user or an access to a single DICOM resource (an instance, a series, a study or a patient).

In the text below, the HTTP header and the GET argument is named the token.

Status: This plugin was deprecated between 2020 and 2022, but its active development has been resumed since May 2022 and is intensively used in the orthanc-auth-service project that provides user permissions and sharing of single studies.

How to get it ?

The source code is available on Mercurial.

Binaries are included in:

Release notes are available here.

Compilation instructions are available below.

Usage

Once Orthanc is installed, you must change the configuration file to tell Orthanc where it can find the plugin: This is done by properly modifying the Plugins option. You could for instance use the following configuration file:

{
  "Name" : "MyOrthanc",
  [...]
  "Plugins" : [
    "/home/user/OrthancAuthorization/Build/libOrthancAuthorization.so"
  ],
  "Authorization" : {
    // .. all options are document below
  }
}

Orthanc must of course be restarted after the modification of its configuration file.

User based authorization vs resource based tokens

The plugin can work in 2 modes that can be combined:

  • User based authorization is used e.g. in Orthanc Explorer 2 to allow various actions based on permissions defined in a user profile.

  • Resource based authorization is used e.g. to share a link that grants access to a single DICOM resource (e.g. a study).

External Web Service

This section describes how an external Web service suitable for the authorization plugin can be designed.

For each HTTP/REST request that Orthanc receives, the plugin will issue a set of HTTP POST requests against the Web service that is specified in the configuration file.

Depending on the kind of authorization you’d like to use, your Web service shall implement part or all of these routes:

  • /tokens/validate to validate tokens identifying a DICOM resource

  • /tokens/{token_type} to generate tokens granting access to specific DICOM resources.

  • /tokens/decode to extract the info from a resource token

  • /user/get-profile to return the user profile linked to a given token. This profile must include a list of permissions.

These routes url may be defined individually or globally in the configuration file.

Note: The source code of the plugin contains a basic example of a simple Web service that implements only the validate route.

The orthanc-auth-service project provides a full implementation of the Web service. It notably contains a definition of all the requests and responses used between the plugin and the Web service.

Resource tokens generation: /tokens/{token_type}

The tokens can actually be generated anywhere, e.g, in the orthanc-auth-service project, the user tokens are generated by KeyCloak. But a user logged into Orthanc can also generate links to share a single study in Orthanc Explorer 2. In this case, OE2 will call the authorization plugin that will forward the call to the Authorization Web Service (this route) that will generate a resource token.

The implementation of this route is optional and only required if you want to generate share links in OE2.

Your Web service receives this kind of POST requests:

{
  "id": "your-optional-id",
  "type": "depending-on-your-web-service",          // will instruct your Web service how to generate the url to access the resource (if relevant)
  "resources": [  // a list of Orthanc resources that can be identified either by the orthanc id
                  // or their DICOM ID (SOPInstanceUID, StudyInstanceUID, PatientID, SeriesInstanceUID)
    {
      "dicom-uid": "1.2.3",
      "orthanc-id": "6eeded74-75005003-c3ae9738-d4a06a4f-6beedeb8",
      "level": "study",                             // one of "patient", "study", "series", "instance", "system"
      "url": "/optional/system/url"                 // only for system level resources
    }
  ],
  "expiration-date": "2027-04-23T19:25:43.511Z",    // optional
  "validity-duration": 3600                         // validity duration (in seconds)
}

And your Web service must provide this kind of responses:

{
  "request": {},                                    // a copy of the request
  "token": "my-super-safe-resource-token",          // the token that will identify the resource
  "url": "http://optional.link.to/ui/app/token-landing.html?token=my-super-safe-resource-token"     // optional: url to access the shared resource
}

Resource tokens decoding: /tokens/decode

This route is quite specific to OE2 shares: When a user opens OE2 with a resource token, it usually lands on a specific landing page that calls this route to extract the content of the token to know e.g which viewer must be opened to display the DICOM resource or to check if the token has expired.

The implementation of this route is optional and only required if you want to open the share links in OE2.

Your Web service receives this kind of POST requests:

{
  "token-key": "token",                              // the name of the token (HTTP Header or GET argument)
  "token-value": "my-super-safe-resource-token"      // the token to be decoded
}

And your Web service must provide this kind of responses:

{
  "token-type": "depending-on-your-web-service",     // the type of the token
  "redirect-url": "http://your.domain.com/orthanc/stone-webviewer/index.html?study=...&token=....",
  "error-code": "expired"                            // optional; one of "expired", "invalid", "unknown".  This is used to display
                                                     // a friendly user message in OE2 in case of error.
}

Resource tokens validation: /tokens/validate

This route must absolutely be implemented if you want to implement resource based authentication, For each query that is made through Orthanc, Orthanc will use the response of this route to grant access or not to the API route.

Consider that a user issues this request:

curl -H "auth-token-header: my-super-safe-resource-token" http://localhost:8042/patients/6eeded74-75005003-c3ae9738-d4a06a4f-6beedeb8

Your Web service receives this kind of POST requests:

{
  "dicom-uid": "123ABC",
  "orthanc-id": "6eeded74-75005003-c3ae9738-d4a06a4f-6beedeb8",
  "level": "patient",
  "method": "get",
  "token-key": "auth-token-header",
  "token-value": "my-super-safe-resource-token",
  "server-id": "optional-id-ex-orthanc-public"
}

In this example, the user is accessing an URI that is related to some DICOM resource, namely a patient whose DICOM identifier is 123ABC and orthanc id 6eeded74-75005003-c3ae9738-d4a06a4f-6beedeb8. In such a case, the following fields will be set in the JSON body:

  • The level field specifies which type of resource the user is accessing, according to the DICOM model of the real world. This field can be set to patient, study, series, or instance.

  • The method field specifies which HTTP method is used by the to-be-authorized request. It can be set to get, post, delete, or put.

  • The dicom-uid field gives the DICOM identifier of the resource that is accessed. If the resource is a patient, this field contains the PatientID DICOM tag. For a study, it contains its StudyInstanceUID. For a series, it contains its SeriesInstanceUID. For an instance, it contains its SOPInstanceUID.

  • The orthanc-id field gives the Orthanc identifier of the resource.

  • The server-id field contains the value of the WebServiceIdentifier configuration or null if this configuration is not defined. This allows the WebService to identity which Orthanc instance is calling it (new in v 0.3.0).

It the user is accessing a URI that is not directly related to an individual DICOM resource, the JSON body will look as follows:

{
  "level" : "system",
  "method" : "get",
  "uri" : "/changes",
  "token-key": "auth-token-header",
  "token-value": "my-super-safe-resource-token",
  "server-id": "optional-id-ex-orthanc-public"
}

In such a situation, the following fields are set:

  • The level field is always set to system.

  • The method field is the same as above.

  • The uri field provides the URI that was accessed by the user.

And your Web service must provide this kind of responses:

{
  "granted": true,
  "validity": 60
}

Where:

  • granted tells whether access to the resource is granted (true) or not granted (false). In the case the user is accessing a DICOM resource, the access to all the levels of the hierarchy above this resource must be granted (logical conjunction over the levels).

  • validity tells the authorization plugin for how many seconds the result of the Web service must be cached. If set to 0 second, the cache entry will never expire. By setting a validity duration, Orthanc can cache the response to avoid asking the same question thousands of times to your web-service e.g. when opening a study in a web viewer.

Note depending on your configuration, the Web service might receive multiple requests, one for each level of the hierarchy that must be checked (see in the configuration below). E.G:

{
  "dicom-uid" : "123ABC",
  "level" : "patient",
  "method" : "get",
  "orthanc-id" : "6eeded74-75005003-c3ae9738-d4a06a4f-6beedeb8",
  ...
}
{
  "dicom-uid" : "1.3.51.0.1.1.192.168.29.133.1681753.1681732",
  "level" : "study",
  "method" : "get",
  "orthanc-id" : "6e2c0ec2-5d99c8ca-c1c21cee-79a09605-68391d12",
  ...
}
{
  "dicom-uid" : "1.3.12.2.1107.5.2.33.37097.2012041612474981424569674.0.0.0",
  "level" : "series",
  "method" : "get",
  "orthanc-id" : "6ca4c9f3-5e895cb3-4d82c6da-09e060fe-9c59f228",
  ...
}

Important note: The plugin will transparently parse the URIs of the core REST API of Orthanc and the most common official plugins. Unrecognized URIs (such as those introduced by other non official plugins) will be handled as a system call. It is possible to introduce parsing support for more plugins by modifying the DefaultAuthorizationParser C++ class in the source code of the plugin.

Get User profile: /user/get-profile

This route must absolutely be implemented if you want to implement user permissions based authorization.

Note that user based authorization has been implemented with the OE2 integration in mind. It has currently not been designed for other use cases.

For each query that is made through Orthanc, if no resource token granting access to the route was found, Orthanc will possibly try to retrieve a user profile to identify a possible user for this token.

Consider that a user issues this request:

curl -H "auth-token-header: my-super-safe-user-token" http://localhost:8042/studies/6e2c0ec2-5d99c8ca-c1c21cee-79a09605-68391d12

Your Web service receives this kind of POST requests:

{
  "token-key": "auth-token-header",
  "token-value": "my-super-safe-user-token",
  "server-id": "optional-id-ex-orthanc-public"
}

And your Web service must provide this kind of responses:

{
  "name": "John Who",                               // The name of the user (e.g. to display in OE2)
  "authorized-labels": [                            // A list of labels the user has access to.
    "my-label",                                     // use "*" to grant access to all labels
    "his-label"
  ],
  "permissions": [                                  // A list of permissions for this user
    "view",
    "upload",
    "..."
  ]
  "validity": 60                                    // the validity duration (in seconds) of this response.
}

By setting a validity duration, Orthanc can cache the response to avoid asking the same question thousands of times to your web-service e.g. when opening a study in a web viewer.

If a list of authorized-labels has been returned, the authorization plugin will add a label filter to each call to tools/find to include only the labels the user has access to or, when accessing a specific DICOM resource, the plugin will check that the resource has one of these authorized-labels.

The list of permissions are defined in the plugin configuration. E.g, the following configuration defines that a user must have either the all or the view permission to be authorized to issue GET requests to /studies/{orthanc-id}, provided that the study has one of the labels that is listed in the authorized-labels

["get" , "^/(patients|studies|series|instances)/([a-f0-9-]+)$", "all|view"],

This permission defines that a user must have either the all or the share permission to be authorized to issue a PUT request to generate a resource token to share a single DICOM study:

["put", "^/auth/tokens/(stone-viewer-publication||ohif-viewer-publication)$", "all|share"],

Authentication tokens

To configure the authentication plugin to use some HTTP header or GET argument, one must provide these options:

{
  "Authorization" : {
    ...
    "TokenHttpHeaders" : [ "token-header" ],
    "TokenGetArguments" : [ "token-in-url" ],
  }
}

Note 1: It is allowed to provide a list of HTTP tokens or a list of GET arguments in the configuration options. In this case, the authorization plugin will loop over all the available authentication tokens, until it finds one for which the access is granted (logical disjunction over the authentication tokens).

Note 2: The cache entry that remembers whether some access was granted in the past, depends on the value of the token.

Note 3: The support of authentication tokens provided as GET arguments requires a version of Orthanc that is above 1.2.1.

Full configuration

The full list of configuration is available here.

Here is the list of all the configuration options:

{
  "Authorization" : {
      // The Base URL of the auth webservice.  This is an alias for all next 4 configurations:
      // // "WebServiceUserProfileUrl" : " ROOT /user/get-profile",
      // // "WebServiceTokenValidationUrl" : " ROOT /tokens/validate",
      // // "WebServiceTokenCreationBaseUrl" : " ROOT /tokens/",
      // // "WebServiceTokenDecoderUrl" : " ROOT /tokens/decode",
      // You should define it only if your auth webservice implements all 4 routes !
      // "WebServiceRootUrl" : "http://change-me:8000/",

      // The URL of the auth webservice route implementing user profile (optional)
      // "WebServiceUserProfileUrl" : "http://change-me:8000/user/profile",

      // The URL of the auth webservice route implementing resource level authorization (optional)
      // "WebServiceTokenValidationUrl" : "http://change-me:8000/tokens/validate",

      // The Base URL of the auth webservice route to create tokens (optional)
      // "WebServiceTokenCreationBaseUrl" : "http://change-me:8000/tokens/",

      // The URL of the auth webservice route implementing token decoding (optional)
      // "WebServiceTokenDecoderUrl": "http://change-me:8000/tokens/decode"

      // The username and password to connect to the webservice (optional)
      //"WebServiceUsername": "change-me",
      //"WebServicePassword": "change-me",

      // An identifier added to the payload of each request to the auth webservice (optional).
      // It is used to identify the Orthanc instance that is sending the request to the auth webservice
      //"WebServiceIdentifier": "change-me"

      // The name of the HTTP headers that may contain auth tokens
      //"TokenHttpHeaders" : [],

      // The name of the GET arguments that may contain auth tokens
      //"TokenGetArguments" : [],

      // A list of predefined configurations for well-known plugins
      // "StandardConfigurations": [               // new in v 0.4.0
      //     "osimis-web-viewer",
      //     "stone-webviewer",
      //     "orthanc-explorer-2",
      //     "ohif"
      // ],

      //"UncheckedResources" : [],
      //"UncheckedFolders" : [],
      //"CheckedLevel" : "studies",
      //"UncheckedLevels" : [],

      // Definition of required "user-permissions".  This can be fully customized.
      // You may define other permissions yourself as long as they match the permissions
      // provided in the user-profile route implemented by the auth-service.
      // You may test your regex in https://regex101.com/ by selecting .NET (C#) and removing the leading ^ and trailing $
      // The default configuration is suitable for Orthanc-Explorer-2 (see https://github.com/orthanc-team/orthanc-auth-service)
      "Permissions" : [
          ["post", "^/auth/tokens/decode$", ""],
          ["post", "^/tools/lookup$", ""],

          // elemental browsing in OE2
          ["post", "^/tools/find$", "all|view"],
          ["get" , "^/(patients|studies|series|instances)/([a-f0-9-]+)$", "all|view"],
          ...
      ]
  }
}

The following options have been described above: WebServiceRootUrl, TokenGetArguments, and TokenHttpHeaders. Here are the remaining options:

  • StandardConfigurations is a helper configuration to pre-populate UncheckedResources, UncheckedFolders, TokenGetArguments, and TokenHttpHeaders of well-known plugins. Allowed values are osimis-web-viewer, stone-webviewer.

  • CheckedLevel may replace UncheckedLevels when authorization is checked only at one level of the DICOM hierarchy. This is the most common use-case.

  • UncheckedResources specifies a list of resources for which the authentication plugin is not triggered, and to which access is always granted.

  • UncheckedFolders specifies a list of root paths for which the authentication plugin is not triggered when receiving a GET request. This is actually mainly used to grant access to static resources e.g. HTML and JS resources from plugins like Orthanc Explorer 2.

  • UncheckedLevels allows to specify which levels of the DICOM hierarchy are ignored by the authorization plugin. This can be used to reduce the number of calls to the Web service. Think for instance about an authorization mechanism that simply associates its studies to a set of granted users: In this case, the series and instance levels can be ignored.

  • WebServiceIdentifier is used to identify the Orthanc instance that is calling the Web service. This value is copied in server-id in the requests to the web services (new in v 0.3.0).

Here is a minimal configuration for the Stone Web viewer:

{
  // disable basic authentication since it is replaced by the authorization plugin
  "AuthenticationEnabled": false,

  "Authorization" : {
    "WebServiceTokenValidationUrl" : "http://localhost:8000/shares/validate",
    "StandardConfigurations": [
      "stone-webviewer"
    ],
    "CheckedLevel" : "studies"
  }
}

Integration with the Orthanc Explorer 2

This project contains a complete example of a Web services integrating with Orthanc Explorer 2 to implement user level permissions and sharing of single studies.

This sample also shows how to implement the 4 routes that the webservice might provide.

Integration with the Orthanc Explorer

Starting from Orthanc 1.5.8, you can pass authorization tokens in the url search params when opening the Orthanc explorer, i.e. http://localhost:8042/app/explorer.html?token=1234. This token will be included as an HTTP header in every request sent to the Orthanc Rest API. It will also be included in the URL search params when opening the Orthanc or Osimis Web viewer.

Only 3 tokens name will be recognized and forwarded: token, auth-token and authorization.

Please note that the Orthanc Explorer has not been designed to handle the authorization so, when an authorization is not granted, it will simply display an empty page or an error message.

Compilation

The procedure to compile this plugin is similar of that for the core of Orthanc. The following commands should work for most UNIX-like distribution (including GNU/Linux):

$ mkdir Build
$ cd Build
$ cmake .. -DSTATIC_BUILD=ON -DCMAKE_BUILD_TYPE=Release
$ make

The compilation will produce a shared library OrthancAuthorization that contains the authorization plugin.