Repository: CVEProject/cve-services
Pull request #337 (2021-03-11 18:13:35)

Issue 336
Updating JSON schema 4.0 and the validator in RSUS, and adding tests for JSON schema 4.0 validation. 


Patch:
diff --git a/src/controller/cve.controller/index.js b/src/controller/cve.controller/index.js
index c81d4216..47e99d24 100644
--- a/src/controller/cve.controller/index.js
+++ b/src/controller/cve.controller/index.js
@@ -3,9 +3,9 @@ const router = express.Router()
 // const mw = require('../../middleware/middleware')
 // const controller = require('./cve.controller')
 
-// router.get('/cve', middleware.validateUser, controller.CVE_LIST_ALL)
-// router.get('/cve/:id', middleware.validateUser, controller.CVE_SINGLE)
-// router.post('/cve', middleware.validateUser, middleware.validateCveJsonSchema, controller.CVE_SUBMIT)
-// router.post('/cve/:id', middleware.validateUser, middleware.validateCveJsonSchema, controller.CVE_UPDATE_SINGLE)
+// router.get('/cve', mw.validateUser, controller.CVE_LIST_ALL)
+// router.get('/cve/:id', mw.validateUser, controller.CVE_SINGLE)
+// router.post('/cve', mw.validateUser, mw.validateCveJsonSchema, controller.CVE_SUBMIT)
+// router.post('/cve/:id', mw.validateUser, mw.validateCveJsonSchema, controller.CVE_UPDATE_SINGLE)
 
 module.exports = router
diff --git a/src/middleware/error.js b/src/middleware/error.js
index 4e1e064a..3d2ded4f 100644
--- a/src/middleware/error.js
+++ b/src/middleware/error.js
@@ -35,6 +35,17 @@ class MiddlewareError extends idrErr.IDRError {
     err.message = 'This function is currently only allowed to CNAs. This will change for many functions as more administrative roles are implemented.'
     return err
   }
+
+  invalidJsonSchema (errors) { // mw
+    const err = {}
+    err.error = 'INVALID_JSON_SCHEMA'
+    err.message = 'CVE JSON schema validation FAILED.'
+    err.details = {
+      errors: errors
+    }
+
+    return err
+  }
 }
 
 module.exports = {
diff --git a/src/middleware/jsonSchemaV4.js b/src/middleware/jsonSchemaV4.js
new file mode 100644
index 00000000..1efca9d0
--- /dev/null
+++ b/src/middleware/jsonSchemaV4.js
@@ -0,0 +1,296 @@
+const CONSTANTS = require('../constants')
+
+const definitions = {
+  data_type: {
+    enum: [
+      'CVE'
+    ]
+  },
+  data_format: {
+    enum: [
+      'MITRE'
+    ]
+  },
+  data_version: {
+    enum: [
+      '4.0'
+    ]
+  },
+  state_public: {
+    enum: [
+      CONSTANTS.CVE_STATES.PUBLIC
+    ]
+  },
+  state_reserved: {
+    enum: [
+      CONSTANTS.CVE_STATES.RESERVED
+    ]
+  },
+  state_rejected: {
+    enum: [
+      CONSTANTS.CVE_STATES.REJECT
+    ]
+  },
+  cve_id: {
+    type: 'string',
+    pattern: '^CVE-[0-9]{4}-[0-9]{4,}$'
+  },
+  email_address: {
+    type: 'string',
+    pattern: '^([a-zA-Z0-9_\\-\\.]+)@([a-zA-Z0-9_\\-\\.]+)\\.([a-zA-Z]{2,5})$'
+  },
+  lang_string: {
+    type: 'object',
+    required: [
+      'lang',
+      'value'
+    ],
+    properties: {
+      lang: {
+        type: 'string'
+      },
+      value: {
+        type: 'string',
+        maxLength: 3999
+      }
+    }
+  },
+  reference: {
+    type: 'object',
+    required: [
+      'url'
+    ],
+    properties: {
+      url: {
+        maxLength: 500,
+        type: 'string',
+        pattern: '^(ftp|http)s?://\\S+$'
+      }
+    }
+  },
+  product: {
+    type: 'object',
+    required: [
+      'product_name',
+      'version'
+    ],
+    properties: {
+      product_name: {
+        type: 'string'
+      },
+      version: {
+        type: 'object',
+        required: [
+          'version_data'
+        ],
+        properties: {
+          version_data: {
+            type: 'array',
+            minItems: 1,
+            items: {
+              type: 'object',
+              required: [
+                'version_value'
+              ],
+              properties: {
+                version_value: {
+                  type: 'string'
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+const reservedOrRejectedSchema = {
+  $schema: 'http://json-schema.org/draft-04/schema#',
+  type: 'object',
+  required: [
+    'data_type',
+    'data_format',
+    'data_version',
+    'CVE_data_meta',
+    'description'
+  ],
+  properties: {
+    data_type: definitions.data_type,
+    data_format: definitions.data_format,
+    data_version: definitions.data_version,
+    CVE_data_meta: {
+      type: 'object',
+      required: [
+        'ID',
+        'ASSIGNER'
+      ],
+      properties: {
+        ID: definitions.cve_id,
+        ASSIGNER: definitions.email_address,
+        STATE: definitions.state_reserved
+      }
+    },
+    affects: {
+      not: {}
+    },
+    problemtype: {
+      not: {}
+    },
+    references: {
+      not: {}
+    },
+    description: {
+      type: 'object',
+      required: [
+        'description_data'
+      ],
+      properties: {
+        description_data: {
+          type: 'array',
+          minItems: 1,
+          items: definitions.lang_string
+        }
+      }
+    }
+  }
+}
+
+const reservedSchema = JSON.parse(JSON.stringify(reservedOrRejectedSchema)) // clone the object
+const rejectSchema = JSON.parse(JSON.stringify(reservedOrRejectedSchema)) // clone the object
+rejectSchema.properties.CVE_data_meta.properties.STATE = definitions.state_rejected
+
+const publicSchema = {
+  $schema: 'http://json-schema.org/draft-04/schema#',
+  type: 'object',
+  required: [
+    'data_type',
+    'data_format',
+    'data_version',
+    'CVE_data_meta',
+    'affects',
+    'problemtype',
+    'references',
+    'description'
+  ],
+  properties: {
+    data_type: definitions.data_type,
+    data_format: definitions.data_format,
+    data_version: definitions.data_version,
+    CVE_data_meta: {
+      type: 'object',
+      required: [
+        'ID',
+        'ASSIGNER'
+      ],
+      properties: {
+        ID: definitions.cve_id,
+        ASSIGNER: definitions.email_address,
+        STATE: definitions.state_public
+      }
+    },
+    affects: {
+      type: 'object',
+      required: [
+        'vendor'
+      ],
+      properties: {
+        vendor: {
+          type: 'object',
+          required: [
+            'vendor_data'
+          ],
+          properties: {
+            vendor_data: {
+              type: 'array',
+              minItems: 1,
+              items: {
+                type: 'object',
+                required: [
+                  'vendor_name',
+                  'product'
+                ],
+                properties: {
+                  vendor_name: {
+                    type: 'string'
+                  },
+                  product: {
+                    type: 'object',
+                    required: [
+                      'product_data'
+                    ],
+                    properties: {
+                      product_data: {
+                        type: 'array',
+                        minItems: 1,
+                        items: definitions.product
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    problemtype: {
+      type: 'object',
+      required: [
+        'problemtype_data'
+      ],
+      properties: {
+        problemtype_data: {
+          type: 'array',
+          minItems: 1,
+          items: {
+            type: 'object',
+            required: [
+              'description'
+            ],
+            properties: {
+              description: {
+                type: 'array',
+                minItems: 1,
+                items: definitions.lang_string
+              }
+            }
+          }
+        }
+      }
+    },
+    references: {
+      type: 'object',
+      required: [
+        'reference_data'
+      ],
+      properties: {
+        reference_data: {
+          type: 'array',
+          maxItems: 500,
+          minItems: 1,
+          items: definitions.reference
+        }
+      }
+    },
+    description: {
+      type: 'object',
+      required: [
+        'description_data'
+      ],
+      properties: {
+        description_data: {
+          type: 'array',
+          minItems: 1,
+          items: definitions.lang_string
+        }
+      }
+    }
+  }
+}
+
+module.exports = {
+  publicSchema,
+  reservedSchema,
+  rejectSchema
+}
diff --git a/src/middleware/jsonSchema.js b/src/middleware/jsonSchemaV5.js
similarity index 89%
rename from src/middleware/jsonSchema.js
rename to src/middleware/jsonSchemaV5.js
index c9bf5928..c5577214 100644
--- a/src/middleware/jsonSchema.js
+++ b/src/middleware/jsonSchemaV5.js
@@ -1,153 +1,3 @@
-const cveSchema4 = {
-  $schema: 'http://json-schema.org/draft-04/schema#',
-  definitions: {
-    cve_id: {
-      type: 'string',
-      pattern: '^CVE-[0-9]{4}-[0-9]{4,}$'
-    },
-    email_address: {
-      type: 'string',
-      pattern: '^([a-zA-Z0-9_\\-\\.]+)@([a-zA-Z0-9_\\-\\.]+)\\.([a-zA-Z]{2,5})$'
-    },
-    product: {
-      type: 'object',
-      required: ['product_name', 'version'],
-      properties: {
-        product_name: { type: 'string' },
-        version: {
-          type: 'object',
-          required: ['version_data'],
-          properties: {
-            version_data: {
-              type: 'array',
-              minItems: 1,
-              items: {
-                type: 'object',
-                required: ['version_value'],
-                properties: {
-                  version_value: { type: 'string' }
-                }
-              }
-            }
-          }
-        }
-      }
-    },
-    reference: {
-      type: 'object',
-      required: ['url'],
-      properties: {
-        url: {
-          maxLength: 500,
-          type: 'string',
-          pattern: '^(ftp|http)s?://\\S+$'
-        }
-      }
-    },
-    lang_string: {
-      type: 'object',
-      required: ['lang', 'value'],
-      properties: {
-        lang: { type: 'string' },
-        value: { type: 'string', maxLength: 3999 }
-      }
-    }
-  },
-  type: 'object',
-  required: ['data_type', 'data_format', 'data_version', 'CVE_data_meta', 'affects', 'problemtype', 'references', 'description'],
-  properties: {
-    data_type: { enum: ['CVE'] },
-    data_format: { enum: ['MITRE'] },
-    data_version: { enum: ['4.0'] },
-    CVE_data_meta: {
-      type: 'object',
-      required: ['ID', 'ASSIGNER'],
-      properties: {
-        ID: { $ref: '#/definitions/cve_id' },
-        ASSIGNER: { $ref: '#/definitions/email_address' }
-      }
-    },
-    affects: {
-      type: 'object',
-      required: ['vendor'],
-      properties: {
-        vendor: {
-          type: 'object',
-          required: ['vendor_data'],
-          properties: {
-            vendor_data: {
-              type: 'array',
-              minItems: 1,
-              items: {
-                type: 'object',
-                required: ['vendor_name', 'product'],
-                properties: {
-                  vendor_name: { type: 'string' },
-                  product: {
-                    type: 'object',
-                    required: ['product_data'],
-                    properties: {
-                      product_data: {
-                        type: 'array',
-                        minItems: 1,
-                        items: { $ref: '#/definitions/product' }
-                      }
-                    }
-                  }
-                }
-              }
-            }
-          }
-        }
-      }
-    },
-    problemtype: {
-      type: 'object',
-      required: ['problemtype_data'],
-      properties: {
-        problemtype_data: {
-          type: 'array',
-          minItems: 1,
-          items: {
-            type: 'object',
-            required: ['description'],
-            properties: {
-              description: {
-                type: 'array',
-                minItems: 1,
-                items: { $ref: '#/definitions/lang_string' }
-              }
-            }
-          }
-        }
-      }
-    },
-    references: {
-      type: 'object',
-      required: ['reference_data'],
-      properties: {
-        reference_data: {
-          type: 'array',
-          maxItems: 500,
-          minItems: 1,
-          items: { $ref: '#/definitions/reference' }
-        }
-      }
-    },
-    description: {
-      type: 'object',
-      required: ['description_data'],
-      properties: {
-        description_data: {
-          type: 'array',
-          minItems: 1,
-          items: { $ref: '#/definitions/lang_string' }
-        }
-      }
-    }
-  }
-}
-
 const publicSchema = {
   $schema: 'http://json-schema.org/draft-07/schema#',
   $id: 'https://cve.mitre.org/cve/v5_00/',
@@ -1197,7 +1047,6 @@ const rejectSchema = {
 }
 
 module.exports = {
-  cveSchema4,
   publicSchema,
   reservedSchema,
   rejectSchema
diff --git a/src/middleware/middleware.js b/src/middleware/middleware.js
index 301a6b65..16eaebaa 100644
--- a/src/middleware/middleware.js
+++ b/src/middleware/middleware.js
@@ -1,5 +1,6 @@
 const CONSTANTS = require('../constants')
-const cveSchema = require('./jsonSchema')
+const cveSchemaV4 = require('./jsonSchemaV4')
+const cveSchemaV5 = require('./jsonSchemaV5')
 const argon2 = require('argon2')
 const logger = require('./logger')
 const Validator = require('jsonschema').Validator
@@ -153,57 +154,65 @@ async function onlyCnas (req, res, next) {
   }
 }
 
-// TODO: Request body sanitation, better user messages, and use correct status codes
 function validateCveJsonSchema (req, res, next) {
-  if (req.method.toUpperCase() === 'POST') {
-    const cve = req.body
-    const cveState = cve.CVE_data_meta.STATE
-    let schema
-    logger.info({ uuid: req.ctx.uuid, message: 'Validating CVE JSON schema.' })
-
-    // For validating schema 4.0
-    // if (cveState === 'PUBLIC' || cveState === 'RESERVED' || cveState === 'REJECT') {
-    //   schema = cveSchema.cveSchema4
-    // } else {
-    //   return res.status(400).json({ message: 'CVE JSON schema validation FAILED.', errors: ['instance.CVE_data_meta.STATE is not one of enum values'] })
-    // }
-
-    // For validating schema 5.0
-    if (cveState === 'PUBLIC') {
-      schema = cveSchema.publicSchema
+  const cve = req.body
+  const cveVersion = cve.data_version
+  let cveState = cve.CVE_data_meta
+  let schema
+  logger.info({ uuid: req.ctx.uuid, message: 'Validating CVE JSON schema.' })
+
+  if (cveState) {
+    cveState = cveState.STATE
+  }
+
+  if (cveVersion === '4.0') {
+    if (cveState === CONSTANTS.CVE_STATES.PUBLIC) {
+      schema = cveSchemaV4.publicSchema
+    } else if (cveState === CONSTANTS.CVE_STATES.RESERVED) {
+      schema = cveSchemaV4.reservedSchema
+    } else if (cveState === CONSTANTS.CVE_STATES.REJECT) {
+      schema = cveSchemaV4.rejectSchema
+    } else {
+      logger.error(JSON.stringify({ uuid: req.ctx.uuid, message: 'CVE JSON schema validation FAILED.' }))
+      return res.status(400).json(error.invalidJsonSchema(['instance.CVE_data_meta.STATE is not one of enum values']))
+    }
+  } else if (cveVersion === '5.0') {
+    if (cveState === CONSTANTS.CVE_STATES.PUBLIC) {
+      schema = cveSchemaV5.publicSchema
       v.addSchema(schema, '/https://www.first.org/cvss/cvss-v3.1.json?20190610')
       v.addSchema(schema, '/https://www.first.org/cvss/cvss-v3.0.json?20170531')
       v.addSchema(schema, '/https://www.first.org/cvss/cvss-v2.0.json?20170531')
-    } else if (cveState === 'RESERVED') {
-      schema = cveSchema.reservedSchema
-    } else if (cveState === 'REJECT') {
-      schema = cveSchema.rejectSchema
+    } else if (cveState === CONSTANTS.CVE_STATES.RESERVED) {
+      schema = cveSchemaV5.reservedSchema
+    } else if (cveState === CONSTANTS.CVE_STATES.REJECT) {
+      schema = cveSchemaV5.rejectSchema
     } else {
-      return res.status(400).json({ message: 'CVE JSON schema validation FAILED.', errors: ['instance.CVE_data_meta.STATE is not one of enum values'] })
-    }
-
-    const result = v.validate(cve, schema, { nestedErrors: true })
-
-    if (result.valid) {
-      logger.info({ uuid: req.ctx.uuid, message: 'SUCCESSFUL CVE JSON schema validation.' })
-      next()
-    } else {
-      logger.error(JSON.stringify(result))
       logger.error(JSON.stringify({ uuid: req.ctx.uuid, message: 'CVE JSON schema validation FAILED.' }))
+      return res.status(400).json(error.invalidJsonSchema(['instance.CVE_data_meta.STATE is not one of enum values']))
+    }
+  } else {
+    logger.error(JSON.stringify({ uuid: req.ctx.uuid, message: 'CVE JSON schema validation FAILED.' }))
+    return res.status(400).json(error.invalidJsonSchema(['instance.data_version is not one of enum values']))
+  }
 
-      const temp = result.toString().split('\n')
-      const errors = []
-      temp.forEach((error) => {
-        if (error !== '') {
-          const array = error.split(':', 2)
-          errors.push(array[1].trim())
-        }
-      })
+  const result = v.validate(cve, schema, { nestedErrors: true })
 
-      return res.status(400).json({ message: 'CVE JSON schema validation FAILED.', errors: errors })
-    }
+  if (result.valid) {
+    logger.info(JSON.stringify({ uuid: req.ctx.uuid, message: 'SUCCESSFUL CVE JSON schema validation.' }))
+    next()
   } else {
-    return res.status(400).send('Bad Request')
+    logger.error(JSON.stringify({ uuid: req.ctx.uuid, message: 'CVE JSON schema validation FAILED.' }))
+
+    const temp = result.toString().split('\n')
+    const errors = []
+    temp.forEach((error) => {
+      if (error !== '') {
+        const array = error.split(':', 2)
+        errors.push(array[1].trim())
+      }
+    })
+
+    return res.status(400).json(error.invalidJsonSchema(errors))
   }
 }
 
diff --git a/src/model/cve.js b/src/model/cve.js
index 59ec1275..5c563993 100644
--- a/src/model/cve.js
+++ b/src/model/cve.js
@@ -1,16 +1,15 @@
 const mongoose = require('mongoose')
 
 const CveSchema = new mongoose.Schema({
-  _id: false,
-  cve: Object,
   time: {
     created: Date,
     modified: Date
-  }
+  },
+  cve: Object
 }, { collection: 'Cve', timestamps: { createdAt: 'time.created', updatedAt: 'time.modified' } })
 
 CveSchema.query.byCveId = function (cveId) {
-  return this.where({ 'cve.CVE_data_meta.ID': new RegExp('^' + cveId + '$', 'i') }) // match cveId exactly (case insensitive)
+  return this.where({ 'cve.CVE_data_meta.ID': cveId })
 }
 
 const Cve = mongoose.model('Cve', CveSchema)


Test patch:
diff --git a/test-utils/index.js b/test-utils/index.js
index 6c54b85d..f83ea174 100644
--- a/test-utils/index.js
+++ b/test-utils/index.js
@@ -1422,6 +1422,12 @@ app.route('/api/test/mw/schema5')
     return res.status(200).json({ message: 'Success! You have reached the target endpoint.' })
   })
 
+// validate json schema 4.0
+app.route('/api/test/mw/schema4')
+  .post(middleware.validateCveJsonSchema, (req, res) => {
+    return res.status(200).json({ message: 'Success! You have reached the target endpoint.' })
+  })
+
 //* ********API Routes Setup END*********** */
 
 module.exports = app
diff --git a/test/schemas/4.0/CVE-2017-5832_reject_fail.json b/test/schemas/4.0/CVE-2017-5832_reject_fail.json
index ce6dc088..e5e9c70d 100644
--- a/test/schemas/4.0/CVE-2017-5832_reject_fail.json
+++ b/test/schemas/4.0/CVE-2017-5832_reject_fail.json
@@ -10,7 +10,6 @@
     "description": {
         "description_data": [
             {
-                "lang": "eng",
                 "value": "** REJECT ** DO NOT USE THIS CANDIDATE NUMBER.  ConsultIDs: none.  Reason: This candidate was withdrawn by its CNA.  Further investigation showed that it was not a security issue.  Notes: none."
             }
         ]
diff --git a/test/schemas/4.0/CVE-2017-5832_reserved_fail.json b/test/schemas/4.0/CVE-2017-5832_reserved_fail.json
index a7eeebce..5d662cec 100644
--- a/test/schemas/4.0/CVE-2017-5832_reserved_fail.json
+++ b/test/schemas/4.0/CVE-2017-5832_reserved_fail.json
@@ -8,11 +8,6 @@
     "data_type": "CVE",
     "data_version": "4.0",
     "description": {
-        "description_data": [
-            {
-                "lang": "eng",
-                "value": "** RESERVED ** This candidate has been reserved by an organization or individual that will use it when announcing a new security problem. When the candidate has been publicized, the details for this candidate will be provided."
-            }
-        ]
+        "description_data": []
     }
 } 
diff --git a/test/unit-tests/middleware/middlewareTest.js b/test/unit-tests/middleware/middlewareTest.js
index bf292d69..133ab5fc 100644
--- a/test/unit-tests/middleware/middlewareTest.js
+++ b/test/unit-tests/middleware/middlewareTest.js
@@ -8,14 +8,20 @@ const error = new errors.MiddlewareError()
 const CONSTANTS = require('../../../src/constants')
 const cveId5 = 'CVE-2017-4024'
 const cvePass5 = require('../../schemas/5.0/' + cveId5 + '.json')
-const cvefail5 = require('../../schemas/5.0/' + cveId5 + '_fail.json')
+const cveFail5 = require('../../schemas/5.0/' + cveId5 + '_fail.json')
 const cveRejectFail5 = require('../../schemas/5.0/' + cveId5 + '_reject_fail.json')
 const cveReservedFail5 = require('../../schemas/5.0/' + cveId5 + '_reserved_fail.json')
 const cvePublicFail5 = require('../../schemas/5.0/' + cveId5 + '_public_fail.json')
+const cveId4 = 'CVE-2017-5832'
+const cvePass4 = require('../../schemas/4.0/' + cveId4 + '.json')
+const cveFail4 = require('../../schemas/4.0/' + cveId4 + '_fail.json')
+const cveRejectFail4 = require('../../schemas/4.0/' + cveId4 + '_reject_fail.json')
+const cveReservedFail4 = require('../../schemas/4.0/' + cveId4 + '_reserved_fail.json')
+const cvePublicFail4 = require('../../schemas/4.0/' + cveId4 + '_public_fail.json')
 
 describe('Test for middleware', () => {
-  context('Validate json schema 5.0', () => {
-    it('Json is successfully validated', (done) => {
+  context('Validate JSON schema 5.0', () => {
+    it('Json validator pass', (done) => {
       chai.request(server)
         .post('/api/test/mw/schema5')
         .set(secretariatHeaders)
@@ -33,11 +39,11 @@ describe('Test for middleware', () => {
         })
     })
 
-    it('Using an invalid STATE', (done) => {
+    it('Json validator fails because the STATE is invalid', (done) => {
       chai.request(server)
         .post('/api/test/mw/schema5')
         .set(secretariatHeaders)
-        .send(cvefail5)
+        .send(cveFail5)
         .end((err, res) => {
           if (err) {
             done(err)
@@ -47,16 +53,15 @@ describe('Test for middleware', () => {
           expect(res).to.have.property('body').and.to.be.a('object')
           expect(res.body).to.have.property('message').and.to.be.a('string')
           expect(res.body.message).to.equal('CVE JSON schema validation FAILED.')
-          expect(res.body).to.have.property('errors').and.to.be.an('array')
-          expect(res.body.errors[0]).to.have.string('CVE_data_meta.STATE is not one of enum values')
+          expect(res.body.details).to.have.property('errors').and.to.be.an('array')
+          expect(res.body.details.errors[0]).to.have.string('CVE_data_meta.STATE is not one of enum values')
           done()
         })
     })
 
-    it('Using a method other than POST or GET', (done) => {
-      // create a request to the api
+    it('Json validator fails in REJECT state', (done) => {
       chai.request(server)
-        .put('/api/test/mw/schema5')
+        .post('/api/test/mw/schema5')
         .set(secretariatHeaders)
         .send(cveRejectFail5)
         .end((err, res) => {
@@ -64,16 +69,21 @@ describe('Test for middleware', () => {
             done(err)
           }
 
-          expect(res).to.have.property('status', 404)
+          expect(res).to.have.property('status', 400)
+          expect(res).to.have.property('body').and.to.be.a('object')
+          expect(res.body).to.have.property('message').and.to.be.a('string')
+          expect(res.body.message).to.equal('CVE JSON schema validation FAILED.')
+          expect(res.body.details).to.have.property('errors').to.be.an('array')
+          expect(res.body.details.errors[0]).to.have.string('instance.descriptions[0] requires property "lang"')
           done()
         })
     })
 
-    it('Json with reject state', (done) => {
+    it('Json validator fails in RESERVED state', (done) => {
       chai.request(server)
         .post('/api/test/mw/schema5')
         .set(secretariatHeaders)
-        .send(cveRejectFail5)
+        .send(cveReservedFail5)
         .end((err, res) => {
           if (err) {
             done(err)
@@ -83,17 +93,82 @@ describe('Test for middleware', () => {
           expect(res).to.have.property('body').and.to.be.a('object')
           expect(res.body).to.have.property('message').and.to.be.a('string')
           expect(res.body.message).to.equal('CVE JSON schema validation FAILED.')
-          expect(res.body).to.have.property('errors').to.be.an('array')
-          expect(res.body.errors[0]).to.have.string('instance.descriptions[0] requires property "lang"')
+          expect(res.body.details).to.have.property('errors').to.be.an('array')
+          expect(res.body.details.errors[0]).to.have.string('value does not meet minimum length of 1')
+          expect(res.body.details.errors[1]).to.have.string('instance.descriptions[0] requires property "lang"')
           done()
         })
     })
 
-    it('Json with reserved state', (done) => {
+    it('Json validator fails in PUBLIC state', (done) => {
       chai.request(server)
         .post('/api/test/mw/schema5')
         .set(secretariatHeaders)
-        .send(cveReservedFail5)
+        .send(cvePublicFail5)
+        .end((err, res) => {
+          if (err) {
+            done(err)
+          }
+
+          expect(res).to.have.status(400)
+          expect(res).to.have.property('body').and.to.be.a('object')
+          expect(res.body).to.have.property('message').and.to.be.a('string')
+          expect(res.body.message).to.equal('CVE JSON schema validation FAILED.')
+          expect(res.body.details).to.have.property('errors').and.to.be.an('array')
+          expect(res.body.details.errors[0]).to.have.string('CNA.provider_data_meta.ID is not of a type(s) string')
+          expect(res.body.details.errors[1]).to.have.string('CNA.affected.vendors[0].products[0].versions[0].version_affected is not one of enum values')
+          expect(res.body.details.errors[2]).to.have.string('CNA.affected.affects_SWID does not meet minimum length of 1')
+          expect(res.body.details.errors[3]).to.have.string('CNA.problemtypes contains duplicate item')
+          expect(res.body.details.errors[4]).to.have.string('additionalProperty "additional" exists in instance when not allowed')
+          done()
+        })
+    })
+  })
+
+  context('Validate JSON schema 4.0', () => {
+    it('Json validator pass', (done) => {
+      chai.request(server)
+        .post('/api/test/mw/schema4')
+        .set(secretariatHeaders)
+        .send(cvePass4)
+        .end((err, res) => {
+          if (err) {
+            done(err)
+          }
+
+          expect(res).to.have.status(200)
+          expect(res).to.have.property('body').and.to.be.a('object')
+          expect(res.body).to.have.property('message').and.to.be.a('string')
+          expect(res.body.message).to.equal('Success! You have reached the target endpoint.')
+          done()
+        })
+    })
+
+    it('Json validator fails because the STATE is invalid', (done) => {
+      chai.request(server)
+        .post('/api/test/mw/schema4')
+        .set(secretariatHeaders)
+        .send(cveFail4)
+        .end((err, res) => {
+          if (err) {
+            done(err)
+          }
+
+          expect(res).to.have.status(400)
+          expect(res).to.have.property('body').and.to.be.a('object')
+          expect(res.body).to.have.property('message').and.to.be.a('string')
+          expect(res.body.message).to.equal('CVE JSON schema validation FAILED.')
+          expect(res.body.details).to.have.property('errors').and.to.be.an('array')
+          expect(res.body.details.errors[0]).to.have.string('CVE_data_meta.STATE is not one of enum values')
+          done()
+        })
+    })
+
+    it('Json validator fails in REJECT state', (done) => {
+      chai.request(server)
+        .post('/api/test/mw/schema4')
+        .set(secretariatHeaders)
+        .send(cveRejectFail4)
         .end((err, res) => {
           if (err) {
             done(err)
@@ -103,18 +178,37 @@ describe('Test for middleware', () => {
           expect(res).to.have.property('body').and.to.be.a('object')
           expect(res.body).to.have.property('message').and.to.be.a('string')
           expect(res.body.message).to.equal('CVE JSON schema validation FAILED.')
-          expect(res.body).to.have.property('errors').to.be.an('array')
-          expect(res.body.errors[0]).to.have.string('value does not meet minimum length of 1')
-          expect(res.body.errors[1]).to.have.string('instance.descriptions[0] requires property "lang"')
+          expect(res.body.details).to.have.property('errors').to.be.an('array')
+          expect(res.body.details.errors[0]).to.have.string('instance.description.description_data[0] requires property "lang"')
           done()
         })
     })
 
-    it('Json with public state', (done) => {
+    it('Json validator fails in RESERVED state', (done) => {
       chai.request(server)
-        .post('/api/test/mw/schema5')
+        .post('/api/test/mw/schema4')
         .set(secretariatHeaders)
-        .send(cvePublicFail5)
+        .send(cveReservedFail4)
+        .end((err, res) => {
+          if (err) {
+            done(err)
+          }
+
+          expect(res).to.have.property('status', 400)
+          expect(res).to.have.property('body').and.to.be.a('object')
+          expect(res.body).to.have.property('message').and.to.be.a('string')
+          expect(res.body.message).to.equal('CVE JSON schema validation FAILED.')
+          expect(res.body.details).to.have.property('errors').to.be.an('array')
+          expect(res.body.details.errors[0]).to.have.string('instance.description.description_data does not meet minimum length of 1')
+          done()
+        })
+    })
+
+    it('Json validator fails in PUBLIC state', (done) => {
+      chai.request(server)
+        .post('/api/test/mw/schema4')
+        .set(secretariatHeaders)
+        .send(cvePublicFail4)
         .end((err, res) => {
           if (err) {
             done(err)
@@ -124,12 +218,11 @@ describe('Test for middleware', () => {
           expect(res).to.have.property('body').and.to.be.a('object')
           expect(res.body).to.have.property('message').and.to.be.a('string')
           expect(res.body.message).to.equal('CVE JSON schema validation FAILED.')
-          expect(res.body).to.have.property('errors').and.to.be.an('array')
-          expect(res.body.errors[0]).to.have.string('CNA.provider_data_meta.ID is not of a type(s) string')
-          expect(res.body.errors[1]).to.have.string('CNA.affected.vendors[0].products[0].versions[0].version_affected is not one of enum values')
-          expect(res.body.errors[2]).to.have.string('CNA.affected.affects_SWID does not meet minimum length of 1')
-          expect(res.body.errors[3]).to.have.string('CNA.problemtypes contains duplicate item')
-          expect(res.body.errors[4]).to.have.string('additionalProperty "additional" exists in instance when not allowed')
+          expect(res.body.details).to.have.property('errors').and.to.be.an('array')
+          expect(res.body.details.errors[0]).to.have.string('instance.affects.vendor.vendor_data[0].product.product_data[0].version.version_data[0] requires property "version_value"')
+          expect(res.body.details.errors[1]).to.have.string('instance.affects.vendor.vendor_data[0].product.product_data[1] requires property "version"')
+          expect(res.body.details.errors[2]).to.have.string('instance.problemtype.problemtype_data[0].description[0] requires property "lang"')
+          expect(res.body.details.errors[3]).to.have.string('instance.description.description_data is not of a type(s) array')
           done()
         })
     })