changeset 1020:3f4a7ee8033b

auth plugin huge rewrite
author Alain Mazy <am@osimis.io>
date Fri, 26 Jan 2024 18:37:04 +0100
parents a1d28570ef23
children a3436ae3709c
files Sphinx/source/plugins/authorization.rst
diffstat 1 files changed, 255 insertions(+), 130 deletions(-) [+]
line wrap: on
line diff
--- a/Sphinx/source/plugins/authorization.rst	Fri Jan 26 15:15:46 2024 +0100
+++ b/Sphinx/source/plugins/authorization.rst	Fri Jan 26 18:37:04 2024 +0100
@@ -8,14 +8,21 @@
 
 This **official plugin** extends Orthanc with an advanced
 authorization mechanism. For each incoming REST request to some URI,
-the plugin will query a Web service to know whether the access is
-granted to the user. If access is not granted, the HTTP status code is
-set to ``403`` (Forbidden).
+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.
+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 ?
@@ -51,9 +58,7 @@
       "/home/user/OrthancAuthorization/Build/libOrthancAuthorization.so"
     ],
     "Authorization" : {
-      "WebServiceRootUrl" : "http://localhost:8000/",
-      "WebServiceUsername": "my-user",
-      "WebServicePassword": "my-password"
+      // .. all options are document below
     }
   }
 
@@ -61,36 +66,141 @@
 configuration file.
 
 
-Web Service
------------
+User based authorization vs resource based tokens
+-------------------------------------------------
+
+The plugin can work in 2 modes that can be combined:
 
-This section describes how a Web service suitable for the
-authorization plugin can be designed.
+* **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).
 
 
-Incoming request
-^^^^^^^^^^^^^^^^
+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 (in the basic configuration file
-above, the Web service listening at ``http://localhost:8000/tokens/validate`` is
-used). The body of each of those ``POST`` requests is a JSON file
-similar to the following one::
+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::
 
   {
-    "dicom-uid" : "123ABC",
-    "level" : "patient",
-    "method" : "get",
-    "orthanc-id" : "6eeded74-75005003-c3ae9738-d4a06a4f-6beedeb8",
-    "server-id": null,
-    "uri": null
+    "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``. In such a case, the following fields will be set in the
-JSON body:
+``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
@@ -111,30 +221,6 @@
   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).
 
-When the user accesses a lower-level resource in the DICOM hierarchy
-(a study, a series or an instance), the authorization plugin will
-issue one separate call to the Web service for each level of the
-hierarchy.  For instance, here are the 3 successive requests that are
-issued when accessing some series::
-
-  {
-    "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"
-  }
 
 It the user is accessing a URI that is not directly related to an
 individual DICOM resource, the JSON body will look as follows::
@@ -142,7 +228,10 @@
   {
     "level" : "system",
     "method" : "get",
-    "uri" : "/changes"
+    "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:
@@ -150,29 +239,16 @@
 * 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.
-  
-**Important note:** The plugin will transparently parse the URIs of
-the core :ref:`REST API of Orthanc <rest>`, of the :ref:`Web viewer
-plugin <webviewer>`, of the :ref:`DICOMweb plugin <dicomweb>`, and of
-the :ref:`whole-slide imaging plugin <wsi>`. Unrecognized URIs (such
-as those introduced by other 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.
-  
+
 
-Expected answer
-^^^^^^^^^^^^^^^
-
-The Web service must answer by sending a JSON file that tells whether
-the access is granted or not to the user. Here is a sample answer::
+And your Web service must provide this kind of responses::
 
   {
     "granted": true,
-    "validity" : 5
+    "validity": 60
   }
 
-Here is a description of these two fields:
+Where:
 
 * ``granted`` tells whether access to the resource is granted
   (``true``) or not granted (``false``). In the case the user is
@@ -181,72 +257,124 @@
   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.
-
-**Note:** The source code of the plugin contains a `basic example
-<https://orthanc.uclouvain.be/hg/orthanc-authorization/file/default/Resources/TestService.js>`__
-of such a Web service written in node.js.
+  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.
 
 
-Authentication tokens
-^^^^^^^^^^^^^^^^^^^^^
 
-It is obviously desirable to limit access to the resources depending
-on the user that is logged in. Real-life Web framework such as Django
-would send the identity of the authenticated user either as an HTTP
-header, or as an additional argument for ``GET`` requests. The
-authorization plugin allows to forward these authentication tokens to
-the Web service.
-
-To configure the authentication plugin to use some HTTP header, one
-must provide the option ``TokenHttpHeaders`` the configuration file of
-Orthanc as follows::
-
-  {
-    "Name" : "MyOrthanc",
-    [...]
-    "Authorization" : {
-      "WebService" : "http://localhost:8000/",
-      "TokenHttpHeaders" : [ "token" ]
-    }
-  }
-
-.. highlight:: text
-
-In such a situation, if some HTTP client issues the following call::
-
-  # curl -H 'token: my-token' http://localhost:8042/patients/6eeded74-75005003-c3ae9738-d4a06a4f-6beedeb8
-
-.. highlight:: json
-
-Here is the JSON body the Web service would receive::
+**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",
-    "token-key" : "token",
-    "token-value" : "my-token"
+    ...
+  }
+  {
+    "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",
+    ...
   }
 
-.. highlight:: text
+
+**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 how the key and the value of the authentication token stored as a
-HTTP header are forwarded to the Web service.
+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
 
-The same mechanism can be used if the authentication token is provided
-as some ``GET`` argument by setting the ``TokenGetArguments``
-configuration option::
+
+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::
 
-  # curl http://localhost:8042/patients/6eeded74-75005003-c3ae9738-d4a06a4f-6beedeb8?token=my-token
   {
-    "dicom-uid" : "123ABC",
-    "level" : "patient",
-    "method" : "get",
-    "orthanc-id" : "6eeded74-75005003-c3ae9738-d4a06a4f-6beedeb8",
-    "token-key" : "token",
-    "token-value" : "my-token"
+    "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
@@ -273,17 +401,15 @@
 
   {
     "Authorization" : {
-        // The Base URL of the auth webservice.  This is an alias for all 3 next configurations:
+        // 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 3 routes !
+        // 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)
-        // (this configuration was previously named "WebService" and its old name is still accepted
-        //  for backward compatibility)
         // "WebServiceUserProfileUrl" : "http://change-me:8000/user/profile",
 
         // The URL of the auth webservice route implementing resource level authorization (optional)
@@ -299,7 +425,8 @@
         //"WebServiceUsername": "change-me",
         //"WebServicePassword": "change-me",
         
-        // An identifier added to the payload of each request to the auth webservice (optional)
+        // 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
@@ -366,6 +493,10 @@
   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>`::
 
@@ -391,13 +522,7 @@
 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 all routes that the webservice might provide:
-
-- ``/tokens/validate`` to validate tokens identifying either a user or granting access to a single resource
-- ``/tokens/{token_type}`` to generate tokens granting access to specific DICOM resources.
-- ``/tokens/decode`` to extract the info from a token
-- ``/user/get-profile`` to return the user profile linked to a given token.  This profile
-  includes a list of permissions.
+This sample also shows how to implement the 4 routes that the webservice might provide.
 
 
 .. _orthanc-explorer-authorization: