Skip to content

Commit

Permalink
clean up
Browse files Browse the repository at this point in the history
  • Loading branch information
bcrickboom committed Jun 5, 2024
1 parent df1bf22 commit 5652b8a
Show file tree
Hide file tree
Showing 11 changed files with 297 additions and 15 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM orthancteam/orthanc:24.3.4
FROM orthancteam/orthanc:24.5.1

COPY robust-dicomweb-forwarder.lua /scripts/

Expand Down
21 changes: 19 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,33 @@ Just before the deletion of an instance, the script will try to apply a label to
## How to use it ?

- configure Orthanc as usually (through env var or via json)
- add these mandatory env var:

- add these env var:
```
DESTINATION_URL: "http://orthanc.team:8042/dicom-web"
```
and either this (basic auth):
```
DESTINATION_USER: "demo"
DESTINATION_PASSWORD: "demo"
```
- add this env var to apply a label to the forwarder studies (on the destination Orthanc)
or this (Keycloak setup)
```
DESTINATION_API_KEY: "forwarder-api-key"
```

- optional: add this env var to apply a label to the forwarder studies (on the destination Orthanc)
```
DESTINATION_LABEL: "MY-HOSPITAL"
```
- if you want to apply labels on forwarded studies, make sure that the receiving Orthanc is reachable on the dicom-web route (of course) but also on these 2 ones:
- `/studies/{orthancId}/labels/{label}`
- `/tools/lookup`

If you use Keycloak and Auth plugin, this should be the case if you give the permissions in the json file as follow:
```
"upload-role": {
"permissions":["upload", "edit-labels"],
"authorized_labels": ["*"]
}
```
8 changes: 5 additions & 3 deletions release-notes.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
Warning: version number should fit with Orthanc docker image version number!
(so a new version of the forwarder with the same version as Orthanc should be tagged 22.10.1.x)

v 24.3.4.0
v 24.5.1.0
==========
- upgraded Orthanc docker image to 24.5.1
- added `api-key` mode

v 24.3.4.0
==========
- upgraded Orthanc docker image to 24.3.4


Expand All @@ -14,11 +18,9 @@ v 23.4.0

v 22.10.1
=========

- upgraded Orthanc docker image to 22.10.1


v 22.10.0
=========

- first release
39 changes: 34 additions & 5 deletions robust-dicomweb-forwarder.lua
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,24 @@ function Initialize()
userDestination = os.getenv("DESTINATION_USER")
passwordDestination = os.getenv("DESTINATION_PASSWORD")
labelToApply = os.getenv("DESTINATION_LABEL")

apiKey = os.getenv("DESTINATION_API_KEY")

-- configure the dicomweb destination in Orthanc
local body = '{"Url":"' .. urlDestination .. '","Username":"' .. userDestination .. '","Password":"' .. passwordDestination .. '"}'
RestApiPut("/dicom-web/servers/destination", body, false)
local payload = {}
payload["Url"] = urlDestination

if apiKey ~= nil then
-- api key case
local payload2 = {}
payload2["api-key"] = apiKey
payload["HttpHeaders"] = payload2
else
-- user/password case
payload["Username"] = userDestination
payload["Password"] = passwordDestination
end
RestApiPut("/dicom-web/servers/destination", DumpJson(payload, true), false)

-- prepare urlDestination for calls to api
urlDestination = string.gsub(urlDestination, "/dicom%-web", "")
Expand All @@ -47,6 +61,7 @@ urlDestination = ""
userDestination = ""
passwordDestination = ""
labelToApply = ""
apiKey = ""

labeledStudyInstanceUIDs = {}
index = 1
Expand Down Expand Up @@ -154,7 +169,14 @@ function GetRemoteStudyId(studyInstanceUID)
-- gets the orthanc study id from the destination Orthanc
SetHttpCredentials(userDestination, passwordDestination)
SetHttpTimeout(1)
local headers = {["content-type"] = "application/json",}

local headers = {}
if apiKey ~= nil then
-- api key case (api-key will take over the user/password if both are provided)
headers = {["content-type"] = "application/json", ["api-key"] = apiKey}
else
headers = {["content-type"] = "application/json",}
end
local response = HttpPost(urlDestination .. "/tools/lookup", studyInstanceUID, headers)

-- print(ParseJson(response)[1]["ID"])
Expand All @@ -165,8 +187,15 @@ function ApplyLabel(orthancId, label)
-- call the API route to apply the label to the study in the distant Orthanc
SetHttpCredentials(userDestination, passwordDestination)
SetHttpTimeout(1)

HttpPut(urlDestination .. "/studies/" .. orthancId .. "/labels/" .. label, "")

local headers = {}
if apiKey ~= nil then
-- api key case (api-key will take over the user/password if both are provided)
headers = {["api-key"] = apiKey,}
else
headers = nil
end
HttpPut(urlDestination .. "/studies/" .. orthancId .. "/labels/" .. label, "", headers)
end

function MarkStudyInstanceUIDAsLabeled(studyInstanceUID)
Expand Down
129 changes: 129 additions & 0 deletions tests-api-key/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
version: "3.8"

services:
orthanc-source:
build: ..
ports: ["10042:8042"]
restart: unless-stopped
environment:
ORTHANC__AUTHENTICATION_ENABLED: "false"
ORTHANC__DICOM_AET: "ORTHANCFORWARDER"
DESTINATION_URL: "http://nginx/orthanc/dicom-web"
DESTINATION_API_KEY: "forwarder-api-key"
DESTINATION_LABEL: "MYLABEL"
VERBOSE_ENABLED: "true"

nginx:
image: orthancteam/orthanc-nginx:24.5.1
depends_on: [orthanc, orthanc-auth-service, keycloak]
restart: unless-stopped
ports: ["80:80"]
environment:
ENABLE_ORTHANC: "true"
ENABLE_KEYCLOAK: "true"
ENABLE_ORTHANC_TOKEN_SERVICE: "false"
ENABLE_HTTPS: "false"

# orthanc-destination
orthanc:
image: orthancteam/orthanc:24.5.1
restart: unless-stopped
environment:
ORTHANC__AUTHENTICATION_ENABLED: "false"
ORTHANC__POSTGRESQL__HOST: "orthanc-db"
ORTHANC__POSTGRESQL__ENABLE_STORAGE: "true"
ORTHANC__DATABASE_SERVER_IDENTIFIER: "orthanc1"
ORTHANC__DICOM_AET: "DESTINATION"
VERBOSE_ENABLED: "true"
ORTHANC_JSON: |
{
"DicomWeb" : {
"Enable" : true,
"PublicRoot": "/orthanc/dicom-web/"
},
"OrthancExplorer2": {
"Keycloak" : {
"Enable": true,
"Url": "http://localhost/keycloak/",
"Realm": "orthanc",
"ClientId": "orthanc"
}
},
"Authorization": {
"WebServiceRootUrl": "http://orthanc-auth-service:8000/",
"WebServiceUsername": "share-user",
"WebServicePassword": "change-me",
"StandardConfigurations" : [
"stone-webviewer",
"orthanc-explorer-2"
],
"CheckedLevel": "studies",
"TokenHttpHeaders" : [ "api-key" ]
}
}
orthanc-destination-2:
image: orthancteam/orthanc:24.5.1
ports: ["10043:8042"]
restart: unless-stopped
environment:
ORTHANC__AUTHENTICATION_ENABLED: "true"
ORTHANC__POSTGRESQL__HOST: "orthanc-db"
ORTHANC__POSTGRESQL__ENABLE_STORAGE: "true"
ORTHANC__DATABASE_SERVER_IDENTIFIER: "orthanc2"
ORTHANC__DICOM_AET: "DESTINATION"
VERBOSE_ENABLED: "true"
ORTHANC_JSON: |
{
"RegisteredUsers" : {
"demo" : "demo"
},
"DicomWeb" : {
"Enable" : true
}
}
orthanc-db:
image: postgres:14
restart: unless-stopped
environment:
POSTGRES_HOST_AUTH_METHOD: "trust"

orthanc-auth-service:
image: orthancteam/orthanc-auth-service:24.5.1
depends_on: [keycloak]
restart: unless-stopped
environment:
ENABLE_KEYCLOAK: "true"
ENABLE_KEYCLOAK_API_KEYS: "true"
KEYCLOAK_CLIENT_SECRET: "sVh1itTUBXQ5YMKiYHYDoPcO1l1HbAnU"
PUBLIC_ORTHANC_ROOT: "http://localhost/orthanc/"
PUBLIC_LANDING_ROOT: "http://localhost/orthanc/ui/app/token-landing.html"
PERMISSIONS_FILE_PATH: "/orthanc_auth_service/permissions.json"
SECRET_KEY: "change-me-I-am-a-secret-key"
USERS: |
{
"share-user": "change-me"
}
volumes:
- ./permissions.json:/orthanc_auth_service/permissions.json

keycloak:
image: orthancteam/orthanc-keycloak:24.5.1
depends_on: [keycloak-db]
restart: unless-stopped
environment:
KEYCLOAK_ADMIN: "admin"
KEYCLOAK_ADMIN_PASSWORD: "change-me"
KC_DB: "postgres"
KC_DB_URL: "jdbc:postgresql://keycloak-db:5432/keycloak"
KC_DB_USERNAME: "keycloak"
KC_DB_PASSWORD: "keycloak"

keycloak-db:
image: postgres:14
restart: unless-stopped
environment:
POSTGRES_PASSWORD: "keycloak"
POSTGRES_USER: "keycloak"
POSTGRES_DB: "keycloak"
16 changes: 16 additions & 0 deletions tests-api-key/permissions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"roles" : {
"admin-role": {
"permissions": ["all"],
"authorized_labels": ["*"]
},
"doctor-role": {
"permissions":["view", "download", "share", "send"],
"authorized_labels": ["*"]
},
"upload-role": {
"permissions":["upload", "edit-labels"],
"authorized_labels": ["*"]
}
}
}
File renamed without changes.
88 changes: 88 additions & 0 deletions tests-api-key/test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import time
import unittest
import subprocess
import tempfile
import datetime
from orthanc_api_client import OrthancApiClient, ChangeType
from orthanc_api_client import helpers
import pathlib
import os
import logging
from orthanc_tools import OrthancTestDbPopulator

'''
This test has to be run manually.
Steps to make the setup working:
- run the compose file
- go to the keycloak web interface
- do the stuff to get api-key feature (see tips)
- create a `upload-role`
- create a `upload-user`
- add the Orthanc api-key in the attributes of this user (`forwarder-api-key`)
- get the Keycloak api-key and put it in the compose file
- redo a compose up, only the auth service should restart
- run the test.py from terminal
CAUTION: if you run this test 2 times in a row, it will fail (expected).
restart the source orthanc-source container to avoid this problem.
'''

here = pathlib.Path(__file__).parent.resolve()

logger = logging.getLogger('dicom-web-forwarder')
logger.setLevel(logging.DEBUG)
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
ch.setFormatter(formatter)
logger.addHandler(ch)

label = "MYLABEL"

class TestForwarder(unittest.TestCase):

@classmethod
def setUpClass(cls):

cls.oa = OrthancApiClient('http://localhost:10042')
cls.oa.wait_started()
cls.ob = OrthancApiClient('http://localhost:10043', user='demo', pwd='demo')
cls.ob.wait_started()

def test_studies_are_labeled(self):
self.oa.delete_all_content()
self.ob.delete_all_content()

# let's fill the orthanc A (10 studies, = circular memory size)
populator_a = OrthancTestDbPopulator(api_client=self.oa, studies_count=10, random_seed=42, series_count = 1, instances_count=1)
populator_a.execute()

# lua will forward to the orthanc B and tag them
helpers.wait_until(lambda: len(self.oa.studies.get_all_ids()) == 0, timeout=10)

b_ids = self.ob.studies.get_all_ids()
self.assertEqual(len(b_ids), 10)

# let's check the labeling
for id in b_ids:
self.assertEqual(self.ob.studies.get_labels(id)[0], label)
self.oa.delete_all_content()
self.ob.delete_all_content()

# let's fill the orthanc A again (10 studies, = circular memory size)
populator_a = OrthancTestDbPopulator(api_client=self.oa, studies_count=10, random_seed=42, series_count=1, instances_count=1)
populator_a.execute()

# lua will forward to the orthanc B and shouldn't tag them (because the ids are in the circular memory)
helpers.wait_until(lambda: len(self.oa.studies.get_all_ids()) == 0, timeout=10)

b_ids = self.ob.studies.get_all_ids()
self.assertEqual(len(b_ids), 10)

# let's check the labeling
self.assertEqual(len(self.ob.get_all_labels()), 0)

if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
unittest.main()

Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ services:
VERBOSE_ENABLED: "true"

orthanc-destination:
image: orthancteam/orthanc:24.3.4
image: orthancteam/orthanc:24.5.1
ports: ["10043:8042"]
restart: unless-stopped
environment:
Expand All @@ -30,4 +30,4 @@ services:
"DicomWeb" : {
"Enable" : true
}
}
}
2 changes: 2 additions & 0 deletions tests-basic-auth/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
orthanc-api-client>=0.15.1
orthanc_tools>=0.12.9
Loading

0 comments on commit 5652b8a

Please sign in to comment.