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

Commit

Permalink
feat: Implement interface for fhir-works-on-aws-interface v2.0.0 (#9)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: fhir-works-on-aws-interface v2.0.0 changes the return type of isAuthorized
  • Loading branch information
carvantes committed Sep 25, 2020
1 parent 5d08b06 commit de02cdc
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 21 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

All notable changes to this project will be documented in this file.

## [2.0.0] - 2020-09-25

- Implement `fhir-works-on-aws-interface` v2.0.0

## [1.0.0] - 2020-08-31

### Added
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "fhir-works-on-aws-authz-rbac",
"version": "1.0.0",
"version": "2.0.0",
"description": "FHIR Works on AWS role base access control",
"main": "lib/index.js",
"types": "lib/index.d.ts",
Expand Down Expand Up @@ -28,7 +28,7 @@
"prepublish": "tsc"
},
"dependencies": {
"fhir-works-on-aws-interface": "^1.0.0",
"fhir-works-on-aws-interface": "^2.0.0",
"jsonwebtoken": "^8.5.1"
},
"devDependencies": {
Expand Down
70 changes: 56 additions & 14 deletions src/RBACHandler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ describe('isAuthorized', () => {
const authZHandler: RBACHandler = new RBACHandler(RBACRules);

test('TRUE; read direct patient; practitioner', async () => {
const results: boolean = authZHandler.isAuthorized({
const results: boolean = await authZHandler.isAuthorized({
accessToken: practitionerAccessToken,
resourceType: 'Patient',
operation: 'read',
Expand All @@ -62,22 +62,22 @@ describe('isAuthorized', () => {
expect(results).toEqual(true);
});
test('TRUE; create direct patient; practitioner', async () => {
const results: boolean = authZHandler.isAuthorized({
const results: boolean = await authZHandler.isAuthorized({
accessToken: practitionerAccessToken,
resourceType: 'Patient',
operation: 'create',
});
expect(results).toEqual(true);
});
test('TRUE; transaction; practitioner', async () => {
const results: boolean = authZHandler.isAuthorized({
const results: boolean = await authZHandler.isAuthorized({
accessToken: practitionerAccessToken,
operation: 'transaction',
});
expect(results).toEqual(true);
});
test('TRUE; update direct patient; practitioner', async () => {
const results: boolean = authZHandler.isAuthorized({
const results: boolean = await authZHandler.isAuthorized({
accessToken: practitionerAccessToken,
resourceType: 'Patient',
operation: 'update',
Expand All @@ -86,7 +86,7 @@ describe('isAuthorized', () => {
expect(results).toEqual(true);
});
test('TRUE; DELETE patient; practitioner', async () => {
const results: boolean = authZHandler.isAuthorized({
const results: boolean = await authZHandler.isAuthorized({
accessToken: practitionerAccessToken,
resourceType: 'Patient',
operation: 'delete',
Expand All @@ -96,7 +96,7 @@ describe('isAuthorized', () => {
});

test('FASLE; patch patient; practitioner', async () => {
const results: boolean = authZHandler.isAuthorized({
const results: boolean = await authZHandler.isAuthorized({
accessToken: practitionerAccessToken,
resourceType: 'Patient',
operation: 'patch',
Expand All @@ -105,15 +105,15 @@ describe('isAuthorized', () => {
expect(results).toEqual(false);
});
test('TRUE; GET capability statement; no groups', async () => {
const results: boolean = authZHandler.isAuthorized({
const results: boolean = await authZHandler.isAuthorized({
accessToken: 'notReal',
operation: 'read',
resourceType: 'metadata',
});
expect(results).toEqual(true);
});
test('FALSE; GET Patient; no groups', async () => {
const results: boolean = authZHandler.isAuthorized({
const results: boolean = await authZHandler.isAuthorized({
accessToken: noGroupsAccessToken,
resourceType: 'Patient',
operation: 'read',
Expand All @@ -122,15 +122,15 @@ describe('isAuthorized', () => {
expect(results).toEqual(false);
});
test('FALSE; POST Patient; non-practitioner/auditor', async () => {
const results: boolean = authZHandler.isAuthorized({
const results: boolean = await authZHandler.isAuthorized({
accessToken: nonPractAndAuditorAccessToken,
resourceType: 'Patient',
operation: 'create',
});
expect(results).toEqual(false);
});
test('TRUE; GET Patient; non-practitioner/auditor', async () => {
const results: boolean = authZHandler.isAuthorized({
const results: boolean = await authZHandler.isAuthorized({
accessToken: nonPractAndAuditorAccessToken,
resourceType: 'Patient',
operation: 'read',
Expand All @@ -139,22 +139,22 @@ describe('isAuthorized', () => {
expect(results).toEqual(true);
});
test('TRUE; Patient Search; non-practitioner/auditor', async () => {
const results: boolean = authZHandler.isAuthorized({
const results: boolean = await authZHandler.isAuthorized({
accessToken: nonPractAndAuditorAccessToken,
resourceType: 'Patient',
operation: 'search-type',
});
expect(results).toEqual(true);
});
test('FALSE; Global Search; non-practitioner/auditor', async () => {
const results: boolean = authZHandler.isAuthorized({
const results: boolean = await authZHandler.isAuthorized({
accessToken: nonPractAndAuditorAccessToken,
operation: 'search-system',
});
expect(results).toEqual(false);
});
test('TRUE; GET specific Patient history; non-practitioner/auditor', async () => {
const results: boolean = authZHandler.isAuthorized({
const results: boolean = await authZHandler.isAuthorized({
accessToken: nonPractAndAuditorAccessToken,
resourceType: 'Patient',
operation: 'vread',
Expand All @@ -164,7 +164,7 @@ describe('isAuthorized', () => {
expect(results).toEqual(true);
});
test('FALSE; GET Patient history; non-practitioner/auditor', async () => {
const results: boolean = authZHandler.isAuthorized({
const results: boolean = await authZHandler.isAuthorized({
accessToken: nonPractAndAuditorAccessToken,
resourceType: 'Patient',
operation: 'history-type',
Expand Down Expand Up @@ -226,3 +226,45 @@ describe('isBundleRequestAuthorized', () => {
expect(results).toEqual(false);
});
});

describe('getAllowedResourceTypesForOperation', () => {
test('Single group', async () => {
const authZHandler: RBACHandler = new RBACHandler(RBACRules);
await expect(
authZHandler.getAllowedResourceTypesForOperation({
accessToken: practitionerAccessToken,
operation: 'search-type',
}),
).resolves.toEqual([...financialResources, 'Patient']);
});

test('No groups', async () => {
const authZHandler: RBACHandler = new RBACHandler(RBACRules);
await expect(
authZHandler.getAllowedResourceTypesForOperation({
accessToken: noGroupsAccessToken,
operation: 'search-type',
}),
).resolves.toEqual([]);
});

test('Multiple groups', async () => {
const authZHandler: RBACHandler = new RBACHandler(RBACRules);
await expect(
authZHandler.getAllowedResourceTypesForOperation({
accessToken: nonPractAndAuditorAccessToken,
operation: 'search-type',
}),
).resolves.toEqual([...financialResources, 'Patient']);
});

test('operation not allowed', async () => {
const authZHandler: RBACHandler = new RBACHandler(RBACRules);
await expect(
authZHandler.getAllowedResourceTypesForOperation({
accessToken: nonPractAndAuditorAccessToken,
operation: 'history-instance',
}),
).resolves.toEqual([]);
});
});
17 changes: 16 additions & 1 deletion src/RBACHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
TypeOperation,
SystemOperation,
BatchReadWriteRequest,
AllowedResourceTypesForOperationRequest,
} from 'fhir-works-on-aws-interface';
import { Rule, RBACConfig } from './RBACConfig';

Expand All @@ -26,7 +27,7 @@ export class RBACHandler implements Authorization {
}
}

isAuthorized(request: AuthorizationRequest): boolean {
async isAuthorized(request: AuthorizationRequest): Promise<boolean> {
const decoded = decode(request.accessToken, { json: true }) || {};
const groups: string[] = decoded['cognito:groups'] || [];

Expand All @@ -45,6 +46,20 @@ export class RBACHandler implements Authorization {
return authZResponses.every(Boolean);
}

async getAllowedResourceTypesForOperation(request: AllowedResourceTypesForOperationRequest): Promise<string[]> {
const { accessToken, operation } = request;
const decoded = decode(accessToken, { json: true }) || {};
const groups: string[] = decoded['cognito:groups'] || [];

return groups.flatMap(group => {
const groupRule = this.rules.groupRules[group];
if (groupRule !== undefined && groupRule.operations.includes(operation)) {
return groupRule.resources;
}
return [];
});
}

private isAllowed(groups: string[], operation: TypeOperation | SystemOperation, resourceType?: string): boolean {
if (operation === 'read' && resourceType === 'metadata') {
return true; // capabilities statement
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1709,10 +1709,10 @@ fb-watchman@^2.0.0:
dependencies:
bser "2.1.1"

fhir-works-on-aws-interface@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fhir-works-on-aws-interface/-/fhir-works-on-aws-interface-1.0.0.tgz#7de73929ebab013745582518f7596607c85a4ad0"
integrity sha512-b+B7D4/Msfzku/efM7dhbqCANEjCEKqUm7YJwEsQVjfmNbyRHWNmk+Mer89POmPnbw0eqryjnvV7C698C/8+rQ==
fhir-works-on-aws-interface@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/fhir-works-on-aws-interface/-/fhir-works-on-aws-interface-2.0.0.tgz#98c04208c32653f9d75743663c036f1d6eae6ffb"
integrity sha512-hdjZJaNEoSYXYVEDOtCQW/HxnQhnF2Nu02nzgeNEWdsZN8MiMakmLX/JxnPYmG13/L9J69CpeV1PmZCsT9HG3g==

figures@^3.0.0:
version "3.2.0"
Expand Down

0 comments on commit de02cdc

Please sign in to comment.