Skip to content

Commit 184d730

Browse files
authored
[dicom_archive] Dicom archive siteproject main (#9549)
This adds advanced site and project permissions to the dicom archive. It is designed to be fully backwards compatible based on the `useImagingSiteProjectPermissions` configuration setting. Here is how the permissions are designed to work - If useImagingSiteProjectPermissions is disabled (status-quo) - A user with dicom_archive_allsites can see all the data (DEFAULT) - A user with own sites can only see data at their own sites (can not see data with no session ID) - A user with own sites and nosessionid permission can see their site data + dicoms not associated to a session - A user can always see DICOMS of scans they uploaded - If useImagingSiteProjectPermissions is enabled - A user with all sites permissions can see all the data from all sites as long as the projectID is defined and they are affiliated with that project (null sessions WILL NOT SHOW) - A user with own sites, same logic, only projects they are affiliated with (NO NULL sessions) - A user with either site permissions + nosessionID permission can see respectively the same as above + DICOMS not affiliated to a sessions - A user can always see DICOMS they have uploaded TO BE NOTED: The core of this is the work done in the `dicom_archive::getDataProvisionerWithFilters()` function. The code there could have been made slightly more concise but I wrote it in that way because I would like it to eventually become the DEFAULT dataframework filtering logic since it accounts for most if not all usecases (replacing the current UserSiteMatchOrHasAnyPermissions). It defines flags that I would like to make standard going forward like `dataSessionCanBeNull` where multiple module seem to lack a session related to the resource.
1 parent dd6e297 commit 184d730

14 files changed

+359
-81
lines changed

SQL/0000-00-02-Permission.sql

+3-1
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,9 @@ INSERT INTO `permissions` (code, description, moduleID, action, categoryID) VALU
144144
('issue_tracker_close_site_issue','Issues - Own Sites',(SELECT ID FROM modules WHERE Name = 'issue_tracker'),'Close',2),
145145
('issue_tracker_close_all_issue','Issues - All Sites',(SELECT ID FROM modules WHERE Name = 'issue_tracker'),'Close',2),
146146
('imaging_uploader_ownsites', 'Imaging Scans - Own Sites', (SELECT ID FROM modules WHERE Name='imaging_uploader'), 'View', '2'),
147-
('imaging_uploader_nosessionid', 'Imaging Scans with no session ID', (SELECT ID FROM modules WHERE Name='imaging_uploader'), 'View', '2')
147+
('imaging_uploader_nosessionid', 'Imaging Scans with no session ID', (SELECT ID FROM modules WHERE Name='imaging_uploader'), 'View', '2'),
148+
('dicom_archive_nosessionid', 'DICOMs with no session ID', (SELECT ID FROM modules WHERE Name='dicom_archive'), 'View', '2'),
149+
('dicom_archive_view_ownsites', 'DICOMs - Own Sites', (SELECT ID FROM modules WHERE Name='dicom_archive'), 'View', '2')
148150
;
149151

150152
INSERT INTO `user_perm_rel` (userID, permID)

SQL/0000-00-03-ConfigTables.sql

-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,6 @@ INSERT INTO ConfigSettings (Name, Description, Visible, AllowMultiple, DataType,
101101
INSERT INTO ConfigSettings (Name, Description, Visible, AllowMultiple, DataType, Parent, Label, OrderNumber) SELECT 'tblScanTypes', 'Scan types from the mri_scan_type table that the project wants to see displayed in Imaging Browser table', 1, 1, 'scan_type', ID, 'Imaging Browser Tabulated Scan Types', 7 FROM ConfigSettings WHERE Name="imaging_modules";
102102
INSERT INTO ConfigSettings (Name, Description, Visible, AllowMultiple, DataType, Parent, Label, OrderNumber) SELECT 'ImagingBrowserLinkedInstruments', 'Instruments that the users want to see linked from Imaging Browser', 1, 1, 'instrument', ID, 'Imaging Browser Links to Instruments', 8 FROM ConfigSettings WHERE Name="imaging_modules";
103103

104-
105104
INSERT INTO ConfigSettings (Name, Description, Visible, AllowMultiple, Label, OrderNumber) VALUES ('statistics', 'Statistics module settings', 1, 0, 'Statistics', 7);
106105
INSERT INTO ConfigSettings (Name, Description, Visible, AllowMultiple, DataType, Parent, Label, OrderNumber) SELECT 'excludedMeasures', 'Instruments to be excluded from Statistics calculations', 1, 1, 'instrument', ID, 'Excluded instruments', 1 FROM ConfigSettings WHERE Name="statistics";
107106

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
INSERT INTO permissions (code, description, moduleID, `action`, categoryID)
2+
SELECT 'dicom_archive_nosessionid', 'DICOMs with no session ID', ID, 'View', 2 FROM modules WHERE Name='dicom_archive';
3+
4+
INSERT INTO permissions (code, description, moduleID, `action`, categoryID)
5+
SELECT 'dicom_archive_view_ownsites', 'DICOMs - Own Sites', ID, 'View', 2 FROM modules WHERE Name='dicom_archive';

modules/dicom_archive/README.md

+37-2
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,35 @@ user's system.
2525

2626
## Permissions
2727

28-
The permission `dicom_archive_view_allsites` is required to access
29-
the DICOM Archive module.
28+
*In the interest of backward compatibility, permission behaviour varies slightly
29+
based on the `useAdvancedPermissions` configuration*
30+
31+
Any of the following permissions grants access to the module.
32+
33+
`dicom_archive_view_allsites`:
34+
- If `useAdvancedPermissions` is disabled, this permission gives access
35+
to all DICOMs in the database (backward compatible with projects not requiring a
36+
session ID to be defined).
37+
- If `useAdvancedPermissions` is enabled, this permission gives access to
38+
all DICOMs as long as they are associated to a session and the session is affiliated
39+
to a project that the user is affiliated with. When combined with
40+
`dicom_archive_nosessionid`, user gets access to their projects' data as well as
41+
DICOMs with no session ID associated.
42+
43+
`dicom_archive_view_ownsites`:
44+
- If `useAdvancedPermissions` is disabled, this permission gives access
45+
to all DICOMs as long as they are associated to a session and the session is affiliated
46+
to a site that the user is affiliated with. When combined with
47+
`dicom_archive_nosessionid`, user gets access to their sites' data as well as
48+
DICOMs with no session ID associated.
49+
- If `useAdvancedPermissions` is enabled, this permission gives access to
50+
all DICOMs as long as they are associated to a session and the session is affiliated
51+
to both a site and a project that the user is affiliated with. When combined with
52+
`dicom_archive_nosessionid`, user gets access to their sites' and projects' data as well as
53+
DICOMs with no session ID associated.
54+
55+
Note that if you have access to the module, you will always see DICOMS uploaded by
56+
your own user regardless of their site and project affiliation.
3057

3158
## Configurations
3259

@@ -46,6 +73,14 @@ The `showTransferStatus` configuration option is obsolete and should
4673
not be used, but determines if a first "Transfer Status" column
4774
appears in the menu table.
4875

76+
The `useAdvancedPermissions` configuration enables more advanced Site and
77+
Project access control (Although Site permissions are enabled without this
78+
configuration, "all sites" gives access to DICOMS with no Session ID if this
79+
configuration is turned off). If enabled, users accessing the module can only see
80+
DICOMs where a session ID has been found and are thus linked to the site and project
81+
of the session AND the site and project match the user's. Access to DICOMs with no
82+
session is granted by the `dicom_archive_nosessionid` permission (see permissions section)
83+
4984
#### Install Configurations
5085

5186
For downloading large DICOM files, it may be necessary to increase the

modules/dicom_archive/php/dicom_archive.class.inc

+102-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@
1515
*/
1616
namespace LORIS\dicom_archive;
1717

18+
use LORIS\Data\Filters\CompositeORFilter;
19+
use LORIS\Data\Filters\CompositeANDFilter;
20+
use LORIS\Data\Filters\UserSiteMatch;
21+
use LORIS\Data\Filters\UserHasAnyPermission;
22+
use LORIS\Data\Filters\UserProjectMatch;
23+
use LORIS\Data\Filters\ResourceHasNoSession;
24+
use LORIS\Data\Filters\UserIsCreator;
25+
1826
/**
1927
* Provides the PHP code for the menu filter for the dicom archive
2028
*
@@ -27,6 +35,7 @@ namespace LORIS\dicom_archive;
2735
*/
2836
class Dicom_Archive extends \DataFrameworkMenu
2937
{
38+
protected $dataSessionCanBeNull = true;
3039

3140
/**
3241
* Determine whether the user has permission to view this page
@@ -37,7 +46,12 @@ class Dicom_Archive extends \DataFrameworkMenu
3746
*/
3847
function _hasAccess(\User $user) : bool
3948
{
40-
return $user->hasPermission('dicom_archive_view_allsites');
49+
return $user->hasAnyPermission(
50+
[
51+
'dicom_archive_view_allsites',
52+
'dicom_archive_view_ownsites',
53+
]
54+
);
4155
}
4256

4357
/**
@@ -59,7 +73,24 @@ class Dicom_Archive extends \DataFrameworkMenu
5973
*/
6074
public function useProjectFilter() : bool
6175
{
62-
return false;
76+
// Only enable project filtering if the site project permissions are enabled
77+
// to be compatible with projects not requiring a session ID for all DICOMS
78+
$config = \NDB_Factory::singleton()->config();
79+
$siteprojectperms = $config->getSetting(
80+
'useAdvancedPermissions'
81+
);
82+
return $siteprojectperms === 'true';
83+
}
84+
85+
/**
86+
* Determines which permissions (if any) allows the user access to resources
87+
* where the project is null.
88+
*
89+
* @return ?array
90+
*/
91+
public function nullSessionPermissionNames() : ?array
92+
{
93+
return ['dicom_archive_nosessionid'];
6394
}
6495

6596
/**
@@ -92,6 +123,75 @@ class Dicom_Archive extends \DataFrameworkMenu
92123
return $provisioner;
93124
}
94125

126+
/**
127+
* Return a data provisioner of the same type as BaseDataProvisioner, with
128+
* default LORIS filters applied. A subclass may override this to remove (or
129+
* change) filters.
130+
*
131+
* @return \LORIS\Data\Provisioner a provisioner with default filters added
132+
*/
133+
public function getDataProvisionerWithFilters() : \LORIS\Data\Provisioner
134+
{
135+
$provisioner = $this->getBaseDataProvisioner();
136+
$allSitePerms = $this->allSitePermissionNames();
137+
$nullSessionPerms = $this->nullSessionPermissionNames();
138+
139+
// Set filter default returns based on if the data loaded by this module can
140+
// have or not a null session
141+
$defaultReturn = $this->dataSessionCanBeNull ? false : null;
142+
143+
//Default filter to User Site Match
144+
$filter = new UserSiteMatch($defaultReturn);
145+
146+
//Combine filter with Null session permissions if data can have null sessions
147+
if ($this->dataSessionCanBeNull) {
148+
$filter = new CompositeORFilter(
149+
$filter,
150+
new CompositeANDFilter(
151+
new UserHasAnyPermission($nullSessionPerms),
152+
new ResourceHasNoSession(),
153+
),
154+
);
155+
}
156+
157+
// If the user has any of the All Site permissions, combine as an OR filter
158+
if (!empty($allSitePerms)) {
159+
$filter = new CompositeORFilter(
160+
$filter,
161+
new UserHasAnyPermission($allSitePerms),
162+
);
163+
}
164+
165+
// Check if module uses Project filters
166+
if ($this->useProjectFilter()) {
167+
//Default project filter to User Project Match
168+
$projectFilter = new UserProjectMatch($defaultReturn);
169+
170+
// Check if resources with null project should be included in the results
171+
if (!empty($nullSessionPerms)) {
172+
$projectFilter = new CompositeORFilter(
173+
new CompositeANDFilter(
174+
new ResourceHasNoSession(),
175+
new UserHasAnyPermission($nullSessionPerms)
176+
),
177+
new UserProjectMatch($defaultReturn)
178+
);
179+
}
180+
181+
$filter = new CompositeANDFilter($filter, $projectFilter);
182+
}
183+
184+
// Final filter, check if the user looking at the data is the user that
185+
// uploaded the data
186+
$filter = new CompositeORFilter(
187+
$filter,
188+
new UserIsCreator(),
189+
);
190+
191+
$provisioner = $provisioner->filter($filter);
192+
return $provisioner;
193+
}
194+
95195
/**
96196
* Overrides base getJSDependencies() to add support for dicom specific
97197
* React column formatters.

modules/dicom_archive/php/dicomarchiveanonymizer.class.inc

+13-3
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,20 @@ class DICOMArchiveAnonymizer implements Mapper
9999
}
100100

101101
if (!$resource instanceof \LORIS\StudyEntities\SiteHaver) {
102-
return new DICOMArchiveRowWithoutSession($newrow);
102+
'@phan-var object $resource';
103+
return new DICOMArchiveRowWithoutSession(
104+
$newrow,
105+
$resource->CreatedBy()
106+
);
103107
} else {
104-
$cid = $resource->getCenterID();
105-
return new DICOMArchiveRowWithSession($newrow, $cid);
108+
'@phan-var object $resource';
109+
return new DICOMArchiveRowWithSession(
110+
$newrow,
111+
$resource->getCenterID(),
112+
$resource->getProjectID(),
113+
$resource->getSessionID(),
114+
$resource->CreatedBy()
115+
);
106116
}
107117
}
108118
}

modules/dicom_archive/php/dicomarchiverowprovisioner.class.inc

+17-4
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@ class DicomArchiveRowProvisioner extends \LORIS\Data\Provisioners\DBRowProvision
5656
t.TarchiveID AS TarchiveID,
5757
s.ID AS SessionID,
5858
s.CenterID AS CenterID,
59-
m.IsPhantom AS IsPhantom
59+
s.ProjectID AS ProjectID,
60+
m.IsPhantom AS IsPhantom,
61+
t.CreatingUser
6062
FROM tarchive t
6163
LEFT JOIN session s ON (s.ID=t.SessionID)
6264
LEFT JOIN candidate c ON (c.ID=s.CandidateID)
@@ -77,12 +79,23 @@ class DicomArchiveRowProvisioner extends \LORIS\Data\Provisioners\DBRowProvision
7779
*/
7880
public function getInstance($row) : \LORIS\Data\DataInstance
7981
{
80-
if ($row['CenterID'] !== null) {
82+
$creator = \User::factory($row['CreatingUser']);
83+
84+
if ($row['CenterID'] !== null
85+
&& $row['ProjectID'] !== null
86+
&& $row['SessionID'] !== null
87+
) {
8188
$cid = \CenterID::singleton(intval($row['CenterID']));
89+
$pid = \ProjectID::singleton(intval($row['ProjectID']));
90+
$sid = new \SessionID(strval($row['SessionID']));
8291
unset($row['CenterID']);
83-
return new DICOMArchiveRowWithSession($row, $cid);
92+
unset($row['ProjectID']);
93+
unset($row['CreatingUser']);
94+
return new DICOMArchiveRowWithSession($row, $cid, $pid, $sid, $creator);
8495
}
8596
unset($row['CenterID']);
86-
return new DICOMArchiveRowWithoutSession($row);
97+
unset($row['ProjectID']);
98+
unset($row['CreatingUser']);
99+
return new DICOMArchiveRowWithoutSession($row, $creator);
87100
}
88101
}

modules/dicom_archive/php/dicomarchiverowwithoutsession.class.inc

+30-4
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,43 @@ namespace LORIS\dicom_archive;
2929
class DICOMArchiveRowWithoutSession implements \LORIS\Data\DataInstance
3030
{
3131
protected $DBRow;
32+
protected $CreatedBy;
33+
3234

3335
/**
3436
* Create a new DICOMArchiveRow without a session.
3537
*
3638
* Session-less DICOMArchiveRows do not have CenterIDs to be filtered
3739
* by.
3840
*
39-
* @param array $row The row (in the same format as \Database::pselectRow
40-
* returns)
41+
* @param array $row The row (in the same format as \Database::pselectRow
42+
* returns)
43+
* @param \User $creator The user who created this row
44+
*/
45+
public function __construct(array $row, \User $creator)
46+
{
47+
$this->DBRow = $row;
48+
$this->CreatedBy = $creator;
49+
}
50+
51+
/**
52+
* Returns Null always since this class is specifically for no session IDs.
53+
*
54+
* @return ?\SessionID The SessionID
4155
*/
42-
public function __construct(array $row)
56+
public function getSessionID(): ?\SessionID
4357
{
44-
$this->DBRow = $row;
58+
return null;
59+
}
60+
61+
/**
62+
* Return the User who created this row.
63+
*
64+
* @return \User The user who created this row
65+
*/
66+
public function createdBy() : \User
67+
{
68+
return $this->CreatedBy;
4569
}
4670

4771
/**
@@ -53,4 +77,6 @@ class DICOMArchiveRowWithoutSession implements \LORIS\Data\DataInstance
5377
{
5478
return $this->DBRow;
5579
}
80+
81+
5682
}

0 commit comments

Comments
 (0)