Skip to content

Commit

Permalink
add ADP implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
ElectricNroff authored and jdaigneau5 committed May 9, 2023
1 parent b955ace commit 4f7d03d
Show file tree
Hide file tree
Showing 8 changed files with 278 additions and 3 deletions.
23 changes: 23 additions & 0 deletions schemas/cve/adp-minimum-request.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"$schema": "http://json-schema.org/draft-04/schema",
"type": "object",
"required":["adpContainer"],
"properties": {
"adpContainer": {
"type": "object",
"required": [
"providerMetadata"
],
"properties": {
"providerMetadata": {
"type": "object",
"properties": {
"orgId": {
"type": "string"
}
}
}
}
}
}
}
110 changes: 109 additions & 1 deletion src/controller/cve.controller/cve.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,113 @@ async function rejectExistingCve (req, res, next) {
}
}

// Called by PUT /cve/:id/adp
async function insertAdp (req, res, next) {
const CONSTANTS = getConstants()

try {
const id = req.ctx.params.id
const cveRepo = req.ctx.repositories.getCveRepository()
const cveIdRepo = req.ctx.repositories.getCveIdRepository()
const orgRepo = req.ctx.repositories.getOrgRepository()
const userRepo = req.ctx.repositories.getUserRepository()
const orgUuid = await orgRepo.getOrgUUID(req.ctx.org)
const userUuid = await userRepo.getUserUUID(req.ctx.user, orgUuid)

// check that cve id exists
let result = await cveIdRepo.findOneByCveId(id)
if (!result || result.state === CONSTANTS.CVE_STATES.AVAILABLE) {
return res.status(400).json(error.cveDne())
}

// check that cve record does exist
result = await cveRepo.findOneByCveId(id)
if (!result) {
return res.status(403).json(error.cveRecordDne())
}

// update cve record here
const cveRecord = result.cve

if (cveRecord.cveMetadata.state === CONSTANTS.CVE_STATES.REJECTED) {
return res.status(403).json(error.cveRecordRejected())
}

if (orgUuid === cveRecord.containers.cna.providerMetadata.orgId) {
return res.status(403).json(error.cveRecordCnaContainerConflict())
}

if (! req.ctx.body.hasOwnProperty('adpContainer')) {
return res.status(400).json(error.badAdpFormat())
}

const adpContainer = req.ctx.body.adpContainer
const dateUpdated = (new Date()).toISOString()
cveRecord.cveMetadata.dateUpdated = dateUpdated

const providerMetadata = createProviderMetadata(orgUuid, req.ctx.org, dateUpdated)
adpContainer.providerMetadata = providerMetadata
let adpCount = 0
let dupeFound = 0
let dupeIndex = -1
let dupeStatus = 'new'

if (cveRecord.containers.hasOwnProperty('adp')) {
cveRecord.containers.adp.forEach(function (item, index) {
if (orgUuid === item.providerMetadata.orgId) {
dupeFound = 1
dupeIndex = index
dupeStatus = 'replacement'
}
})

adpCount = cveRecord.containers.adp.length + 1
if (dupeFound === 1) {
adpCount = adpCount - 1
}
logger.info('Number of ADP containers already: ' + cveRecord.containers.adp.length)
} else {
logger.info('There were previously zero ADP containers.')
adpCount = 1
cveRecord.containers.adp = []
}

if (dupeFound === 1) {
cveRecord.containers.adp[dupeIndex] = adpContainer
} else {
cveRecord.containers.adp.push(adpContainer)
}

const cveModel = new Cve({ cve: cveRecord })
result = Cve.validateCveRecord(cveModel.cve)
if (!result.isValid) {
logger.error(JSON.stringify({ uuid: req.ctx.uuid, message: 'CVE JSON schema validation FAILED.' }))
return res.status(400).json(error.badAdpJson(result.errors))
}

await cveRepo.updateByCveId(id, cveModel)

let outcome = id + ' record had ' + dupeStatus + ' ADP container ' + adpCount + ' successfully inserted. This submission should appear on ' + url + ' within 15 minutes.'
const responseMessage = {
message: outcome,
updated: cveModel.cve
}

const payload = {
action: 'update_cve_record_from_adp',
change: outcome,
req_UUID: req.ctx.uuid,
org_UUID: orgUuid,
user_UUID: userUuid,
cve: id
}
logger.info(JSON.stringify(payload))
return res.status(200).json(responseMessage)
} catch (err) {
next(err)
}
}

module.exports = {
CVE_GET_SINGLE: getCve,
CVE_GET_FILTERED: getFilteredCves,
Expand All @@ -588,5 +695,6 @@ module.exports = {
CVE_SUBMIT_CNA: submitCna,
CVE_UPDATE_CNA: updateCna,
CVE_REJECT_RECORD: rejectCVE,
CVE_REJECT_EXISTING_CVE: rejectExistingCve
CVE_REJECT_EXISTING_CVE: rejectExistingCve,
CVE_INSERT_ADP: insertAdp
}
8 changes: 8 additions & 0 deletions src/controller/cve.controller/cve.middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,11 +147,19 @@ function validateCveCnaContainerJsonSchema (req, res, next) {
next()
}

// 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.
function validateCveAdpContainerJsonSchema (req, res, next) {
next()
}

module.exports = {
parseGetParams,
parsePostParams,
parseError,
validateCveCnaContainerJsonSchema,
validateCveAdpContainerJsonSchema,
validateUniqueEnglishEntry,
hasSingleEnglishEntry,
validateDescription,
Expand Down
31 changes: 31 additions & 0 deletions src/controller/cve.controller/error.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,37 @@ class CveControllerError extends idrErr.IDRError {
err.message = `'${param}' is not a valid parameter.`
return err
}

cveRecordRejected () { // cve
const err = {}
err.error = 'CVE_RECORD_REJECTED'
err.message = 'The CVE Record for the CVE ID is in the REJECTED state and cannot have an ADP container.'
return err
}

cveRecordCnaContainerConflict () { // cve
const err = {}
err.error = 'CVE_RECORD_CNA_CONTAINER_CONFLICT'
err.message = 'The CVE Record already has a CNA container from the provider associated with the current request.'
return err
}

badAdpJson (errors) { // cve
const err = {}
err.error = 'BAD_ADP_JSON'
err.message = 'The ADP data does not comply with the JSON schema.'
err.details = {
errors: errors
}
return err
}

badAdpFormat () { // cve
const err = {}
err.error = 'BAD_ADP_FORMAT'
err.message = 'The ADP data does not begin with adpContainer.'
return err
}
}

module.exports = {
Expand Down
85 changes: 84 additions & 1 deletion src/controller/cve.controller/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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, validateRejectBody, validateUniqueEnglishEntry, validateDescription } = require('./cve.middleware')
const { parseGetParams, parsePostParams, parseError, validateCveCnaContainerJsonSchema, validateCveAdpContainerJsonSchema, validateRejectBody, validateUniqueEnglishEntry, validateDescription } = require('./cve.middleware')
const getConstants = require('../../constants').getConstants
const CONSTANTS = getConstants()
const CHOICES = [CONSTANTS.CVE_STATES.REJECTED, CONSTANTS.CVE_STATES.PUBLISHED]
Expand Down Expand Up @@ -680,4 +680,87 @@ router.put('/cve/:id/reject',
mw.cnaMustOwnID,
controller.CVE_REJECT_EXISTING_CVE)

router.put('/cve/:id/adp',
/*
#swagger.tags = ['CVE Record']
#swagger.operationId = 'cveAdpUpdateSingle'
#swagger.summary = "Updates the CVE Record from ADP Container JSON for the specified ID (accessible to ADPs and Secretariat)"
#swagger.description = "
<h2>Access Control</h2>
<p>User must belong to an organization with the <b>ADP</b> or <b>Secretariat</b> role</p>
<h2>Expected Behavior</h2>
<p><b>ADP:</b> Updates a CVE Record for records that are owned by any organization</p>
<p><b>Secretariat:</b> Updates a CVE Record for records that are owned by any organization</p>"
#swagger.parameters['id'] = { description: 'The CVE ID for which the record is being updated' }
#swagger.parameters['$ref'] = [
'#/components/parameters/apiEntityHeader',
'#/components/parameters/apiUserHeader',
'#/components/parameters/apiSecretHeader'
]
#swagger.requestBody = {
description: 'Note: providerMetadata is set by the server. If provided, it will be overwritten.',
required: true,
content: {
"application/json": {
schema:{ $ref: '/schemas/cve/adp-minimum-request.json' }
}
}
}
#swagger.responses[200] = {
description: 'The updated CVE Record',
content: {
"application/json": {
schema: { $ref: '/schemas/cve/update-full-cve-record-response.json' }
}
}
}
#swagger.responses[400] = {
description: 'Bad Request',
content: {
"application/json": {
schema: { $ref: '/schemas/errors/bad-request.json' }
}
}
}
#swagger.responses[401] = {
description: 'Not Authenticated',
content: {
"application/json": {
schema: { $ref: '/schemas/errors/generic.json' }
}
}
}
#swagger.responses[403] = {
description: 'Forbidden',
content: {
"application/json": {
schema: { $ref: '/schemas/errors/generic.json' }
}
}
}
#swagger.responses[404] = {
description: 'Not Found',
content: {
"application/json": {
schema: { $ref: '/schemas/errors/generic.json' }
}
}
}
#swagger.responses[500] = {
description: 'Internal Server Error',
content: {
"application/json": {
schema: { $ref: '/schemas/errors/generic.json' }
}
}
}
*/
mw.validateUser,
mw.onlyAdps,
validateCveAdpContainerJsonSchema,
param(['id']).isString().matches(CONSTANTS.CVE_ID_REGEX),
parseError,
parsePostParams,
controller.CVE_INSERT_ADP)

module.exports = router
1 change: 1 addition & 0 deletions src/controller/schemas.controller/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ router.get('/cve/list-cve-records-response.json', controller.getListCveRecordsRe
router.get('/cve/update-cve-record-response.json', controller.getUpdateCveRecordResponseSchema)
router.get('/cve/create-full-cve-record-request.json', controller.getFullCveRecordRequestSchema)
router.get('/cve/update-full-cve-record-response.json', controller.getFullUpdateCveRecordResponseSchema)
router.get('/cve/adp-minimum-request.json', controller.getAdpMinimumSchema)

// Schemas relating to CVE IDs
router.get('/cve-id/create-cve-ids-response.json', controller.getCreateCveIdsResponseSchema)
Expand Down
9 changes: 8 additions & 1 deletion src/controller/schemas.controller/schemas.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ async function getUpdateCveRecordResponseSchema (req, res) {
res.status(200)
}

async function getAdpMinimumSchema (req, res) {
const adpMinimumSchema = require('../../../schemas/cve/adp-minimum-request.json')
res.json(adpMinimumSchema)
res.status(200)
}

// Schemas relating to CVE IDs
async function getCreateCveIdsResponseSchema (req, res) {
const createCveIdsResponseSchema = require('../../../schemas/cve-id/create-cve-ids-response.json')
Expand Down Expand Up @@ -216,5 +222,6 @@ module.exports = {
getUserResponseSchema: getUserResponseSchema,
getResetSecretResponseSchema: getResetSecretResponseSchema,
getUpdateUserResponseSchema: getUpdateUserResponseSchema,
getUpdateCVEIdResponseSchema: getUpdateCVEIdResponseSchema
getUpdateCVEIdResponseSchema: getUpdateCVEIdResponseSchema,
getAdpMinimumSchema: getAdpMinimumSchema
}
14 changes: 14 additions & 0 deletions src/middleware/error.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,27 @@ class MiddlewareError extends idrErr.IDRError {
return err
}

adpOnly () { // mw
const err = {}
err.error = 'ADP_ONLY'
err.message = 'This function is only allowed for ADPs.'
return err
}

cnaDoesNotExist (shortname) { // mw
const err = {}
err.error = 'CNA_DOES_NOT_EXIST'
err.message = `The '${shortname}' organization designated by the shortname parameter does not exist.`
return err
}

adpDoesNotExist (shortname) { // mw
const err = {}
err.error = 'ADP_DOES_NOT_EXIST'
err.message = `The '${shortname}' organization designated by the shortname parameter does not exist.`
return err
}

orgDoesNotExist (shortname) { // mw
const err = {}
err.error = 'ORG_DOES_NOT_EXIST'
Expand Down

0 comments on commit 4f7d03d

Please sign in to comment.