view Sphinx/source/plugins/authorization.rst @ 1123:292479b06402

security: authentication wrt RemoteAccess
author Alain Mazy <am@orthanc.team>
date Thu, 19 Dec 2024 08:17:02 +0100
parents d57ca05c6478
children
line wrap: on
line source

.. _authorization:


Advanced authorization plugin
=============================

.. contents::

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
<https://discourse.orthanc-server.org/t/advanced-authorization-plugin-vs-remote-access/1859/5?u=jodogne>`__
between 2020 and 2022, but its active development has been resumed
since May 2022 and is intensively used in the `orthanc-auth-service <https://github.com/orthanc-team/orthanc-auth-service>`__
project that provides user permissions and sharing of single studies.


How to get it ?
---------------

The source code is available on `Mercurial <https://orthanc.uclouvain.be/hg/orthanc-authorization/>`__.

Binaries are included in:

- The `orthancteam/orthanc Docker image <https://hub.docker.com/r/orthancteam/orthanc>`__
- The `Windows Installer <https://www.orthanc-server.com/download-windows.php>`__
- The `MacOS packages <https://www.orthanc-server.com/static.php?page=download-mac>`__

Release notes are available `here <https://orthanc.uclouvain.be/hg/orthanc-authorization/file/tip/NEWS>`__.

Compilation instructions are available below.


Usage
-----

.. highlight:: json

Once Orthanc is installed, you must change the :ref:`configuration file
<configuration>` 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 :ref:`Orthanc Explorer 2 <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
<https://orthanc.uclouvain.be/hg/orthanc-authorization/file/default/Resources/TestService.js>`__
of a simple Web service that implements only the ``validate`` route.

The `orthanc-auth-service project <https://github.com/orthanc-team/orthanc-auth-service>`__
provides a full implementation of the Web service.  It notably contains a `definition of 
all the requests and responses <https://github.com/orthanc-team/orthanc-auth-service/blob/main/sources/orthanc_auth_service/shares/models.py>`__ 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 <https://github.com/orthanc-team/orthanc-auth-service>`__,
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 :ref:`DICOM model of the real world
  <model-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 :ref:`DICOM identifier
  <dicom-identifiers>` 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 :ref:`Orthanc identifier
  <orthanc-ids>` 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 :ref:`REST API of Orthanc <rest>` 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
------------------

.. highlight:: json

The full list of configuration is available `here <https://orthanc.uclouvain.be/hg/orthanc-authorization/file/tip/Plugin/DefaultConfiguration.json>`__.

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 :ref:`Orthanc Explorer 2 <orthanc-explorer-2>`.

* ``UncheckedLevels`` allows to specify which levels of the
  :ref:`DICOM hierarchy <model-world>` 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 :ref:`Stone Web viewer <stone_webviewer>`::

  {
    // 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"
    }
  }

.. _orthanc-explorer-2-authorization:

Integration with the Orthanc Explorer 2
---------------------------------------

This project contains a `complete example <https://github.com/orthanc-team/orthanc-auth-service>`__ 
of a Web services integrating with :ref:`Orthanc Explorer 2 <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.


.. _orthanc-explorer-authorization:

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 :ref:`Osimis Web viewer
<osimis_webviewer>`.

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
-----------

.. highlight:: bash

The procedure to compile this plugin is similar of that for the
:ref:`core of Orthanc <binaries>`. 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.