# HG changeset patch # User Sebastien Jodogne # Date 1749740364 -7200 # Node ID ba3295716819fc0e85c86c6cccffe9dc7871cdb4 # Parent f094845ac9a24d08c2397b860519be32449dba92# Parent b4ac775869b28bcdcaa4f46cca94df14e4105a0f integration mainline->attach-custom-data diff -r f094845ac9a2 -r ba3295716819 NewTests/Authorization/auth_service.py --- a/NewTests/Authorization/auth_service.py Tue May 27 17:07:49 2025 +0200 +++ b/NewTests/Authorization/auth_service.py Thu Jun 12 16:59:24 2025 +0200 @@ -78,3 +78,21 @@ logging.info("validate token: " + response.json()) return response + +@app.post("/tokens/decode") +def decode_token(request: TokenDecoderRequest): + + logging.info("decoding token: " + request.json()) + response = TokenDecoderResponse(resources=[]) + + if request.token_value == "token-a-study" or request.token_value == "token-both-studies": + response.resources.append(OrthancResource(level=Levels.STUDY, + orthanc_id="b9c08539-26f93bde-c81ab0d7-bffaf2cb-a4d0bdd0", + dicom_uid="1.2.840.113619.2.176.2025.1499492.7391.1171285944.390")) + if request.token_value == "token-b-study" or request.token_value == "token-both-studies": + response.resources.append(OrthancResource(level=Levels.STUDY, + orthanc_id="27f7126f-4f66fb14-03f4081b-f9341db2-53925988", + dicom_uid="2.16.840.1.113669.632.20.1211.10000357775")) + + logging.info("decoded token: " + response.json()) + return response diff -r f094845ac9a2 -r ba3295716819 NewTests/Authorization/models.py --- a/NewTests/Authorization/models.py Tue May 27 17:07:49 2025 +0200 +++ b/NewTests/Authorization/models.py Thu Jun 12 16:59:24 2025 +0200 @@ -1,4 +1,4 @@ -from typing import Optional, List +from typing import Optional, List, Dict from pydantic import BaseModel, Field from enum import Enum from datetime import datetime @@ -31,6 +31,7 @@ MEDDREAM_VIEWER_PUBLICATION = 'meddream-viewer-publication' # a link to open the MedDream viewer valid for a long period STONE_VIEWER_PUBLICATION = 'stone-viewer-publication' # a link to open the Stone viewer valid for a long period OHIF_VIEWER_PUBLICATION = 'ohif-viewer-publication' # a link to open the OHIF viewer valid for a long period + VOLVIEW_VIEWER_PUBLICATION = 'volview-viewer-publication' # a link to open the VolView viewer valid for a long period MEDDREAM_INSTANT_LINK = 'meddream-instant-link' # a direct link to MedDream viewer that is valid only a few minutes to open the viewer directly @@ -80,7 +81,6 @@ level: Optional[Levels] method: Methods uri: Optional[str] = None -# labels: Optional[List[str]] class TokenValidationResponse(BaseModel): @@ -97,7 +97,7 @@ token_type: Optional[TokenType] = Field(alias="token-type", default=None) error_code: Optional[DecoderErrorCodes] = Field(alias="error-code", default=None) redirect_url: Optional[str] = Field(alias="redirect-url", default=None) - + resources: List[OrthancResource] class UserProfileRequest(BaseModel): token_key: Optional[str] = Field(alias="token-key", default=None) @@ -118,16 +118,30 @@ SETTINGS = 'settings' API_VIEW = 'api-view' EDIT_LABELS = 'edit-labels' + ADMIN_PERMISSIONS = 'admin-permissions' SHARE = 'share' -class UserProfileResponse(BaseModel): - name: str +class RolePermissions(BaseModel): authorized_labels: List[str] = Field(alias="authorized-labels", default_factory=list) permissions: List[UserPermissions] = Field(default_factory=list) + + class Config: + use_enum_values = True + populate_by_name = True # allow creating object from dict (used when deserializing the permission file) + + +class UserProfileResponse(RolePermissions): + name: str + # authorized_labels: List[str] = Field(alias="authorized-labels", default_factory=list) + # permissions: List[UserPermissions] = Field(default_factory=list) validity: int class Config: use_enum_values = True - populate_by_name = True \ No newline at end of file + populate_by_name = True + +class RolesConfigurationModel(BaseModel): + roles: Dict[str, RolePermissions] # role/permissions mapping + available_labels: List[str] = Field(alias="available-labels", default_factory=list) # if empty, everyone can create additionnal labels, if not, they can only add/remove the listed labels diff -r f094845ac9a2 -r ba3295716819 NewTests/Authorization/test_authorization.py --- a/NewTests/Authorization/test_authorization.py Tue May 27 17:07:49 2025 +0200 +++ b/NewTests/Authorization/test_authorization.py Thu Jun 12 16:59:24 2025 +0200 @@ -371,9 +371,11 @@ # with a resource token, we can access only the given resource, not generic resources or resources from other studies # generic resources are forbidden + # note: even tools/find is still forbidden in 0.9.3 (but not /dicom-web/studies -> see below) self.assert_is_forbidden(lambda: o.studies.find(query={"PatientName": "KNIX"}, # tools/find is forbidden with a resource token labels=['label_b'], labels_constraint='Any')) + self.assert_is_forbidden(lambda: o.get_all_labels()) self.assert_is_forbidden(lambda: o.studies.get_all_ids()) self.assert_is_forbidden(lambda: o.patients.get_all_ids()) @@ -415,6 +417,12 @@ o.get_json(f"dicom-web/series?0020000D={self.label_a_study_dicom_id}") o.get_json(f"dicom-web/instances?0020000D={self.label_a_study_dicom_id}") + if o.is_plugin_version_at_least("authorization", 0, 9, 3): + # equivalent to the prior studies request in OHIF + self.assertEqual(1, len(o.get_json(f"dicom-web/studies?PatientID={self.label_a_patient_dicom_id}"))) + self.assertEqual(0, len(o.get_json(f"dicom-web/studies?PatientID={self.label_b_patient_dicom_id}"))) + + if self.o.is_orthanc_version_at_least(1, 12, 2): o.get_binary(f"tools/create-archive?resources={self.label_a_study_id}") o.get_binary(f"tools/create-archive?resources={self.label_a_series_id}") diff -r f094845ac9a2 -r ba3295716819 NewTests/PostgresUpgrades/downgrade.sh --- a/NewTests/PostgresUpgrades/downgrade.sh Tue May 27 17:07:49 2025 +0200 +++ b/NewTests/PostgresUpgrades/downgrade.sh Thu Jun 12 16:59:24 2025 +0200 @@ -4,12 +4,15 @@ apt-get update && apt-get install -y wget mercurial hg clone https://orthanc.uclouvain.be/hg/orthanc-databases +pushd orthanc-databases + # TODO: change attach-custom-data by the plugin version number or "default" ! -pushd orthanc-databases hg update -r attach-custom-data psql -U postgres -f /scripts/orthanc-databases/PostgreSQL/Plugins/SQL/Downgrades/Rev5ToRev4.sql +psql -U postgres -f /scripts/orthanc-databases/PostgreSQL/Plugins/SQL/Downgrades/Rev4ToRev3.sql # if you want to test a downgrade procedure, you may use this code ... # psql -U postgres -f downgrade.sql popd +popd diff -r f094845ac9a2 -r ba3295716819 Tests/Tests.py --- a/Tests/Tests.py Tue May 27 17:07:49 2025 +0200 +++ b/Tests/Tests.py Thu Jun 12 16:59:24 2025 +0200 @@ -907,9 +907,19 @@ self.assertEqual(0, DoGet(_REMOTE, '/patients/%s/protected' % a)) DoPut(_REMOTE, '/patients/%s/protected' % a, '1', 'text/plain') self.assertEqual(1, DoGet(_REMOTE, '/patients/%s/protected' % a)) + + if IsOrthancVersionAbove(_REMOTE, 1, 12, 8): + p = DoGet(_REMOTE, '/patients/%s' % a) + self.assertIn('IsProtected', p) + self.assertTrue(p['IsProtected']) + DoPut(_REMOTE, '/patients/%s/protected' % a, '0', 'text/plain') self.assertEqual(0, DoGet(_REMOTE, '/patients/%s/protected' % a)) + if IsOrthancVersionAbove(_REMOTE, 1, 12, 8): + p = DoGet(_REMOTE, '/patients/%s' % a) + self.assertIn('IsProtected', p) + self.assertFalse(p['IsProtected']) def test_raw_tags(self): i = UploadInstance(_REMOTE, 'PrivateTags.dcm')['ID'] @@ -10872,13 +10882,21 @@ a = DoGet(_REMOTE, '/patients?expand') self.assertEqual(1, len(a)) - self.assertEqual(7, len(a[0])) + if IsOrthancVersionAbove(_REMOTE, 1, 12, 8): + self.assertEqual(8, len(a[0])) + self.assertTrue('IsProtected' in a[0]) + else: + self.assertEqual(7, len(a[0])) CheckPatientContent(a[0]) self.assertFalse('RequestedTags' in a[0]) a = DoGet(_REMOTE, '/patients?expand&requestedTags=%s' % requestedTags) self.assertEqual(1, len(a)) - self.assertEqual(8, len(a[0])) + if IsOrthancVersionAbove(_REMOTE, 1, 12, 8): + self.assertEqual(9, len(a[0])) + self.assertTrue('IsProtected' in a[0]) + else: + self.assertEqual(8, len(a[0])) CheckPatientContent(a[0]) CheckRequestedTags(a[0]) @@ -10919,12 +10937,20 @@ CheckRequestedTags(a[0]) a = DoGet(_REMOTE, '/patients/%s' % u['ParentPatient']) - self.assertEqual(7, len(a)) + if IsOrthancVersionAbove(_REMOTE, 1, 12, 8): + self.assertEqual(8, len(a)) + self.assertTrue('IsProtected' in a) + else: + self.assertEqual(7, len(a)) CheckPatientContent(a) self.assertFalse('RequestedTags' in a) a = DoGet(_REMOTE, '/patients/%s?requestedTags=%s' % (u['ParentPatient'], requestedTags)) - self.assertEqual(8, len(a)) + if IsOrthancVersionAbove(_REMOTE, 1, 12, 8): + self.assertEqual(9, len(a)) + self.assertTrue('IsProtected' in a) + else: + self.assertEqual(8, len(a)) CheckPatientContent(a) CheckRequestedTags(a) @@ -11519,6 +11545,7 @@ self.assertIn('IsStable', a[0]) self.assertNotIn('Attachments', a[0]) self.assertNotIn('Metadata', a[0]) + self.assertNotIn('IsProtected', a[0]) a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Series', @@ -11542,6 +11569,7 @@ self.assertNotIn('IsStable', a[0]) self.assertNotIn('Attachments', a[0]) self.assertNotIn('Metadata', a[0]) + self.assertNotIn('IsProtected', a[0]) a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Series', @@ -11564,6 +11592,7 @@ self.assertIn('Status', a[0]) self.assertIn('IsStable', a[0]) self.assertNotIn('Attachments', a[0]) + self.assertNotIn('IsProtected', a[0]) a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Instances', @@ -11585,6 +11614,7 @@ self.assertIn('Labels', a[0]) self.assertNotIn('Attachments', a[0]) self.assertNotIn('Metadata', a[0]) + self.assertNotIn('IsProtected', a[0]) a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Instances', 'Query' : { @@ -11616,6 +11646,18 @@ self.assertIn('RequestedTags', a[0]) # the RequestedTags are always in the response as soon as you have requested them self.assertIn('SOPClassUID', a[0]['RequestedTags']) + if IsOrthancVersionAbove(_REMOTE, 1, 12, 8): + a = DoPost(_REMOTE, '/tools/find', { 'Level' : 'Patients', + 'Query' : { + }, + 'ResponseContent' : ['IsProtected'] + }) + + self.assertIn('ID', a[0]) # the ID is always in the response + self.assertIn('Type', a[0]) # the Type is always in the response + self.assertIn('IsProtected', a[0]) + + def test_extended_find_full(self): if IsOrthancVersionAbove(_REMOTE, 1, 12, 5) and HasExtendedFind(_REMOTE):