This is a bit of a specific use case, I’m not sure if it’s even possible. I’m trying to implement a custom authorization solution for Orthanc using JWTs to provide fine-grained access control based on a users ID. The idea is this:
-
User A sends a POST request to store a DICOM instance in Orthanc, with a JWT header containing their userID.
-
The IncomingHttpRequestFilter() decodes the JWT and extracts the userID.
-
The userID is stored in the instances custom user-defined metadata.
-
User B attempts a GET request to the resource with his own auth token in the header.
-
User B’s userID is extracted from the JWT, compared with the instances metadata and is denied as it is not listed there.
-
User A attempts a GET request to the resource, goes through step 5 but is approved as their userId is listed there.
I have the logic and the code for most of it worked out - except for step 3. Most metadata/tag manipulation seems to be done in the onStoredInstance callback, but at that stage the function has no access to the userID in the request header.
If step 3 is possible, how would I go about it in the lua scripting? If it isn’t possible or recommended, what is the alternative to get a level of fine-grained access control that won’t be terribly slow when retrieving 300+ instances for, say, a scan being retrieved by an OHIF viewer?
Best regards
Evan
Hi Evan,
Instead of lua, I would use the python sdk, it will give you more control over the whole process.
E.g, for step 3, you could override the /instances route, send the DICOM file to Orthanc to the original /instances route, then, access the metadata of the instance.
For the GET access control, you can indeed implement it with the IncomingHttpFilter (in python or lua). Accessing the metadata for each instance will probably slow down the whole process but you’ll have to measure it since I don’t have any figures.
You might want to implement access control at the study level instead such that you can build a cache and avoid accessing the metadata for each instance. The now deprecated advanced authorization plugin worked that way - you could get some inspiration form its source code.
HTH,
Alain.
Thanks a lot Alain! I’ll certainly look into the python solution. Would you happen to have an example of overriding the instances route using it?
That would look like this:
def OnRestInstances(output, uri, **request):
pprint.pprint(request)
if request[‘method’] == ‘GET’:
answer_from_core_api = orthanc.RestApiGet(‘/instances’)
output.AnswerBuffer(answer_from_core_api, ‘application/json’)
elif request[‘method’] == ‘POST’:
answer_from_core_api = orthanc.RestApiPost(‘/instances’, request[‘body’])
do whatever you need here
output.AnswerBuffer(answer_from_core_api, ‘application/json’)
orthanc.RegisterRestCallback(‘/instances’, OnRestInstances)
Hi,
Does the plugin capture DELETE actions via the OnRestInstances callback?
My quick experimentation suggests not.
Thanks
Darren
Dear Darren,
Yes, DELETE requests can be captured by Python plugins as well.
First, install the sample Python script from the section “Extending the REST API” of the Orthanc Book:
https://book.orthanc-server.com/plugins/python.html#extending-the-rest-api
I copy/paste its code below:
import orthanc
import pprint
def OnRestInstances(output, uri, **request):
pprint.pprint(request)
print(‘Accessing uri: %s’ % uri)
output.AnswerBuffer(‘ok\n’, ‘text/plain’)
orthanc.RegisterRestCallback(‘/tata’, OnRestInstances)
Secondly, issue the following sample DELETE request using curl command-line tool:
$ curl http://localhost:8042/tata -X DELETE
ok
The Orthanc logs read as follows, which shows that DELETE actions are also captured:
[…]
W0610 06:26:00.078656 main.cpp:876] Orthanc has started
{‘groups’: (),
‘headers’: {‘accept’: u’/‘,
‘host’: u’localhost:8042’,
‘user-agent’: u’curl/7.58.0’},
‘method’: u’DELETE’}
Accessing uri: /tata
HTH,
Sébastien-
Hi Darren,
In your case, the DELETE is actually not sent to the /instances route but to /instances/{id} so you actually need to register another callback:
def OnRestInstances(output, uri, **request):
pprint.pprint(request)
if request[‘method’] == ‘GET’:
answer_from_core_api = orthanc.RestApiGet(‘/instances’)
output.AnswerBuffer(answer_from_core_api, ‘application/json’)
elif request[‘method’] == ‘POST’:
answer_from_core_api = orthanc.RestApiPost(‘/instances’, request[‘body’])
do whatever you need here
output.AnswerBuffer(answer_from_core_api, ‘application/json’)
def OnRestInstance(output, uri, **request):
pprint.pprint(request)
if request[‘method’] == ‘GET’:
Retrieve the instance ID from the regular expression (*)
instanceId = request[‘groups’][0]
answer_from_core_api = orthanc.RestApiGet(f’/instances/{instanceId}')
output.AnswerBuffer(answer_from_core_api, ‘application/json’)
elif request[‘method’] == ‘DELETE’:
Retrieve the instance ID from the regular expression (*)
instanceId = request[‘groups’][0]
orthanc.RestApiDelete(f’/instances/{instanceId}') # note: no answer from core api
orthanc.RegisterRestCallback(‘/instances’, OnRestInstances)
orthanc.RegisterRestCallback(‘/instances/(.*)’, OnRestInstance)
Best regards,
Alain.