From 99a622c1ebe7052661b5278f0de8986a31728868 Mon Sep 17 00:00:00 2001 From: Mohammad Jafari Date: Thu, 16 Jan 2025 12:45:17 -0800 Subject: [PATCH] remove labeling logic --- app.js | 4 - controllers/bundle-security-label.js | 19 --- controllers/sls.js | 15 -- lib/decision-processor.js | 6 +- lib/labeling/confidentiality-rules.json | 1 - lib/labeling/labeler.js | 130 ---------------- lib/labeling/sensitivity-rules.json | 1 - lib/labeling/sls-decision-hooks-response.js | 15 -- lib/validators.js | 23 +-- .../bundle-security-label-request.schema.json | 51 ------- schemas/sls-request.schema.json | 45 ------ .../controllers/bundle-security-label.test.js | 45 ------ test/controllers/sls.test.js | 58 ------- .../observations/observations-ketamine.json | 12 ++ test/lib/labeling.test.js | 142 ------------------ test/lib/redacter.test.js | 4 +- 16 files changed, 15 insertions(+), 556 deletions(-) delete mode 100644 controllers/bundle-security-label.js delete mode 100644 controllers/sls.js delete mode 100644 lib/labeling/confidentiality-rules.json delete mode 100644 lib/labeling/labeler.js delete mode 100644 lib/labeling/sensitivity-rules.json delete mode 100644 lib/labeling/sls-decision-hooks-response.js delete mode 100644 schemas/bundle-security-label-request.schema.json delete mode 100644 schemas/sls-request.schema.json delete mode 100644 test/controllers/bundle-security-label.test.js delete mode 100644 test/controllers/sls.test.js delete mode 100644 test/lib/labeling.test.js diff --git a/app.js b/app.js index a900f24..14b199f 100644 --- a/app.js +++ b/app.js @@ -7,8 +7,6 @@ const { error } = require("./controllers/error"); const { discovery } = require("./controllers/discovery"); const ConsentDecisionHook = require("./controllers/patient-consent-consult"); const Xacml = require("./controllers/xacml"); -const SLS = require("./controllers/sls"); -const SLSHook = require("./controllers/bundle-security-label"); const app = express(); @@ -26,8 +24,6 @@ app.get("/cds-services", discovery); app.post("/cds-services/patient-consent-consult", ConsentDecisionHook.post); app.post("/xacml", Xacml.post); -app.post("/sls", SLS.post); -app.post("/cds-services/bundle-security-label", SLSHook.post); app.use(error); diff --git a/controllers/bundle-security-label.js b/controllers/bundle-security-label.js deleted file mode 100644 index c98b5c3..0000000 --- a/controllers/bundle-security-label.js +++ /dev/null @@ -1,19 +0,0 @@ -const { validateSlsHookRequest } = require("../lib/validators"); -const { labelBundleDiff } = require("../lib/labeling/labeler"); -const { - resourcesToHookResponse -} = require("../lib/labeling/sls-decision-hooks-response"); - -async function post(req, res, next) { - try { - validateSlsHookRequest(req); - const bundle = req.body.context.bundle; - res.send(resourcesToHookResponse(labelBundleDiff(bundle))); - } catch (e) { - next(e); - } -} - -module.exports = { - post -}; diff --git a/controllers/sls.js b/controllers/sls.js deleted file mode 100644 index 8bdce42..0000000 --- a/controllers/sls.js +++ /dev/null @@ -1,15 +0,0 @@ -const { validateSlsRequest } = require("../lib/validators"); -const { label } = require("../lib/labeling/labeler"); - -async function post(req, res, next) { - try { - validateSlsRequest(req); - res.send(label(req.body)); - } catch (e) { - next(e); - } -} - -module.exports = { - post -}; diff --git a/lib/decision-processor.js b/lib/decision-processor.js index 2e1b08a..a56846c 100644 --- a/lib/decision-processor.js +++ b/lib/decision-processor.js @@ -1,15 +1,11 @@ const { CONSENT_PERMIT } = require("./consent-decisions"); const { maybeRedactBundle } = require("./redacter"); -const { label } = require("./labeling/labeler"); const maybeApplyDecision = (decisionEntry, content) => { return content && decisionEntry.decision === CONSENT_PERMIT ? { ...decisionEntry, - content: maybeRedactBundle( - decisionEntry.obligations, - label(content) - ) + content: maybeRedactBundle(decisionEntry.obligations, content) } : decisionEntry; }; diff --git a/lib/labeling/confidentiality-rules.json b/lib/labeling/confidentiality-rules.json deleted file mode 100644 index fe51488..0000000 --- a/lib/labeling/confidentiality-rules.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/lib/labeling/labeler.js b/lib/labeling/labeler.js deleted file mode 100644 index 62c6e2c..0000000 --- a/lib/labeling/labeler.js +++ /dev/null @@ -1,130 +0,0 @@ -const { JSONPath } = require("jsonpath-plus"); -const { removeRedundantCodes, codesShortHand } = require("../codes"); -const BASE_SENSITIVITY_RULES = require("./sensitivity-rules.json"); -const BASE_CONFIDENTIALITY_RULES = require("./confidentiality-rules.json"); - -const SENSITIVITY_RULES = [ - ...BASE_SENSITIVITY_RULES, - ...JSON.parse(process.env.SENSITIVITY_TAGGING_RULES || "[]") -]; - -const CONFIDENTIALITY_RULES = [ - ...BASE_CONFIDENTIALITY_RULES, - ...JSON.parse(process.env.CONFIDENTIALITY_TAGGING_RULES || "[]") -]; - -const labelBundle = (bundle) => ({ - ...bundle, - entry: bundle.entry.map((entry) => ({ - ...entry, - resource: sanitizeResource(labelResource(entry.resource)) - })) -}); - -/** - * remove the 'updated' attribute used to track which resources were update in the course of labeling. - */ -function sanitizeResource(resource) { - const { updated, ...rest } = resource; - return rest; -} - -const labelBundleDiff = (bundle) => - bundle.entry - .map(({ resource }) => labelResource(resource)) - .filter((resource) => resource.updated) - .map((resource) => sanitizeResource(resource)); - -const labelResource = (resource) => - labelResourceConfidentiality(labelResourceSensitivity(resource)); - -function labelResourceConfidentiality(resource) { - const sensitivityLabels = codesShortHand(resource.meta?.security || []); - const applicableRules = CONFIDENTIALITY_RULES.filter((rule) => - rule.codes.some((code) => sensitivityLabels.includes(code)) - ); - const labels = applicableRules - .map(({ labels, basis }) => - labels.map((label) => ({ - ...label, - ...(basis && { extension: basisExtension(basis) }) - })) - ) - .flat(); - return addUniqueLabelsToResource( - { ...resource, updated: applicableRules.length > 0 }, - labels - ); -} - -function addUniqueLabelsToResource(resource, labels) { - const existingLabels = resource.meta?.security || []; - const allLabels = removeRedundantCodes([...labels, ...existingLabels]); - return { - ...resource, - meta: { - ...(resource.meta || {}), - security: allLabels - } - }; -} - -function applicableSensitivityRules(resource) { - const clinicalCodes = JSONPath({ path: "$..coding", json: resource }).flat(); - const canonicalCodes = codesShortHand(clinicalCodes); - return SENSITIVITY_RULES.map((rule) => ({ - ...rule, - codeSets: rule.codeSets.filter(({ codes }) => - codes.some((code) => canonicalCodes.includes(code)) - ) - })).filter(({ codeSets }) => codeSets.length > 0); -} - -function labelResourceSensitivity(resource) { - const applicableRules = applicableSensitivityRules(resource); - const labels = applicableRules - .map(({ id, basis, labels, codeSets }) => - labels.map((label) => ({ - ...label, - ...{ - extension: [ - ...basisExtension(basis), - ...codeSets - .map(({ groupId }) => - basisExtension({ system: id, code: groupId }) - ) - .flat() - ] - } - })) - ) - .flat(); - return addUniqueLabelsToResource( - { ...resource, updated: applicableRules.length > 0 }, - labels - ); -} - -const SEC_LABEL_BASIS_URL = - "http://hl7.org/fhir/uv/security-label-ds4p/StructureDefinition/extension-sec-label-basis"; - -const basisExtension = (basisCoding) => - basisCoding - ? [ - { - url: SEC_LABEL_BASIS_URL, - valueCoding: basisCoding - } - ] - : []; - -const label = (object) => - object?.resourceType == "Bundle" - ? labelBundle(object) - : sanitizeResource(labelResource(object)); - -module.exports = { - labelBundle, - labelBundleDiff, - label -}; diff --git a/lib/labeling/sensitivity-rules.json b/lib/labeling/sensitivity-rules.json deleted file mode 100644 index fe51488..0000000 --- a/lib/labeling/sensitivity-rules.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/lib/labeling/sls-decision-hooks-response.js b/lib/labeling/sls-decision-hooks-response.js deleted file mode 100644 index 4ece2a0..0000000 --- a/lib/labeling/sls-decision-hooks-response.js +++ /dev/null @@ -1,15 +0,0 @@ -const resourcesToHookResponseUpdateAction = (resources) => - resources.map((resource) => ({ - type: "update", - description: "labeled resource", - resource: resource - })); - -const resourcesToHookResponse = (resources) => ({ - cards: [], - systemActions: resourcesToHookResponseUpdateAction(resources) -}); - -module.exports = { - resourcesToHookResponse -}; diff --git a/lib/validators.js b/lib/validators.js index 84ba723..d3940e3 100644 --- a/lib/validators.js +++ b/lib/validators.js @@ -10,12 +10,6 @@ const consentDecisionHookRequestValidator = ajv.compile( const xacmlRequestSchema = require("../schemas/xacml-request.schema.json"); const xacmlRequestValidator = ajv.compile(xacmlRequestSchema); -const slsRequestSchema = require("../schemas/sls-request.schema.json"); -const slsRequestValidator = ajv.compile(slsRequestSchema); - -const slsHookRequestSchema = require("../schemas/bundle-security-label-request.schema.json"); -const slsHookRequestValidator = ajv.compile(slsHookRequestSchema); - const validationException = (errors) => ({ httpCode: 400, error: "bad_request", @@ -36,19 +30,6 @@ function validateXacmlRequest(req) { } } -function validateSlsRequest(req) { - const body = req.body; - if (!slsRequestValidator(body)) { - throw validationException(slsRequestValidator.errors); - } -} - -function validateSlsHookRequest(req) { - const body = req.body; - if (!slsHookRequestValidator(body)) { - throw validationException(slsHookRequestValidator.errors); - } -} function prettifySchemaValidationErrors(givenErrors) { const errors = givenErrors || []; @@ -59,7 +40,5 @@ function prettifySchemaValidationErrors(givenErrors) { module.exports = { validateConsentDecisionHookRequest, - validateXacmlRequest, - validateSlsRequest, - validateSlsHookRequest + validateXacmlRequest }; diff --git a/schemas/bundle-security-label-request.schema.json b/schemas/bundle-security-label-request.schema.json deleted file mode 100644 index 748db53..0000000 --- a/schemas/bundle-security-label-request.schema.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://github.com/sdhealthconnect/leap-cds/schemas/bundle-security-label-request.schema.json", - "title": "SLS Hook Request", - "description": "SLS Hook Request", - "type": "object", - "properties": { - "hook": { - "type": "string", - "pattern": "bundle-security-label" - }, - "hookInstance": { - "description": "UUID for this hook call", - "type": "string" - }, - "context": { - "type": "object", - "properties": { - "bundle": { - "type": "object", - "properties": { - "entry": { - "type": "array", - "items": { - "type": "object", - "properties": { - "resource": { - "type": "object", - "properties": { - "resourceType": { - "type": "string" - } - }, - "required": ["resourceType"] - } - }, - "required": ["resource"] - } - }, - "resourceType": { - "type": "string", - "pattern": "Bundle" - } - }, - "required": ["entry", "resourceType"] - } - } - } - }, - "required": ["hook", "hookInstance", "context"] -} diff --git a/schemas/sls-request.schema.json b/schemas/sls-request.schema.json deleted file mode 100644 index d47c803..0000000 --- a/schemas/sls-request.schema.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://github.com/sdhealthconnect/leap-cds/schemas/sls-request.schema.json", - "title": "SLS Request", - "description": "SLS Request", - "anyOf": [ - { - "type": "object", - "properties": { - "entry": { - "type": "array", - "items": { - "type": "object", - "properties": { - "resource": { - "type": "object", - "properties": { - "resourceType": { - "type": "string" - } - }, - "required": ["resourceType"] - } - }, - "required": ["resource"] - } - }, - "resourceType": { - "type": "string", - "pattern": "Bundle" - } - }, - "required": ["entry", "resourceType"] - }, - { - "type": "object", - "properties": { - "resourceType": { - "type": "string" - } - }, - "required": ["resourceType"] - } - ] -} diff --git a/test/controllers/bundle-security-label.test.js b/test/controllers/bundle-security-label.test.js deleted file mode 100644 index f6ebd55..0000000 --- a/test/controllers/bundle-security-label.test.js +++ /dev/null @@ -1,45 +0,0 @@ -const _ = require("lodash"); -const request = require("supertest"); -const { app } = require("../../app"); - -const BUNDLE = require("../fixtures/empty-bundle.json"); -const OBSERVATION = require("../fixtures/observations/observations-ketamine.json"); -const NON_SENSITIVE_OBSERVATION = require("../fixtures/observations/observation-bacteria.json"); - -it("should return 200 and a labeled bundle", async () => { - const bundleOfObservations = _.cloneDeep(BUNDLE); - bundleOfObservations.entry = [ - { fullUrl: "1", resource: OBSERVATION }, - { fullUrl: "2", resource: NON_SENSITIVE_OBSERVATION } - ]; - bundleOfObservations.total = 2; - - const res = await request(app) - .post("/cds-services/bundle-security-label") - .set("Accept", "application/json") - .send({ - hookInstance: "...", - hook: "bundle-security-label", - context: { - bundle: bundleOfObservations - } - }); - - expect(res.status).toEqual(200); - - const systemActions = res.body.systemActions; - expect(systemActions.length).toBe(1); - expect(systemActions[0].type).toBe("update"); - expect(systemActions[0].resource.meta?.security).toMatchObject( - expect.arrayContaining([ - expect.objectContaining({ - system: "http://terminology.hl7.org/CodeSystem/v3-ActCode", - code: "SUD" - }), - expect.objectContaining({ - system: "http://terminology.hl7.org/CodeSystem/v3-Confidentiality", - code: "R" - }) - ]) - ); -}); diff --git a/test/controllers/sls.test.js b/test/controllers/sls.test.js deleted file mode 100644 index 7a57afd..0000000 --- a/test/controllers/sls.test.js +++ /dev/null @@ -1,58 +0,0 @@ -const _ = require("lodash"); -const request = require("supertest"); -const { app } = require("../../app"); - -const BUNDLE = require("../fixtures/empty-bundle.json"); -const OBSERVATION = require("../fixtures/observations/observations-ketamine.json"); -const NON_SENSITIVE_OBSERVATION = require("../fixtures/observations/observation-bacteria.json"); - -it("should return 200 and a labeled bundle", async () => { - const bundleOfObservations = _.cloneDeep(BUNDLE); - bundleOfObservations.entry = [ - { fullUrl: "1", resource: OBSERVATION }, - { fullUrl: "2", resource: NON_SENSITIVE_OBSERVATION } - ]; - bundleOfObservations.total = 2; - - const res = await request(app) - .post("/sls") - .set("Accept", "application/json") - .send(bundleOfObservations); - - expect(res.status).toEqual(200); - - expect(res.body.entry[0].resource.meta?.security).toMatchObject( - expect.arrayContaining([ - expect.objectContaining({ - system: "http://terminology.hl7.org/CodeSystem/v3-ActCode", - code: "SUD" - }), - expect.objectContaining({ - system: "http://terminology.hl7.org/CodeSystem/v3-Confidentiality", - code: "R" - }) - ]) - ); -}); - -it("should return 200 and a labeled resource", async () => { - const res = await request(app) - .post("/sls") - .set("Accept", "application/json") - .send(OBSERVATION); - - expect(res.status).toEqual(200); - - expect(res.body?.meta?.security).toMatchObject( - expect.arrayContaining([ - expect.objectContaining({ - system: "http://terminology.hl7.org/CodeSystem/v3-ActCode", - code: "SUD" - }), - expect.objectContaining({ - system: "http://terminology.hl7.org/CodeSystem/v3-Confidentiality", - code: "R" - }) - ]) - ); -}); diff --git a/test/fixtures/observations/observations-ketamine.json b/test/fixtures/observations/observations-ketamine.json index 0ccc15b..131bee7 100644 --- a/test/fixtures/observations/observations-ketamine.json +++ b/test/fixtures/observations/observations-ketamine.json @@ -2,6 +2,18 @@ "resourceType": "Observation", "id": "f204", "status": "final", + "meta": { + "security": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v3-ActCode", + "code": "SUD" + }, + { + "system": "http://terminology.hl7.org/CodeSystem/v3-Confidentiality", + "code": "R" + } + ] + }, "code": { "coding": [ { diff --git a/test/lib/labeling.test.js b/test/lib/labeling.test.js deleted file mode 100644 index 358f74a..0000000 --- a/test/lib/labeling.test.js +++ /dev/null @@ -1,142 +0,0 @@ -const _ = require("lodash"); -const { label } = require("../../lib/labeling/labeler"); - -const OBSERVATION = require("../fixtures/observations/observations-ketamine.json"); -const NON_SENSITIVE_OBSERVATION = require("../fixtures/observations/observation-bacteria.json"); - -const BUNDLE = require("../fixtures/empty-bundle.json"); - -it("correctly labels an unlabeled resource", async () => { - const labeledObservation = label(OBSERVATION); - expect(labeledObservation.meta?.security).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - extension: [ - { - url: "http://hl7.org/fhir/uv/security-label-ds4p/StructureDefinition/extension-sec-label-basis", - valueCoding: { - system: "http://terminology.hl7.org/CodeSystem/v3-ActCode", - code: "42CFRPart2", - display: "42 CFR Part2" - } - }, - { - url: "http://hl7.org/fhir/uv/security-label-ds4p/StructureDefinition/extension-sec-label-basis", - valueCoding: { code: "ketamine", system: "sample-rule-1" } - } - ], - system: "http://terminology.hl7.org/CodeSystem/v3-ActCode", - code: "SUD", - display: "substance use disorder information sensitivity" - }), - expect.objectContaining({ - extension: [ - { - url: "http://hl7.org/fhir/uv/security-label-ds4p/StructureDefinition/extension-sec-label-basis", - valueCoding: { - system: "http://terminology.hl7.org/CodeSystem/v3-ActCode", - code: "42CFRPart2", - display: "42 CFR Part2" - } - } - ], - system: "http://terminology.hl7.org/CodeSystem/v3-Confidentiality", - code: "R", - display: "restricted" - }) - ]) - ); -}); - -it("correctly refrains from labeling a non-sensitiveresource", async () => { - const labeledObservation = label(NON_SENSITIVE_OBSERVATION); - expect(labeledObservation.meta?.security).toEqual([]); -}); - -it("does not add redundant labels to a resource with existing labels", async () => { - const alreadyLabeledObservation = _.cloneDeep(OBSERVATION); - alreadyLabeledObservation.meta = { - security: [ - { - code: "SUD", - system: "http://terminology.hl7.org/CodeSystem/v3-ActCode" - } - ] - }; - - const labeledObservation = label(alreadyLabeledObservation); - expect(labeledObservation.meta?.security).toHaveLength(2); - expect(labeledObservation.meta?.security).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - system: "http://terminology.hl7.org/CodeSystem/v3-ActCode", - code: "SUD" - }), - expect.objectContaining({ - system: "http://terminology.hl7.org/CodeSystem/v3-Confidentiality", - code: "R" - }) - ]) - ); -}); - -it("correctly adds labels to a resource with existing labels", async () => { - const alreadyLabeledObservation = _.cloneDeep(OBSERVATION); - alreadyLabeledObservation.meta = { - security: [ - { - code: "HRELIABLE", - system: "http://terminology.hl7.org/CodeSystem/v3-ObservationValue" - } - ] - }; - - const labeledObservation = label(alreadyLabeledObservation); - expect(labeledObservation.meta?.security).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - system: "http://terminology.hl7.org/CodeSystem/v3-ActCode", - code: "SUD" - }), - expect.objectContaining({ - system: "http://terminology.hl7.org/CodeSystem/v3-ObservationValue", - code: "HRELIABLE" - }) - ]) - ); -}); - -it("correctly labels a bundle of resource", async () => { - const bundleOfObservations = _.cloneDeep(BUNDLE); - bundleOfObservations.entry = [ - { fullUrl: "1", resource: OBSERVATION }, - { fullUrl: "2", resource: OBSERVATION } - ]; - bundleOfObservations.total = 2; - - const labeledBundle = label(bundleOfObservations); - expect(labeledBundle.entry[0].resource.meta?.security).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - system: "http://terminology.hl7.org/CodeSystem/v3-ActCode", - code: "SUD" - }), - expect.objectContaining({ - system: "http://terminology.hl7.org/CodeSystem/v3-Confidentiality", - code: "R" - }) - ]) - ); - expect(labeledBundle.entry[1].resource.meta?.security).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - system: "http://terminology.hl7.org/CodeSystem/v3-ActCode", - code: "SUD" - }), - expect.objectContaining({ - system: "http://terminology.hl7.org/CodeSystem/v3-Confidentiality", - code: "R" - }) - ]) - ); -}); diff --git a/test/lib/redacter.test.js b/test/lib/redacter.test.js index 3b5195f..0a1dff9 100644 --- a/test/lib/redacter.test.js +++ b/test/lib/redacter.test.js @@ -1,5 +1,4 @@ const _ = require("lodash"); -const { label } = require("../../lib/labeling/labeler"); const { maybeRedactBundle } = require("../../lib/redacter"); const RESTRICTED_OBSERVATION = require("../fixtures/observations/observations-ketamine.json"); @@ -30,8 +29,7 @@ it("correctly redacts a bundle of resources", async () => { ]; bundleOfObservations.total = 2; - const labeledBundle = label(bundleOfObservations); - const modifiedBundle = maybeRedactBundle(OBLIGATIONS, labeledBundle); + const modifiedBundle = maybeRedactBundle(OBLIGATIONS, bundleOfObservations); expect(modifiedBundle.total).toBe(1); expect(modifiedBundle.entry).toHaveLength(1);