diff --git a/api-docs/openapi.json b/api-docs/openapi.json index 99dd14389..6652bd137 100644 --- a/api-docs/openapi.json +++ b/api-docs/openapi.json @@ -1,7 +1,7 @@ { "openapi": "3.0.2", "info": { - "version": "2.2.0", + "version": "2.2.1", "title": "CVE Services API", "description": "The CVE Services API supports automation tooling for the CVE Program. Credentials are required for most service endpoints. Representatives of CVE Numbering Authorities (CNAs) should use one of the methods below to obtain credentials:

CVE data is to be in the JSON 5.0 CVE Record format. Details of the JSON 5.0 schema are located here.

Contact the CVE Services team", "contact": { diff --git a/package-lock.json b/package-lock.json index b07c98a0a..aa417ced6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cve-services", - "version": "2.2.0", + "version": "2.2.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cve-services", - "version": "2.2.0", + "version": "2.2.1", "license": "(CC0)", "dependencies": { "ajv": "^8.6.2", @@ -18194,4 +18194,4 @@ "dev": true } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index a44d1aa33..16b32dfc0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cve-services", "author": "Automation Working Group", - "version": "2.2.0", + "version": "2.2.1", "license": "(CC0)", "devDependencies": { "@faker-js/faker": "^7.6.0", @@ -100,4 +100,4 @@ "test:coverage-html": "NODE_ENV=test nyc --reporter=html mocha src/* --recursive --exit || true", "test:scripts": "NODE_ENV=development node-dev src/scripts/templateScript.js" } -} +} \ No newline at end of file diff --git a/src/controller/cve-id.controller/cve-id.controller.js b/src/controller/cve-id.controller/cve-id.controller.js index 4d13a95e9..4dd2c0eb5 100644 --- a/src/controller/cve-id.controller/cve-id.controller.js +++ b/src/controller/cve-id.controller/cve-id.controller.js @@ -42,11 +42,14 @@ async function getFilteredCveId (req, res, next) { const users = await userRepo.getAllUsers() const orgMap = {} + const userMap = {} + orgs.forEach(org => { orgMap[org.UUID] = { shortname: org.short_name, users: {} } }) users.forEach(user => { + userMap[user.UUID] = user.username if (!orgMap[user.org_UUID]) { orgMap[user.org_UUID] = { shortname: `MISSING ORG ${user.org_UUID}`, users: {} } } @@ -122,7 +125,31 @@ async function getFilteredCveId (req, res, next) { cve_ids: pg.itemsList.map((i) => { const cnaid = i.requested_by.cna i.requested_by.cna = orgMap[cnaid].shortname - i.requested_by.user = orgMap[cnaid].users[i.requested_by.user] + + // User value is redacted in certain cases + // Checks if requested_by.user is in requested_by.cna org + if (!orgMap[cnaid].users[i.requested_by.user]) { + // Never redact for secretariat users + if (isSecretariat) { + i.requested_by.user = userMap[i.requested_by.user] + } else { + // Redact because requested_by.user is not in requested_by.cna org + i.requested_by.user = 'REDACTED' + } + // Check is current owning_cna is also requested_by.cna (if a CVE-ID changes orgs) + } else if (cnaid !== i.owning_cna) { + // Never redact for secretariat + if (isSecretariat) { + i.requested_by.user = userMap[i.requested_by.user] + } else { + // Redact because current owner is not requested_by.cna and shouldn't see requested_by.user + i.requested_by.user = 'REDACTED' + } + } else { + // No redaction, original requested_by.user is in requested_by.cna and owning_cna + i.requested_by.user = orgMap[cnaid].users[i.requested_by.user] + } + i.owning_cna = orgMap[i.owning_cna].shortname return i }) diff --git a/src/swagger.js b/src/swagger.js index 938e91fbd..b7df5d4c5 100644 --- a/src/swagger.js +++ b/src/swagger.js @@ -14,7 +14,7 @@ const rejectedCreateCVERecord = require('../schemas/cve/rejected-create-cve-exam /* eslint-disable no-multi-str */ const doc = { info: { - version: '2.2.0', + version: '2.2.1', title: 'CVE Services API', description: "The CVE Services API supports automation tooling for the CVE Program. Credentials are \ required for most service endpoints. Representatives of \ diff --git a/test/integration-tests/constants.js b/test/integration-tests/constants.js index 6aab5359b..a869d8cb0 100644 --- a/test/integration-tests/constants.js +++ b/test/integration-tests/constants.js @@ -11,6 +11,18 @@ const nonSecretariatUserHeaders = { 'CVE-API-USER': 'jasminesmith@win_5.com' } +const nonSecretariatUserHeaders2 = { + 'CVE-API-ORG': 'win_5', + 'CVE-API-Key': 'TCF25YM-39C4H6D-KA32EGF-V5XSHN3', + 'CVE-API-USER': 'win_5_admin@win_5.com' +} + +const nonSecretariatUserHeaders3 = { + 'CVE-API-ORG': 'evidence_15', + 'CVE-API-Key': 'TCF25YM-39C4H6D-KA32EGF-V5XSHN3', + 'CVE-API-USER': 'timothymyers@evidence_15.com' +} + const nonSecretariatUserHeadersWithAdp2 = { 'CVE-API-ORG': 'range_4', 'CVE-API-Key': 'TCF25YM-39C4H6D-KA32EGF-V5XSHN3', @@ -272,6 +284,8 @@ const existingOrg = { module.exports = { headers, nonSecretariatUserHeaders, + nonSecretariatUserHeaders2, + nonSecretariatUserHeaders3, badNonSecretariatUserHeaders, nonSecretariatUserHeadersWithAdp2, testCve, diff --git a/test/integration-tests/cve-id/getCveIdTest.js b/test/integration-tests/cve-id/getCveIdTest.js index a86854634..3209b822e 100644 --- a/test/integration-tests/cve-id/getCveIdTest.js +++ b/test/integration-tests/cve-id/getCveIdTest.js @@ -7,6 +7,7 @@ const _ = require('lodash') const expect = chai.expect const constants = require('../constants.js') +const helpers = require('../helpers.js') const app = require('../../../src/index.js') describe('Testing Get CVE-ID endpoint', () => { @@ -109,6 +110,84 @@ describe('Testing Get CVE-ID endpoint', () => { expect(res).to.have.status(200) }) }) + it('For non Secretariat users, should redact requested_by.user values not in requested_by.cna org', async () => { + const cveId = await helpers.cveIdReserveHelper(1, '2023', constants.nonSecretariatUserHeaders['CVE-API-ORG'], 'non-sequential') + + // change users org for testing + await helpers.userOrgUpdateAsSecHelper(constants.nonSecretariatUserHeaders['CVE-API-USER'], constants.nonSecretariatUserHeaders['CVE-API-ORG'], 'mitre') + + await chai.request(app) + .get('/api/cve-id?state=RESERVED') + .set(constants.nonSecretariatUserHeaders2) + .then(async (res, err) => { + const cveIdObject = _.find(res.body.cve_ids, obj => { + return obj.cve_id === cveId + }) + expect(err).to.be.undefined + expect(res).to.have.status(200) + expect(cveIdObject.requested_by.user).to.equal('REDACTED') + + // Reset user to original org + await helpers.userOrgUpdateAsSecHelper(constants.nonSecretariatUserHeaders['CVE-API-USER'], 'mitre', 'win_5') + }) + }) + it('For non Secretariat users, should redact requested_by.user values when requested_by.cna is not owning_cna', async () => { + const cveId = await helpers.cveIdReserveHelper(1, '2023', constants.nonSecretariatUserHeaders['CVE-API-ORG'], 'non-sequential') + + // change cve-id's owning_org for testing + await helpers.updateOwningOrgAsSecHelper(cveId, constants.nonSecretariatUserHeaders3['CVE-API-ORG']) + + await chai.request(app) + .get('/api/cve-id?state=RESERVED') + .set(constants.nonSecretariatUserHeaders3) + .then(async (res, err) => { + const cveIdObject = _.find(res.body.cve_ids, obj => { + return obj.cve_id === cveId + }) + expect(err).to.be.undefined + expect(res).to.have.status(200) + expect(cveIdObject.requested_by.user).to.equal('REDACTED') + }) + }) + it('For Secretariat users, should redact requested_by.user values not in requested_by.cna org', async () => { + const cveId = await helpers.cveIdReserveHelper(1, '2023', constants.nonSecretariatUserHeaders['CVE-API-ORG'], 'non-sequential') + + // change users org for testing + await helpers.userOrgUpdateAsSecHelper(constants.nonSecretariatUserHeaders['CVE-API-USER'], constants.nonSecretariatUserHeaders['CVE-API-ORG'], 'mitre') + + await chai.request(app) + .get('/api/cve-id?state=RESERVED') + .set(constants.headers) + .then(async (res, err) => { + const cveIdObject = _.find(res.body.cve_ids, obj => { + return obj.cve_id === cveId + }) + expect(err).to.be.undefined + expect(res).to.have.status(200) + expect(cveIdObject.requested_by.user).to.equal(constants.nonSecretariatUserHeaders['CVE-API-USER']) + + // Reset user to original org + await helpers.userOrgUpdateAsSecHelper(constants.nonSecretariatUserHeaders['CVE-API-USER'], 'mitre', 'win_5') + }) + }) + it('For Secretariat users, should redact requested_by.user values when requested_by.cna is not owning_cna', async () => { + const cveId = await helpers.cveIdReserveHelper(1, '2023', constants.nonSecretariatUserHeaders['CVE-API-ORG'], 'non-sequential') + + // change cve-id's owning_org for testing + await helpers.updateOwningOrgAsSecHelper(cveId, constants.nonSecretariatUserHeaders3['CVE-API-ORG']) + + await chai.request(app) + .get('/api/cve-id?state=RESERVED') + .set(constants.headers) + .then(async (res, err) => { + const cveIdObject = _.find(res.body.cve_ids, obj => { + return obj.cve_id === cveId + }) + expect(err).to.be.undefined + expect(res).to.have.status(200) + expect(cveIdObject.requested_by.user).to.equal(constants.nonSecretariatUserHeaders['CVE-API-USER']) + }) + }) }) context('negative tests', () => { it('Feb 29 2100 should not be valid', async () => { diff --git a/test/integration-tests/helpers.js b/test/integration-tests/helpers.js index af977623a..4fd5cce6f 100644 --- a/test/integration-tests/helpers.js +++ b/test/integration-tests/helpers.js @@ -96,6 +96,26 @@ async function cveUpdateAsCnaHelperWithAdpContainer (cveId, adpContainer) { }) } +async function userOrgUpdateAsSecHelper (userName, orgShortName, newOrgShortName) { + await chai.request(app) + .put(`/api/org/${orgShortName}/user/${userName}?org_short_name=${newOrgShortName}`) + .set(constants.headers) + .then((res, err) => { + // Safety Expect + expect(res).to.have.status(200) + }) +} + +async function updateOwningOrgAsSecHelper (cveId, newOrgShortName) { + await chai.request(app) + .put(`/api/cve-id/${cveId}?org=${newOrgShortName}`) + .set(constants.headers) + .then((res, err) => { + // Safety Expect + expect(res).to.have.status(200) + }) +} + module.exports = { cveIdReserveHelper, cveIdBulkReserveHelper, @@ -104,5 +124,7 @@ module.exports = { cveRequestAsSecHelper, cveUpdatetAsCnaHelperWithCnaContainer, cveUpdateAsSecHelper, - cveUpdateAsCnaHelperWithAdpContainer + cveUpdateAsCnaHelperWithAdpContainer, + userOrgUpdateAsSecHelper, + updateOwningOrgAsSecHelper }