From 364f514ac05eda0a415c001bd1df9d92c62ca4c1 Mon Sep 17 00:00:00 2001 From: Julia Afeltra Date: Thu, 18 Mar 2021 11:19:14 -0400 Subject: [PATCH 1/3] Support providing tlsRejectUnauthorized in notificationInfo config --- README.md | 1 + config/csv.config.example.json | 3 ++- src/cli/emailNotifications.js | 1 + test/cli/emailNotifications.test.js | 3 ++- 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 117c0828..f0903b57 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,7 @@ In order to send an email, users must specify the hostname or IP address of an S - `port`: (Optional) The port to connect to (defaults to 587) - `to`: Comma separated list or an array of recipients email addresses that will appear on the _To:_ field - `from`: (Optional) The email address of the sender. All email addresses can be plain `'sender@server.com'` or formatted `'"Sender Name" sender@server.com'` (defaults to mcode-extraction-errors@mitre.org, which cannot receive reply emails) +- `tlsRejectUnauthorized`: (Optional) A boolean value to set the [node.js TLSSocket option](https://nodejs.org/api/tls.html#tls_class_tls_tlssocket) for rejecting any unauthorized connections, `tls.rejectUnauthorized`. (defaults to `true`) An example of this object can be found in [`config/csv.config.example.json`](config/csv.config.example.json). diff --git a/config/csv.config.example.json b/config/csv.config.example.json index a29fa771..9c936e9f 100644 --- a/config/csv.config.example.json +++ b/config/csv.config.example.json @@ -8,7 +8,8 @@ "to": [ "demo@example.com", "test@example.com" - ] + ], + "tlsRejectUnauthorized": true }, "extractors": [ { diff --git a/src/cli/emailNotifications.js b/src/cli/emailNotifications.js index eed8c112..e98487e8 100644 --- a/src/cli/emailNotifications.js +++ b/src/cli/emailNotifications.js @@ -60,6 +60,7 @@ async function sendEmailNotification(notificationInfo, errors, debug = false) { const transporter = nodemailer.createTransport({ host: notificationInfo.host, ...(notificationInfo.port && { port: notificationInfo.port }), + ...(notificationInfo.tlsRejectUnauthorized !== undefined && { tls: { rejectUnauthorized: notificationInfo.tlsRejectUnauthorized } }), }); logger.debug('Sending email with error information'); diff --git a/test/cli/emailNotifications.test.js b/test/cli/emailNotifications.test.js index cc556220..369eb919 100644 --- a/test/cli/emailNotifications.test.js +++ b/test/cli/emailNotifications.test.js @@ -71,6 +71,7 @@ describe('sendEmailNotification', () => { port: 123, to: ['something@example.com', 'someone@example.com'], from: 'other@example.com', + tlsRejectUnauthorized: false, }; const errors = { 0: [], @@ -79,7 +80,7 @@ describe('sendEmailNotification', () => { }; await expect(sendEmailNotification(notificationInfo, errors, false)).resolves.not.toThrow(); - expect(createTransportSpy).toBeCalledWith({ host: notificationInfo.host, port: notificationInfo.port }); + expect(createTransportSpy).toBeCalledWith({ host: notificationInfo.host, port: notificationInfo.port, tls: { rejectUnauthorized: false } }); expect(sendMailMock).toBeCalled(); const sendMailMockArgs = sendMailMock.mock.calls[0][0]; expect(sendMailMockArgs.to).toEqual(notificationInfo.to); From 56ea39e91e96fe23503dec91b17d6ad151206521 Mon Sep 17 00:00:00 2001 From: Julia Afeltra Date: Thu, 18 Mar 2021 17:18:58 -0400 Subject: [PATCH 2/3] Only add tlsRejectUnauthorized if it is a boolean. Add tests to check various config options for tls. --- src/cli/emailNotifications.js | 7 ++++- test/cli/emailNotifications.test.js | 44 ++++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/cli/emailNotifications.js b/src/cli/emailNotifications.js index e98487e8..edb5dc41 100644 --- a/src/cli/emailNotifications.js +++ b/src/cli/emailNotifications.js @@ -57,10 +57,15 @@ async function sendEmailNotification(notificationInfo, errors, debug = false) { emailBody += 'The stack trace information can be seen in the terminal as well as in the notification email.'; } + // Ensure that the tlsRejectUnauthorized property is a boolean + if (notificationInfo.tlsRejectUnauthorized && (notificationInfo.tlsRejectUnauthorized !== true || notificationInfo.tlsRejectUnauthorized !== false)) { + logger.warn('The notificationInfo.tlsRejectUnauthorized should be a boolean value. The value provided will not be used.'); + } + const transporter = nodemailer.createTransport({ host: notificationInfo.host, ...(notificationInfo.port && { port: notificationInfo.port }), - ...(notificationInfo.tlsRejectUnauthorized !== undefined && { tls: { rejectUnauthorized: notificationInfo.tlsRejectUnauthorized } }), + ...((notificationInfo.tlsRejectUnauthorized === true || notificationInfo.tlsRejectUnauthorized === false) && { tls: { rejectUnauthorized: notificationInfo.tlsRejectUnauthorized } }), }); logger.debug('Sending email with error information'); diff --git a/test/cli/emailNotifications.test.js b/test/cli/emailNotifications.test.js index 369eb919..e201ce82 100644 --- a/test/cli/emailNotifications.test.js +++ b/test/cli/emailNotifications.test.js @@ -80,7 +80,7 @@ describe('sendEmailNotification', () => { }; await expect(sendEmailNotification(notificationInfo, errors, false)).resolves.not.toThrow(); - expect(createTransportSpy).toBeCalledWith({ host: notificationInfo.host, port: notificationInfo.port, tls: { rejectUnauthorized: false } }); + expect(createTransportSpy).toBeCalledWith({ host: notificationInfo.host, port: notificationInfo.port, tls: { rejectUnauthorized: notificationInfo.tlsRejectUnauthorized } }); expect(sendMailMock).toBeCalled(); const sendMailMockArgs = sendMailMock.mock.calls[0][0]; expect(sendMailMockArgs.to).toEqual(notificationInfo.to); @@ -94,6 +94,48 @@ describe('sendEmailNotification', () => { expect(sendMailMockArgs.text).not.toMatch(/Error at line 4`/i); }); + it('should send an email with tlsRejectUnauthorized set to true, false, and not set', async () => { + const notificationInfoTLSFalse = { + host: 'my.host.com', + to: ['something@example.com', 'someone@example.com'], + tlsRejectUnauthorized: false, + }; + const notificationInfoTLSTrue = { + host: 'my.host.com', + to: ['something@example.com', 'someone@example.com'], + tlsRejectUnauthorized: true, + }; + const notificationInfoNoTLS = { + host: 'my.host.com', + to: ['something@example.com', 'someone@example.com'], + }; + const notificationInfoUnexpectedTLS = { + host: 'my.host.com', + to: ['something@example.com', 'someone@example.com'], + tlsRejectUnauthorized: 'true', // Any value that is not true or false will log a warning and not be used + }; + const errors = { + 0: [], + 1: [{ message: 'something bad', stack: 'Error at line 1' }], + }; + createTransportSpy.mockReturnValueOnce({ sendMail: sendMailMock }); + await expect(sendEmailNotification(notificationInfoTLSFalse, errors, false)).resolves.not.toThrow(); + expect(createTransportSpy).toBeCalledWith({ host: notificationInfoTLSFalse.host, port: notificationInfoTLSFalse.port, tls: { rejectUnauthorized: false } }); + + createTransportSpy.mockReturnValueOnce({ sendMail: sendMailMock }); + await expect(sendEmailNotification(notificationInfoTLSTrue, errors, false)).resolves.not.toThrow(); + expect(createTransportSpy).toBeCalledWith({ host: notificationInfoTLSTrue.host, port: notificationInfoTLSTrue.port, tls: { rejectUnauthorized: true } }); + + createTransportSpy.mockReturnValueOnce({ sendMail: sendMailMock }); + await expect(sendEmailNotification(notificationInfoNoTLS, errors, false)).resolves.not.toThrow(); + expect(createTransportSpy).toBeCalledWith({ host: notificationInfoNoTLS.host, port: notificationInfoNoTLS.port }); // No tls object set + + createTransportSpy.mockReturnValueOnce({ sendMail: sendMailMock }); + await expect(sendEmailNotification(notificationInfoUnexpectedTLS, errors, false)).resolves.not.toThrow(); + // A warning will be logged and the unexpected value will not be used + expect(createTransportSpy).toBeCalledWith({ host: notificationInfoUnexpectedTLS.host, port: notificationInfoUnexpectedTLS.port }); + }); + it('should send an email with stack traces if debug flag was used', async () => { createTransportSpy.mockReturnValueOnce({ sendMail: sendMailMock }); const notificationInfo = { From a5b43f51ff16b5a57f2e607a5692e5fa0d908548 Mon Sep 17 00:00:00 2001 From: Julia Afeltra Date: Fri, 19 Mar 2021 09:53:50 -0400 Subject: [PATCH 3/3] Add README section about masked properties --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index f0903b57..bdaa1853 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,23 @@ Users can specify a different location for the file by using the `--run-log-file node src/cli/cli.js --run-log-filepath path/to/file.json ``` +### Masking Patient Data + +Currently, patient data can be masked within the extracted `Patient` resource. When masked, the value of the field will be replaced with a [Data Absent Reason extension](https://www.hl7.org/fhir/extension-data-absent-reason.html) with the code `masked`. +Patient properties that can be masked are: `gender`, `mrn`, `name`, `address`, `birthDate`, `language`, `ethnicity`, `birthsex`, and `race`. +To mask a property, provide an array of the properties to mask in the `constructorArgs` of the Patient extractor. For example, the following configuration can be used to mask `address` and `birthDate`: + +```bash +{ + "label": "patient", + "type": "CSVPatientExtractor", + "constructorArgs": { + "filePath": "./data/patient-information.csv" + "mask": ["address", "birthDate"] + } +} +``` + ### Extraction Date Range The mCODE Extraction Client will extract all data that is provided in the CSV files by default, regardless of any dates associated with each row of data. It is recommended that any required date filtering is performed outside of the scope of this client.