diff --git a/api-docs/openapi.json b/api-docs/openapi.json index 6c00ce16a..215dbcadc 100644 --- a/api-docs/openapi.json +++ b/api-docs/openapi.json @@ -1,7 +1,7 @@ { "openapi": "3.0.2", "info": { - "version": "ur-v0.3.0", + "version": "2.6.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.2 CVE Record format. Details of the JSON 5.2 schema are located here.
Contact the CVE Services team", "contact": { diff --git a/package-lock.json b/package-lock.json index 563835e7e..6a15d346c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cve-services", - "version": "ur-v0.3.0", + "version": "2.6.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cve-services", - "version": "ur-v0.3.0", + "version": "2.6.1", "license": "(CC0)", "dependencies": { "ajv": "^8.6.2", @@ -109,6 +109,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", "dev": true, + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.0", @@ -1568,6 +1569,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1601,6 +1603,7 @@ "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -2143,6 +2146,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", @@ -2294,6 +2298,7 @@ "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", "dev": true, + "peer": true, "dependencies": { "assertion-error": "^1.1.0", "check-error": "^1.0.3", @@ -3329,6 +3334,7 @@ "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -3462,6 +3468,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", "dev": true, + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.8", @@ -3564,6 +3571,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", "dev": true, + "peer": true, "dependencies": { "eslint-plugin-es": "^3.0.0", "eslint-utils": "^2.0.0", @@ -3616,6 +3624,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.3.1.tgz", "integrity": "sha512-bY2sGqyptzFBDLh/GMbAxfdJC+b0f23ME63FOE4+Jao0oZ3E1LEwFtWJX/1pGMJLiTtrSSern2CRM/g+dfc0eQ==", "dev": true, + "peer": true, "engines": { "node": ">=6" } @@ -3639,6 +3648,7 @@ "url": "https://feross.org/support" } ], + "peer": true, "peerDependencies": { "eslint": ">=5.0.0" } @@ -8417,6 +8427,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -9006,6 +9017,7 @@ "integrity": "sha512-fbgTiE8BfUJZuBeq2Yi7J3RB3WGUQ9PNuNbmgi6jt9Iv8qrkxfy19Ds3OpL1Pm7zg3BtTVhvcUZbIRQ0wmSjAQ==", "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, + "peer": true, "dependencies": { "@babel/code-frame": "^7.0.0", "@eslint/eslintrc": "^0.3.0", @@ -9110,6 +9122,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.24.2.tgz", "integrity": "sha512-hNVtyhiEtZmpsabL4neEj+6M5DCLgpYyG9nzJY8lZQeQXEn5UPW1DpUdsMHMXsq98dbNm7nt1w9ZMSVpfJdi8Q==", "dev": true, + "peer": true, "dependencies": { "array-includes": "^3.1.3", "array.prototype.flat": "^1.2.4", @@ -9160,6 +9173,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-5.1.1.tgz", "integrity": "sha512-XgdcdyNzHfmlQyweOPTxmc7pIsS6dE4MvwhXWMQ2Dxs1XAL2GJDilUsjWen6TWik0aSI+zD/PqocZBblcm9rdA==", "dev": true, + "peer": true, "engines": { "node": "^10.12.0 || >=12.0.0" }, @@ -9172,6 +9186,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.25.3.tgz", "integrity": "sha512-ZMbFvZ1WAYSZKY662MBVEWR45VaBT6KSJCiupjrNlcdakB90juaZeDCbJq19e73JZQubqFtgETohwgAt8u5P6w==", "dev": true, + "peer": true, "dependencies": { "array-includes": "^3.1.3", "array.prototype.flatmap": "^1.2.4", @@ -10214,6 +10229,7 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz", "integrity": "sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==", "dev": true, + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.6", @@ -10260,6 +10276,7 @@ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", "dev": true, + "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^1.2.0", @@ -10729,6 +10746,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", "dev": true, + "peer": true, "requires": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.0", @@ -11797,7 +11815,8 @@ "version": "8.14.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", - "dev": true + "dev": true, + "peer": true }, "acorn-jsx": { "version": "5.3.2", @@ -11820,6 +11839,7 @@ "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "peer": true, "requires": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -12203,6 +12223,7 @@ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.3.tgz", "integrity": "sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==", "dev": true, + "peer": true, "requires": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", @@ -12297,6 +12318,7 @@ "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", "dev": true, + "peer": true, "requires": { "assertion-error": "^1.1.0", "check-error": "^1.0.3", @@ -13097,6 +13119,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", "dev": true, + "peer": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -13271,6 +13294,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", "dev": true, + "peer": true, "requires": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.8", @@ -13353,6 +13377,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", "dev": true, + "peer": true, "requires": { "eslint-plugin-es": "^3.0.0", "eslint-utils": "^2.0.0", @@ -13393,13 +13418,15 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.3.1.tgz", "integrity": "sha512-bY2sGqyptzFBDLh/GMbAxfdJC+b0f23ME63FOE4+Jao0oZ3E1LEwFtWJX/1pGMJLiTtrSSern2CRM/g+dfc0eQ==", - "dev": true + "dev": true, + "peer": true }, "eslint-plugin-standard": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-4.1.0.tgz", "integrity": "sha512-ZL7+QRixjTR6/528YNGyDotyffm5OQst/sGxKDwGb9Uqs4In5Egi4+jbobhqJoyoCM6/7v/1A5fhQ7ScMtDjaQ==", "dev": true, + "peer": true, "requires": {} }, "eslint-scope": { @@ -16784,6 +16811,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "peer": true, "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -17227,6 +17255,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.18.0.tgz", "integrity": "sha512-fbgTiE8BfUJZuBeq2Yi7J3RB3WGUQ9PNuNbmgi6jt9Iv8qrkxfy19Ds3OpL1Pm7zg3BtTVhvcUZbIRQ0wmSjAQ==", "dev": true, + "peer": true, "requires": { "@babel/code-frame": "^7.0.0", "@eslint/eslintrc": "^0.3.0", @@ -17286,6 +17315,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.24.2.tgz", "integrity": "sha512-hNVtyhiEtZmpsabL4neEj+6M5DCLgpYyG9nzJY8lZQeQXEn5UPW1DpUdsMHMXsq98dbNm7nt1w9ZMSVpfJdi8Q==", "dev": true, + "peer": true, "requires": { "array-includes": "^3.1.3", "array.prototype.flat": "^1.2.4", @@ -17329,6 +17359,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-5.1.1.tgz", "integrity": "sha512-XgdcdyNzHfmlQyweOPTxmc7pIsS6dE4MvwhXWMQ2Dxs1XAL2GJDilUsjWen6TWik0aSI+zD/PqocZBblcm9rdA==", "dev": true, + "peer": true, "requires": {} }, "eslint-plugin-react": { @@ -17336,6 +17367,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.25.3.tgz", "integrity": "sha512-ZMbFvZ1WAYSZKY662MBVEWR45VaBT6KSJCiupjrNlcdakB90juaZeDCbJq19e73JZQubqFtgETohwgAt8u5P6w==", "dev": true, + "peer": true, "requires": { "array-includes": "^3.1.3", "array.prototype.flatmap": "^1.2.4", @@ -18107,6 +18139,7 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz", "integrity": "sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==", "dev": true, + "peer": true, "requires": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.6", @@ -18162,6 +18195,7 @@ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", "dev": true, + "peer": true, "requires": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^1.2.0", diff --git a/package.json b/package.json index 6ec0b42bf..f055c9a40 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cve-services", "author": "Automation Working Group", - "version": "ur-v0.3.0", + "version": "2.6.1", "license": "(CC0)", "devDependencies": { "@faker-js/faker": "^7.6.0", diff --git a/src/controller/cve.controller/cve.middleware.js b/src/controller/cve.controller/cve.middleware.js index 5627246d1..90b5a64e7 100644 --- a/src/controller/cve.controller/cve.middleware.js +++ b/src/controller/cve.controller/cve.middleware.js @@ -178,6 +178,32 @@ function datePublicHelper (datePublic) { return currentDate > datePublicWithGracePeriod } +/** + * Checks that timeline.time fields are valid datetime objects. + * This accounts for invalid timezone offsets that aren't handled by the schema. + * + * @param {String} dateIndex + * @returns true + * @throws Error + */ +function validateTimelineDates (dateIndex) { + // Check if datePublic is a future date + return body(dateIndex).isArray().withMessage('Time must be a date string').optional({ nullable: true }).bail().custom((timelineArray) => { + for (const timelineObj of timelineArray) { + const value = new Date(timelineObj.time) + if (!validateTimelineHelper(value)) { + throw new Error(`Invalid date string: ${timelineObj.time} `) + } + } + + return true + }) +} + +function validateTimelineHelper (value) { + return value instanceof Date && !isNaN(value) +} + // Organizations in the ADP pilot are generating JSON programatically, and thus // informing them about the result of the final validation (against the full // CVE Record schema) is currently sufficient. @@ -290,6 +316,7 @@ module.exports = { validateDescription, validateRejectBody, validateDatePublic, + validateTimelineDates, datePublicHelper, validatePURL, purlValidateHelper diff --git a/src/controller/cve.controller/index.js b/src/controller/cve.controller/index.js index a6520935d..c5a78503e 100644 --- a/src/controller/cve.controller/index.js +++ b/src/controller/cve.controller/index.js @@ -4,7 +4,7 @@ const mw = require('../../middleware/middleware') const errorMsgs = require('../../middleware/errorMessages') const controller = require('./cve.controller') const { body, param, query } = require('express-validator') -const { parseGetParams, parsePostParams, parseError, validateCveCnaContainerJsonSchema, validateCveAdpContainerJsonSchema, validateRejectBody, validateUniqueEnglishEntry, validateDescription, validateDatePublic, validatePURL } = require('./cve.middleware') +const { parseGetParams, parsePostParams, parseError, validateCveCnaContainerJsonSchema, validateCveAdpContainerJsonSchema, validateRejectBody, validateUniqueEnglishEntry, validateDescription, validateDatePublic, validateTimelineDates, validatePURL } = require('./cve.middleware') const getConstants = require('../../constants').getConstants const CONSTANTS = getConstants() const CHOICES = [CONSTANTS.CVE_STATES.REJECTED, CONSTANTS.CVE_STATES.PUBLISHED] @@ -499,6 +499,7 @@ router.post('/cve/:id', validateUniqueEnglishEntry(['containers.cna.descriptions', 'containers.cna.rejectedReasons']), validateDescription(['containers.cna.rejectedReasons', 'containers.cna.descriptions', 'containers.cna.problemTypes[0].descriptions']), validateDatePublic(['containers.cna.datePublic']), + validateTimelineDates(['containers.cna.timeline']), validatePURL(['containers.cna.affected']), param(['id']).isString().matches(CONSTANTS.CVE_ID_REGEX), parseError, @@ -586,6 +587,7 @@ router.put('/cve/:id', validateUniqueEnglishEntry(['containers.cna.descriptions', 'containers.cna.rejectedReasons']), validateDescription(['containers.cna.rejectedReasons', 'containers.cna.descriptions', 'containers.cna.problemTypes[0].descriptions']), validateDatePublic(['containers.cna.datePublic']), + validateTimelineDates(['containers.cna.timeline']), validatePURL(['containers.cna.affected']), param(['id']).isString().matches(CONSTANTS.CVE_ID_REGEX), parseError, @@ -685,6 +687,7 @@ router.post('/cve/:id/cna', validateUniqueEnglishEntry('cnaContainer.descriptions'), validateDescription(['cnaContainer.descriptions', 'cnaContainer.problemTypes[0].descriptions']), validateDatePublic(['cnaContainer.datePublic']), + validateTimelineDates(['cnaContainer.timeline']), validatePURL(['cnaContainer.affected']), param(['id']).isString().matches(CONSTANTS.CVE_ID_REGEX), parseError, @@ -786,6 +789,7 @@ router.put('/cve/:id/cna', validateUniqueEnglishEntry('cnaContainer.descriptions'), validateDescription(['cnaContainer.descriptions', 'cnaContainer.problemTypes[0].descriptions']), validateDatePublic(['cnaContainer.datePublic']), + validateTimelineDates(['cnaContainer.timeline']), validatePURL(['cnaContainer.affected']), param(['id']).isString().matches(CONSTANTS.CVE_ID_REGEX), parseError, @@ -1058,6 +1062,7 @@ router.put('/cve/:id/adp', mw.trimJSONWhitespace, validateCveAdpContainerJsonSchema, validatePURL(['adpContainer.affected']), + validateTimelineDates(['adpContainer.timeline']), param(['id']).isString().matches(CONSTANTS.CVE_ID_REGEX), parseError, parsePostParams, diff --git a/src/swagger.js b/src/swagger.js index 9559a608b..5bca9f75b 100644 --- a/src/swagger.js +++ b/src/swagger.js @@ -22,7 +22,7 @@ const fullCnaContainerRequest = require('../schemas/cve/create-cve-record-cna-re /* eslint-disable no-multi-str */ const doc = { info: { - version: 'ur-v0.3.0', + version: '2.6.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/src/utils/utils.js b/src/utils/utils.js index 355f5178e..1d5f2da88 100644 --- a/src/utils/utils.js +++ b/src/utils/utils.js @@ -234,7 +234,15 @@ function convertDatesToISO (obj, dateKeys) { for (const key of dateKeys) { if (_.has(obj, key)) { const value = _.get(obj, key) - updateDateValue(obj, key, value) + + if (key === 'timeline') { + _.each(value, (timelineObj) => { + const value = _.get(timelineObj, 'time') + updateDateValue(timelineObj, 'time', value) + }) + } else { + updateDateValue(obj, key, value) + } } } diff --git a/test/integration-tests/cve-id/getCveIdTest.js b/test/integration-tests/cve-id/getCveIdTest.js index 41dc671e0..12ed01c0b 100644 --- a/test/integration-tests/cve-id/getCveIdTest.js +++ b/test/integration-tests/cve-id/getCveIdTest.js @@ -12,7 +12,7 @@ const app = require('../../../src/index.js') describe('Testing Get CVE-ID endpoint', () => { // TODO: Update this test to dynamically calculate reserved count. - const RESESRVED_COUNT = 122 + const RESESRVED_COUNT = 124 const YEAR_COUNT = 10 const PUB_YEAR_COUNT = 4 const TIME_WINDOW_COUNT = 40 diff --git a/test/integration-tests/cve/validateTimelineDatesTest.js b/test/integration-tests/cve/validateTimelineDatesTest.js new file mode 100644 index 000000000..166569ea9 --- /dev/null +++ b/test/integration-tests/cve/validateTimelineDatesTest.js @@ -0,0 +1,95 @@ +/* eslint-disable no-unused-expressions */ + +const chai = require('chai') +chai.use(require('chai-http')) +const expect = chai.expect + +const constants = require('../constants.js') +const app = require('../../../src/index.js') +const helpers = require('../helpers.js') +const _ = require('lodash') + +const cnaContainer = require('../../schemas/cna-container/cna-container_pass.json').cnaContainer + +// Parameters for the CVE-ID reservation helper +const requestLength = 1 +const shortName = 'win_5' +const cveYear = '2023' +const batchType = 'non-sequential' + +async function cveRequestAsCna (cveId, headers, body) { + return await chai.request(app) + .post(`/api/cve/${cveId}/cna`) + .set(headers) + .send(body) +} + +describe('Testing validateTimelineDates Middleware', () => { + let cveId + let cnaContainerCopy + + beforeEach(async () => { + // Reserve a custom CVE-ID + cveId = await helpers.cveIdReserveHelper(requestLength, cveYear, shortName, batchType) + cnaContainerCopy = _.cloneDeep(cnaContainer) + }) + + context('Positive Tests', () => { + it('should allow valid timeline dates', async () => { + cnaContainerCopy.timeline = [ + { + time: '2023-10-25T00:00:00.000Z', + lang: 'en', + value: 'timeline' + } + ] + + const body = { + cnaContainer: cnaContainerCopy + } + + const res = await cveRequestAsCna(cveId, constants.nonSecretariatUserHeaders, body) + expect(res).to.have.status(200) + expect(res.body.created.containers.cna.timeline[0].time).to.equal('2023-10-25T00:00:00.000Z') + }) + }) + + context('Negative Tests', () => { + it('should reject invalid timeline date strings', async () => { + cnaContainerCopy.timeline = [ + { + time: 'invalid-date', + lang: 'en', + value: 'timeline' + } + ] + + const body = { + cnaContainer: cnaContainerCopy + } + + const res = await cveRequestAsCna(cveId, constants.nonSecretariatUserHeaders, body) + expect(res).to.have.status(400) + expect(res.body.error).to.include('INVALID_JSON_SCHEMA') + }) + + it('should reject invalid timezone offsets', async () => { + cnaContainerCopy.timeline = [ + { + time: '2026-01-01T00:00:00.123456+25:00', + lang: 'en', + value: 'timeline' + } + ] + + const body = { + cnaContainer: cnaContainerCopy + } + + const res = await cveRequestAsCna(cveId, constants.nonSecretariatUserHeaders, body) + expect(res).to.have.status(400) + expect(res.body.error).to.include('BAD_INPUT') + expect(res.body.details[0].msg).to.include('Invalid date string') + }) + }) +}) diff --git a/test/unit-tests/middleware/convertDatesToISOTest.js b/test/unit-tests/middleware/convertDatesToISOTest.js index 6805d06f7..f9099f716 100644 --- a/test/unit-tests/middleware/convertDatesToISOTest.js +++ b/test/unit-tests/middleware/convertDatesToISOTest.js @@ -28,4 +28,17 @@ describe('Testing convertDatesToISO', () => { expect(cveAfterDateFormat.containers.adp[0].timeline[1].time).to.equal('2019-12-13T20:20:39.000Z') }) }) + it('Should successfully format date fields when passed object does not have a cna.containers', async () => { + const cveAfterDateFormat = convertDatesToISO(testCVE.containers.cna, DATE_FIELDS) + + // CNA dateUpdated + expect(cveAfterDateFormat.providerMetadata.dateUpdated).to.equal('2018-11-13T20:20:39.000Z') + + // CNA date public + expect(cveAfterDateFormat.datePublic).to.equal('2022-02-20T00:00:00.000Z') + + // CNA timelines + expect(cveAfterDateFormat.timeline[0].time).to.equal('2018-11-13T20:20:39.000Z') + expect(cveAfterDateFormat.timeline[1].time).to.equal('2019-12-13T20:20:39.000Z') + }) })