Repository: CVEProject/cve-services (js)

Issue:
Publishing a CVE record should throw a 400 if `datePublic` is a future date
## Summary
Currently, the `datePublic` field in a CVE record can be set to anytime, including future dates. This is misleading because the record will be published immediately after the request is made, regardless of the `datePublic` field. Validation should be updated to catch instances of `datePublic` values and throw an error explaining why.

### Definition of Done

- Future `datePublic` fields are no longer allowed when publishing CVE records
- A helpful error is thrown explaining the above case (i.e. `datePublic` times cannot occur in the future)

Pull request:
Resolves #1097 Prevents datePublic values from being future dates

Closes Issue #1097

# Summary
Created middleware to check for and prevent datePublic values that are in the future and throw an error explaining why.

# Important Changes
`cve.middleware.js`
- created validateDatePublic and datePublicHelper functions

`cve.controller/index.js`
- Added new middleware to corresponding endpoints
- 
# Testing

Steps to manually test updated functionality, if possible
- [ ] 1) npm run test:unit-tests
- [ ] 2) Ensure cna and adp containers cannot have future datePublic values



Patch:
diff --git a/src/controller/cve.controller/cve.middleware.js b/src/controller/cve.controller/cve.middleware.js
index 66931bbf..7aa6a295 100644
--- a/src/controller/cve.controller/cve.middleware.js
+++ b/src/controller/cve.controller/cve.middleware.js
@@ -147,6 +147,28 @@ function validateCveCnaContainerJsonSchema (req, res, next) {
   next()
 }
 
+/**
+ * Checks that datePublic field is not a future date
+ *
+ * @param {String} dateIndex
+ * @returns true
+ * @throws Error
+ */
+function validateDatePublic (dateIndex) {
+  // Check if datePublic is a future date
+  return body(dateIndex).optional({ nullable: true }).custom((datePublic) => {
+    if (datePublicHelper(datePublic)) {
+      return true
+    }
+    throw new Error('datePublic cannot be a future date')
+  })
+}
+
+function datePublicHelper (datePublic) {
+  const currentDate = new Date().toISOString()
+  return currentDate > datePublic
+}
+
 // 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.
@@ -163,5 +185,7 @@ module.exports = {
   validateUniqueEnglishEntry,
   hasSingleEnglishEntry,
   validateDescription,
-  validateRejectBody
+  validateRejectBody,
+  validateDatePublic,
+  datePublicHelper
 }
diff --git a/src/controller/cve.controller/index.js b/src/controller/cve.controller/index.js
index 0a5c465f..2e89fe49 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 } = require('./cve.middleware')
+const { parseGetParams, parsePostParams, parseError, validateCveCnaContainerJsonSchema, validateCveAdpContainerJsonSchema, validateRejectBody, validateUniqueEnglishEntry, validateDescription, validateDatePublic } = require('./cve.middleware')
 const getConstants = require('../../constants').getConstants
 const CONSTANTS = getConstants()
 const CHOICES = [CONSTANTS.CVE_STATES.REJECTED, CONSTANTS.CVE_STATES.PUBLISHED]
@@ -336,6 +336,7 @@ router.post('/cve/:id',
   // the lang key to check depends on the state, so pass both
   validateUniqueEnglishEntry(['containers.cna.descriptions', 'containers.cna.rejectedReasons']),
   validateDescription(['containers.cna.rejectedReasons', 'containers.cna.descriptions', 'containers.cna.problemTypes[0].descriptions']),
+  validateDatePublic(['containers.cna.datePublic']),
   param(['id']).isString().matches(CONSTANTS.CVE_ID_REGEX),
   parseError,
   parsePostParams,
@@ -420,6 +421,7 @@ router.put('/cve/:id',
   // the lang key to check depends on the state, so pass both
   validateUniqueEnglishEntry(['containers.cna.descriptions', 'containers.cna.rejectedReasons']),
   validateDescription(['containers.cna.rejectedReasons', 'containers.cna.descriptions', 'containers.cna.problemTypes[0].descriptions']),
+  validateDatePublic(['containers.cna.datePublic']),
   param(['id']).isString().matches(CONSTANTS.CVE_ID_REGEX),
   parseError,
   parsePostParams,
@@ -505,6 +507,7 @@ router.post('/cve/:id/cna',
   validateCveCnaContainerJsonSchema,
   validateUniqueEnglishEntry('cnaContainer.descriptions'),
   validateDescription(['cnaContainer.descriptions', 'cnaContainer.problemTypes[0].descriptions']),
+  validateDatePublic(['cnaContainer.datePublic']),
   param(['id']).isString().matches(CONSTANTS.CVE_ID_REGEX),
   parseError,
   parsePostParams,
@@ -591,6 +594,7 @@ router.put('/cve/:id/cna',
   validateCveCnaContainerJsonSchema,
   validateUniqueEnglishEntry('cnaContainer.descriptions'),
   validateDescription(['cnaContainer.descriptions', 'cnaContainer.problemTypes[0].descriptions']),
+  validateDatePublic(['cnaContainer.datePublic']),
   param(['id']).isString().matches(CONSTANTS.CVE_ID_REGEX),
   parseError,
   parsePostParams,
@@ -849,6 +853,7 @@ router.put('/cve/:id/adp',
   mw.validateUser,
   mw.onlyAdps,
   validateCveAdpContainerJsonSchema,
+  validateDatePublic(['adpContainer.datePublic']),
   param(['id']).isString().matches(CONSTANTS.CVE_ID_REGEX),
   parseError,
   parsePostParams,


Test patch:
diff --git a/test/integration-tests/constants.js b/test/integration-tests/constants.js
index 1b683776..4b8b954b 100644
--- a/test/integration-tests/constants.js
+++ b/test/integration-tests/constants.js
@@ -242,5 +242,12 @@ const testAdp2 = {
 }
 
 module.exports = {
-  headers, nonSecretariatUserHeaders, badNonSecretariatUserHeaders, nonSecretariatUserHeadersWithAdp2, testCve, testCveEdited, testAdp, testAdp2
+  headers,
+  nonSecretariatUserHeaders,
+  badNonSecretariatUserHeaders,
+  nonSecretariatUserHeadersWithAdp2,
+  testCve,
+  testCveEdited,
+  testAdp,
+  testAdp2
 }
diff --git a/test/schemas/5.0/CVE-2017-4024_published.json b/test/schemas/5.0/CVE-2017-4024_published.json
index a65c5fd2..3dcb7529 100644
--- a/test/schemas/5.0/CVE-2017-4024_published.json
+++ b/test/schemas/5.0/CVE-2017-4024_published.json
@@ -26,17 +26,17 @@
                     }
                 ],
                 "affected": [
-                        {
-                          "vendor": "u",
-                          "product": "yuyi",
-                          "versions": [
-                              {
-                                  "version": "uuy",
-                                  "status": "affected"
-                              }
-                          ]
-                        }
-                  ],
+                    {
+                        "vendor": "u",
+                        "product": "yuyi",
+                        "versions": [
+                            {
+                                "version": "uuy",
+                                "status": "affected"
+                            }
+                        ]
+                    }
+                ],
                 "providerMetadata": {
                     "orgId": "88c02595-c8f7-4864-a0e7-e09b3e1da691",
                     "shortName": "cisco",
@@ -137,17 +137,17 @@
                 }
             ],
             "affected": [
-              {
-                "vendor": "u",
-                "product": "yuyi",
-                "versions": [
-                    {
-                        "version": "uuy",
-                        "status": "affected"
-                    }
-                ]
-              }
-          ],
+                {
+                    "vendor": "u",
+                    "product": "yuyi",
+                    "versions": [
+                        {
+                            "version": "uuy",
+                            "status": "affected"
+                        }
+                    ]
+                }
+            ],
             "providerMetadata": {
                 "orgId": "88c02595-c8f7-4864-a0e7-e09b3e1da691",
                 "shortName": "cisco",
@@ -230,7 +230,8 @@
             ],
             "source": {
                 "discoverer": "Tom Smith"
-            }
+            },
+            "datePublic": "2022-02-20T00:00:00"
         }
     }
-  }
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/test/schemas/5.0/CVE-2017-4024_published_bad_datePublic.json b/test/schemas/5.0/CVE-2017-4024_published_bad_datePublic.json
new file mode 100644
index 00000000..61c4c920
--- /dev/null
+++ b/test/schemas/5.0/CVE-2017-4024_published_bad_datePublic.json
@@ -0,0 +1,237 @@
+{
+    "dataType": "CVE_RECORD",
+    "dataVersion": "5.0",
+    "cveMetadata": {
+        "cveId": "CVE-2017-4024",
+        "assignerOrgId": "88c02595-c8f7-4864-a0e7-e09b3e1da691",
+        "assignerShortName": "cisco",
+        "requesterUserId": "1fcbf829-81ae-4b53-a61d-9fa04711447f",
+        "state": "PUBLISHED",
+        "dateUpdated": "2021-11-19T20:07:00.403Z"
+    },
+    "containers": {
+        "adp": [
+            {
+                "metrics": [
+                    {
+                        "format": "uyi",
+                        "scenario": "kjk",
+                        "other": {
+                            "type": "oio",
+                            "content": {
+                                "kjgk": "kjgkhg"
+                            }
+                        },
+                        "uu": 8
+                    }
+                ],
+                "affected": [
+                    {
+                        "vendor": "u",
+                        "product": "yuyi",
+                        "versions": [
+                            {
+                                "version": "uuy",
+                                "status": "affected"
+                            }
+                        ]
+                    }
+                ],
+                "providerMetadata": {
+                    "orgId": "88c02595-c8f7-4864-a0e7-e09b3e1da691",
+                    "shortName": "cisco",
+                    "dateUpdated": "2018-11-13T20:20:39+00:00"
+                },
+                "descriptions": [
+                    {
+                        "lang": "en",
+                        "value": "y"
+                    }
+                ],
+                "problemTypes": [
+                    {
+                        "descriptions": [
+                            {
+                                "lang": "en",
+                                "description": "y",
+                                "cweId": "CWE-91",
+                                "type": "u",
+                                "references": [
+                                    {
+                                        "url": "https://cwe.mitre.org/data/definitions/284.html"
+                                    }
+                                ]
+                            }
+                        ]
+                    }
+                ],
+                "references": [
+                    {
+                        "url": "https://cwe.mitre.org/data/definitions/284.html",
+                        "name": "12345",
+                        "refsource": "12345",
+                        "tags": [
+                            "x_Broken Link"
+                        ]
+                    }
+                ],
+                "impacts": [
+                    {
+                        "descriptions": [
+                            {
+                                "lang": "en",
+                                "value": "y"
+                            }
+                        ],
+                        "capecId": "CAPEC-9999"
+                    }
+                ],
+                "configurations": [
+                    {
+                        "lang": "en",
+                        "value": "y"
+                    }
+                ],
+                "workarounds": [
+                    {
+                        "lang": "en",
+                        "value": "y"
+                    }
+                ],
+                "exploits": [
+                    {
+                        "lang": "en",
+                        "value": "y"
+                    }
+                ],
+                "timeline": [
+                    {
+                        "time": "2018-11-13T20:20:39+00:00",
+                        "lang": "in",
+                        "value": "y"
+                    }
+                ],
+                "credits": [
+                    {
+                        "lang": "en",
+                        "value": "y"
+                    }
+                ],
+                "source": {
+                    "discoverer": "Tom Smith"
+                }
+            }
+        ],
+        "cna": {
+            "metrics": [
+                {
+                    "format": "uyi",
+                    "scenario": "kjk",
+                    "other": {
+                        "type": "oio",
+                        "content": {
+                            "kjgk": "kjgkhg"
+                        }
+                    },
+                    "uu": 8
+                }
+            ],
+            "affected": [
+                {
+                    "vendor": "u",
+                    "product": "yuyi",
+                    "versions": [
+                        {
+                            "version": "uuy",
+                            "status": "affected"
+                        }
+                    ]
+                }
+            ],
+            "providerMetadata": {
+                "orgId": "88c02595-c8f7-4864-a0e7-e09b3e1da691",
+                "shortName": "cisco",
+                "dateUpdated": "2018-11-13T20:20:39+00:00"
+            },
+            "descriptions": [
+                {
+                    "lang": "en",
+                    "value": "ya"
+                }
+            ],
+            "problemTypes": [
+                {
+                    "descriptions": [
+                        {
+                            "lang": "en",
+                            "description": "y",
+                            "cweId": "CWE-91",
+                            "type": "u",
+                            "references": [
+                                {
+                                    "url": "https://cwe.mitre.org/data/definitions/284.html"
+                                }
+                            ]
+                        }
+                    ]
+                }
+            ],
+            "references": [
+                {
+                    "url": "https://cwe.mitre.org/data/definitions/284.html",
+                    "name": "12345",
+                    "refsource": "12345",
+                    "tags": [
+                        "x_Broken Link"
+                    ]
+                }
+            ],
+            "impacts": [
+                {
+                    "descriptions": [
+                        {
+                            "lang": "en",
+                            "value": "y"
+                        }
+                    ],
+                    "capecId": "CAPEC-9999"
+                }
+            ],
+            "configurations": [
+                {
+                    "lang": "en",
+                    "value": "y"
+                }
+            ],
+            "workarounds": [
+                {
+                    "lang": "en",
+                    "value": "y"
+                }
+            ],
+            "exploits": [
+                {
+                    "lang": "en",
+                    "value": "y"
+                }
+            ],
+            "timeline": [
+                {
+                    "time": "2018-11-13T20:20:39+00:00",
+                    "lang": "in",
+                    "value": "y"
+                }
+            ],
+            "credits": [
+                {
+                    "lang": "en",
+                    "value": "y"
+                }
+            ],
+            "source": {
+                "discoverer": "Tom Smith"
+            },
+            "datePublic": "3024-02-20T00:00:00"
+        }
+    }
+}
\ No newline at end of file
diff --git a/test/unit-tests/cve/validateDatePublicTest.js b/test/unit-tests/cve/validateDatePublicTest.js
new file mode 100644
index 00000000..b0c1e0e7
--- /dev/null
+++ b/test/unit-tests/cve/validateDatePublicTest.js
@@ -0,0 +1,26 @@
+/* eslint-disable no-unused-expressions */
+
+const chai = require('chai')
+const expect = chai.expect
+const _ = require('lodash')
+
+const { datePublicHelper } = require('../../../src/controller/cve.controller/cve.middleware')
+const tempValidDatePublicRecord = require('../../schemas/5.0/CVE-2017-4024_published.json')
+const validDatePublicRecord = _.cloneDeep(tempValidDatePublicRecord)
+const inValidDatePublicRecord = require('../../schemas/5.0/CVE-2017-4024_published_bad_datePublic.json')
+
+describe('Testing validateDatePublic middleware', () => {
+  context('Negative Tests', () => {
+    it('Should throw an error for datePublic dates in the future ', () => {
+      const result = datePublicHelper(inValidDatePublicRecord.containers.cna.datePublic)
+      expect(result).to.be.false
+    })
+  })
+
+  context('Positive Tests', () => {
+    it('Should return true for records with datePublic in the past', () => {
+      const result = datePublicHelper(validDatePublicRecord.containers.cna.datePublic)
+      expect(result).to.be.true
+    })
+  })
+})