Skip to content
This repository has been archived by the owner on Apr 13, 2023. It is now read-only.

Commit

Permalink
fix: scope checking for getting status on an export (#64)
Browse files Browse the repository at this point in the history
  • Loading branch information
rsmayda authored Sep 10, 2021
1 parent cb91d43 commit 69f9297
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 61 deletions.
108 changes: 69 additions & 39 deletions src/smartScopeHelper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
import { BulkDataAuth } from 'fhir-works-on-aws-interface';
import { BulkDataAuth, ExportType } from 'fhir-works-on-aws-interface';
import { ScopeRule, ScopeType } from './smartConfig';
import {
isScopeSufficient,
Expand All @@ -27,7 +27,9 @@ const emptyScopeRule = (): ScopeRule => ({
},
});
const isScopeSufficientCases: ScopeType[][] = [['user'], ['patient'], ['system']];
describe.each(isScopeSufficientCases)('%s: isScopeSufficient', (scopeType: ScopeType) => {
const exportTypes: ExportType[][] = [['group'], ['system']];
const exportOperations: ('cancel-export' | 'get-status-export')[][] = [['cancel-export'], ['get-status-export']];
describe.each(isScopeSufficientCases)('ScopeType: %s: isScopeSufficient', (scopeType: ScopeType) => {
test('scope is sufficient to read Observation: Scope with resourceType "Observation" should be able to read "Observation" resources', () => {
const clonedScopeRule = emptyScopeRule();
clonedScopeRule[scopeType].read = ['read'];
Expand Down Expand Up @@ -62,43 +64,6 @@ describe.each(isScopeSufficientCases)('%s: isScopeSufficient', (scopeType: Scope
);
});

test('scope is sufficient for system bulk data access with "user" || "system" scopeType but not "patient" scopeType', () => {
const clonedScopeRule = emptyScopeRule();
clonedScopeRule[scopeType].read = ['read'];
const bulkDataAuth: BulkDataAuth = { operation: 'initiate-export', exportType: 'system' };

// Only scopeType of user has bulkDataAccess
expect(isScopeSufficient(`${scopeType}/*.read`, clonedScopeRule, 'read', undefined, bulkDataAuth)).toEqual(
scopeType !== 'patient',
);
});

test('scope is NOT sufficient for system bulk data access: Scope needs to have resourceType "*"', () => {
const clonedScopeRule = emptyScopeRule();
clonedScopeRule[scopeType].read = ['read'];

const bulkDataAuth: BulkDataAuth = { operation: 'initiate-export', exportType: 'system' };
expect(
isScopeSufficient(`${scopeType}/Observation.read`, clonedScopeRule, 'read', undefined, bulkDataAuth),
).toEqual(false);
});

test('scope is sufficient for group export with "system" scopeType, not "user" of "patient" scopeType', () => {
const clonedScopeRule = emptyScopeRule();
clonedScopeRule[scopeType].read = ['read'];
const bulkDataAuth: BulkDataAuth = { operation: 'initiate-export', exportType: 'group' };

// Only scopeType of system has bulkDataAccess
expect(isScopeSufficient(`${scopeType}/*.read`, clonedScopeRule, 'read', undefined, bulkDataAuth)).toEqual(
scopeType === 'system',
);

// Group export result is filtered on allowed resourceType, scope not having resourceType "*" should be passed
expect(
isScopeSufficient(`${scopeType}/Observation.read`, clonedScopeRule, 'read', undefined, bulkDataAuth),
).toEqual(scopeType === 'system');
});

test('scope is sufficient to do a search-system', () => {
const clonedScopeRule = emptyScopeRule();
clonedScopeRule[scopeType].read = ['search-system'];
Expand All @@ -124,6 +89,71 @@ describe.each(isScopeSufficientCases)('%s: isScopeSufficient', (scopeType: Scope

expect(isScopeSufficient(`fake`, clonedScopeRule, 'read')).toEqual(false);
});

describe('BulkDataAuth', () => {
test('scope is sufficient for `system` initiate-export with "user" || "system" scopeType but not "patient" scopeType', () => {
const clonedScopeRule = emptyScopeRule();
clonedScopeRule[scopeType].read = ['read'];
const bulkDataAuth: BulkDataAuth = { operation: 'initiate-export', exportType: 'system' };

// Only scopeType of user has bulkDataAccess
expect(isScopeSufficient(`${scopeType}/*.read`, clonedScopeRule, 'read', undefined, bulkDataAuth)).toEqual(
scopeType !== 'patient',
);
});

test('scope is NOT sufficient for `system` initiate-export: Scope needs to have resourceType "*"', () => {
const clonedScopeRule = emptyScopeRule();
clonedScopeRule[scopeType].read = ['read'];

const bulkDataAuth: BulkDataAuth = { operation: 'initiate-export', exportType: 'system' };
expect(
isScopeSufficient(`${scopeType}/Observation.read`, clonedScopeRule, 'read', undefined, bulkDataAuth),
).toEqual(false);
});

test('scope is sufficient for `group` initiate-export with "system" scopeType, not "user" of "patient" scopeType', () => {
const clonedScopeRule = emptyScopeRule();
clonedScopeRule[scopeType].read = ['read'];
const bulkDataAuth: BulkDataAuth = { operation: 'initiate-export', exportType: 'group' };

// Only scopeType of system has bulkDataAccess
expect(isScopeSufficient(`${scopeType}/*.read`, clonedScopeRule, 'read', undefined, bulkDataAuth)).toEqual(
scopeType === 'system',
);

// Group export result is filtered on allowed resourceType, scope not having resourceType "*" should be passed
expect(
isScopeSufficient(`${scopeType}/Observation.read`, clonedScopeRule, 'read', undefined, bulkDataAuth),
).toEqual(scopeType === 'system');
});
describe.each(exportTypes)('export type: %s', (exportType: ExportType) => {
describe.each(exportOperations)(
'export operation: %s',
(operation: 'cancel-export' | 'get-status-export') => {
test('scope is sufficient for non "patient" scopeType', () => {
const clonedScopeRule = emptyScopeRule();
clonedScopeRule[scopeType].read = ['read'];
const bulkDataAuth: BulkDataAuth = { operation, exportType };

expect(
isScopeSufficient(`${scopeType}/*.read`, clonedScopeRule, 'read', undefined, bulkDataAuth),
).toEqual(scopeType !== 'patient');

expect(
isScopeSufficient(
`${scopeType}/Observation.read`,
clonedScopeRule,
'read',
undefined,
bulkDataAuth,
),
).toEqual(scopeType !== 'patient');
});
},
);
});
});
});

describe('getScopes', () => {
Expand Down
38 changes: 16 additions & 22 deletions src/smartScopeHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,30 +84,24 @@ function isSmartScopeSufficientForBulkDataAccess(
smartScope: ClinicalSmartScope,
scopeRule: ScopeRule,
) {
let bulkDataRequestHasCorrectScope = false;
if (bulkDataAuth.exportType === 'system') {
bulkDataRequestHasCorrectScope =
['system', 'user'].includes(smartScope.scopeType) &&
smartScope.resourceType === '*' &&
['*', 'read'].includes(smartScope.accessType) &&
getValidOperationsForScopeTypeAndAccessType(
smartScope.scopeType,
smartScope.accessType,
scopeRule,
).includes('read');
} else if (bulkDataAuth.exportType === 'group') {
bulkDataRequestHasCorrectScope =
['system'].includes(smartScope.scopeType) &&
['*', 'read'].includes(smartScope.accessType) &&
getValidOperationsForScopeTypeAndAccessType(
smartScope.scopeType,
smartScope.accessType,
scopeRule,
).includes('read');
const { scopeType, accessType, resourceType } = smartScope;
const hasReadPermissions = getValidOperationsForScopeTypeAndAccessType(scopeType, accessType, scopeRule).includes(
'read',
);
if (bulkDataAuth.operation === 'initiate-export') {
let bulkDataRequestHasCorrectScope = false;
if (bulkDataAuth.exportType === 'system') {
bulkDataRequestHasCorrectScope =
['system', 'user'].includes(scopeType) && resourceType === '*' && hasReadPermissions;
} else if (bulkDataAuth.exportType === 'group') {
bulkDataRequestHasCorrectScope = ['system'].includes(scopeType) && hasReadPermissions;
}
return bulkDataRequestHasCorrectScope;
}
return (
['initiate-export', 'get-status-export', 'cancel-export'].includes(bulkDataAuth.operation) &&
bulkDataRequestHasCorrectScope
['get-status-export', 'cancel-export'].includes(bulkDataAuth.operation) &&
['system', 'user'].includes(scopeType) &&
hasReadPermissions
);
}

Expand Down

0 comments on commit 69f9297

Please sign in to comment.