text:
require('dotenv').config()
const CveId = require('../../model/cve-id')
const logger = require('../../middleware/logger')
const getConstants = require('../../constants').getConstants
const errors = require('./error')
const error = new errors.CveIdControllerError()

// Called by GET /api/cve-id
async function getFilteredCveId (req, res, next) {
  const CONSTANTS = getConstants()

  // temporary measure to allow tests to work after fixing #920
  // tests required changing the global limit to force pagination
  if (req.TEST_PAGINATOR_LIMIT) {
    CONSTANTS.PAGINATOR_OPTIONS.limit = req.TEST_PAGINATOR_LIMIT
  }

  const options = CONSTANTS.PAGINATOR_OPTIONS
  options.sort = { owning_cna: 'asc', cve_id: 'asc' }

  try {
    const orgShortName = req.ctx.org
    let state
    let year
    const timeReserved = {
      timeStamp: [],
      dateOperator: []
    }
    const timeModified = {
      timeStamp: [],
      dateOperator: []
    }
    options.page = req.ctx.query.page ? parseInt(req.ctx.query.page) : CONSTANTS.PAGINATOR_PAGE // if 'page' query parameter is not defined, set 'page' to the default page value
    const cveIdRepo = req.ctx.repositories.getCveIdRepository()
    const orgRepo = req.ctx.repositories.getOrgRepository()
    const userRepo = req.ctx.repositories.getUserRepository()
    const isSecretariat = await orgRepo.isSecretariat(orgShortName)
    const isBulkDownload = await orgRepo.isBulkDownload(orgShortName)

    // Create map of orgUUID to shortnames and users to simplify aggregation later
    const orgs = await orgRepo.getAllOrgs()
    const users = await userRepo.getAllUsers()

    const orgMap = {}
    orgs.forEach(org => {
      orgMap[org.UUID] = { shortname: org.short_name, users: {} }
    })

    users.forEach(user => {
      if (!orgMap[user.org_UUID]) {
        orgMap[user.org_UUID] = { shortname: `MISSING ORG ${user.org_UUID}`, users: {} }
      }
      orgMap[user.org_UUID].users[user.UUID] = user.username
    })

    Object.keys(req.ctx.query).forEach(k => {
      const key = k.toLowerCase()

      if (key === 'state') {
        state = req.ctx.query.state
      } else if (key === 'cve_id_year') {
        year = req.ctx.query.cve_id_year
      } else if (key === 'time_reserved.lt') {
        timeReserved.dateOperator.push('lt')
        timeReserved.timeStamp.push(req.ctx.query['time_reserved.lt'])
      } else if (key === 'time_reserved.gt') {
        timeReserved.dateOperator.push('gt')
        timeReserved.timeStamp.push(req.ctx.query['time_reserved.gt'])
      } else if (key === 'time_modified.lt') {
        timeModified.dateOperator.push('lt')
        timeModified.timeStamp.push(req.ctx.query['time_modified.lt'])
      } else if (key === 'time_modified.gt') {
        timeModified.dateOperator.push('gt')
        timeModified.timeStamp.push(req.ctx.query['time_modified.gt'])
      }
    })

    const query = {
      state: { $ne: CONSTANTS.CVE_STATES.AVAILABLE }
    }

    // Secretariat and BulkDownload get results for all CNAs
    if (!(isSecretariat || isBulkDownload)) {
      query.owning_cna = await orgRepo.getOrgUUID(orgShortName)
    }

    if (year) {
      query.cve_year = year
    }

    if (state) {
      query.state = state
    }

    if (timeReserved.timeStamp.length > 0) {
      query.reserved = {}

      for (let i = 0; i < timeReserved.timeStamp.length; i++) {
        if (timeReserved.dateOperator[i] === 'lt') {
          query.reserved.$lt = timeReserved.timeStamp[i]
        } else {
          query.reserved.$gt = timeReserved.timeStamp[i]
        }
      }
    }

    if (timeModified.timeStamp.length > 0) {
      query['time.modified'] = {}

      for (let i = 0; i < timeModified.timeStamp.length; i++) {
        if (timeModified.dateOperator[i] === 'lt') {
          query['time.modified'].$lt = timeModified.timeStamp[i]
        } else {
          query['time.modified'].$gt = timeModified.timeStamp[i]
        }
      }
    }

    const agt = setMinAggregateObj(query)
    const pg = await cveIdRepo.aggregatePaginate(agt, options)
    const payload = {
      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]
        i.owning_cna = orgMap[i.owning_cna].shortname
        return i
      })
    }

    if (pg.itemCount >= CONSTANTS.PAGINATOR_OPTIONS.limit) {
      payload.totalCount = pg.itemCount
      payload.itemsPerPage = pg.itemsPerPage
      payload.pageCount = pg.pageCount
      payload.currentPage = pg.currentPage
      payload.prevPage = pg.prevPage
      payload.nextPage = pg.nextPage
    }

    logger.info({ uuid: req.ctx.uuid, message: 'The CVEs were sent to the user.' })
    return res.status(200).json(payload)
  } catch (err) {
    next(err)
  }
}

// Called by POST /api/cve-id
async function reserveCveId (req, res, next) {
  const requester = req.ctx.user
  const orgShortName = req.ctx.org
  let hasLock = false
  let batchType
  let amount
  let shortName
  let year
  const orgRepo = req.ctx.repositories.getOrgRepository()

  try {
    Object.keys(req.ctx.query).forEach(k => {
      const key = k.toLowerCase()

      if (key === 'amount') {
        amount = req.ctx.query.amount
      } else if (key === 'batch_type') {
        batchType = req.ctx.query.batch_type
      } else if (key === 'short_name') {
        shortName = req.ctx.query.short_name
      } else if (key === 'cve_year') {
        year = req.ctx.query.cve_year
      }
    })

    const isSecretariat = await orgRepo.isSecretariat(orgShortName)
    if (orgShortName !== shortName && !isSecretariat) {
      return res.status(403).json(error.orgCannotReserveForOther())
    }

    if (!shortName) {
      return res.status(400).json(error.noOrgShortName())
    }

    if (!year) {
      return res.status(400).json(error.noYear())
    }

    if (!amount) {
      return res.status(400).json(error.noAmount())
    }

    if (amount <= 0) {
      return res.status(400).json(error.invalidAmount())
    }

    if (amount > 1 && !batchType) {
      return res.status(400).json(error.noBatchType())
    }

    const result = await orgRepo.findOneByShortName(shortName)
    if (!result) {
      logger.info({ uuid: req.ctx.uuid, message: shortName + ' organization does not exist.' })
      return res.status(403).json(error.orgDne(shortName, 'short_name', 'query'))
    }

    const payload = await getPayload(req, result)
    if (amount > payload.available) {
      return res.status(403).json(error.overIdQuota(payload))
    }

    hasLock = await orgRepo.findOneAndUpdate({ short_name: shortName, inUse: false }, { $set: { inUse: true } }, { new: true }) // set lock for org
    if (!hasLock) {
      return res.status(403).json(error.reservationInProgress())
    }

    if (batchType === undefined) { // priority
      await priorityReservation(year, amount, shortName, orgShortName, requester, payload.available, res, req)
    } else if (batchType === 'sequential') {
      await sequentialReservation(year, amount, shortName, orgShortName, requester, payload.available, false, res, req)
    } else if (batchType === 'non-sequential' || batchType === 'nonsequential') {
      if (amount > getConstants().NONSEQUENTIAL_MAX_AMOUNT) {
        return res.status(403).json(error.overNonSequentialMaxAmount())
      }
      await nonSequentialReservation(year, amount, shortName, orgShortName, requester, payload.available, res, req)
    } else {
      return res.status(400).json(error.invalidBatchType())
    }
  } catch (err) {
    next(err)
  } finally {
    if (shortName && hasLock) {
      await orgRepo.findOneAndUpdate({ short_name: shortName, inUse: true }, { $set: { inUse: false } }, { new: true }) // release lock for org
    }
  }
}

/* Expected behavior by role:
Secretariat: can retrieve full information for all CVE IDs
Regular, CNA & Admin Users: Retrieve full information for a CVE ID owned by their organization
Unauthenticated users along with Regular, CNA & Admin users requesting ids not owned by their organization retrieve
  partial information as follows:
    requested_by not included for a CVE ID in the “PUBLISHED” or “REJECTED” state
    owning_org not included for ids in the RESERVED state
*/
async function getCveId (req, res, next) {
  const CONSTANTS = getConstants()

  try {
    const auth = req.ctx.authenticated
    const id = req.ctx.params.id
    const cveIdRepo = req.ctx.repositories.getCveIdRepository()
    const orgRepo = req.ctx.repositories.getOrgRepository()

    const agt = setAggregateObj({ cve_id: id })
    let result = await cveIdRepo.aggregate(agt)
    result = result.length > 0 ? result[0] : null

    if (!result || result.state === CONSTANTS.CVE_STATES.AVAILABLE) {
      return res.status(404).json(error.cveIdNotFound(id))
    }

    let finalResult = {}
    let loggerUuid = 'unauthenticated-user'
    let orgShortName = ''
    let orgUUID = null
    let isSecretariat = false

    if (auth) {
      loggerUuid = req.ctx.uuid
      orgShortName = req.ctx.org
      orgUUID = await orgRepo.getOrgUUID(orgShortName) // orgShortName is not null
      isSecretariat = await orgRepo.isSecretariatUUID(orgUUID)
    }

    // Secretariat and owning org are allowed to see complete results
    if (isSecretariat || (orgShortName === result.owning_cna)) {
      finalResult = result
    } else { // otherwise, remove Requested by information, and redact owning_cna for RESERVED ids
      finalResult = {
        cve_id: result.cve_id,
        cve_year: result.cve_year,
        state: result.state
      }
      if (result.state === CONSTANTS.CVE_STATES.RESERVED) {
        finalResult.owning_cna = '[REDACTED]'
      } else {
        finalResult.owning_cna = result.owning_cna
        finalResult.dateUpdated = result.time.modified
      }
    }

    logger.info({ uuid: loggerUuid, message: id + ' record was sent to the user.', cveId: finalResult })
    return res.status(200).json(finalResult)
  } catch (err) {
    next(err)
  }
}

// Called by PUT /cve-id/:id
async function modifyCveId (req, res, next) {
  try {
    const id = req.ctx.params.id
    let state
    let newOrgShortName
    let orgUUID
    const orgRepo = req.ctx.repositories.getOrgRepository()
    const cveIdRepo = req.ctx.repositories.getCveIdRepository()
    const userRepo = req.ctx.repositories.getUserRepository()
    const cveRepo = req.ctx.repositories.getCveRepository()
    const org = await orgRepo.findOneByShortName(req.ctx.org)

    // Get remaining org quota
    const totalReserved = await cveIdRepo.countDocuments({ owning_cna: org.UUID, state: 'RESERVED' })
    const remainingQuota = (org.policies.id_quota - totalReserved)

    // Check for existing record - await only allowed at top level so cannot
    // move inside of it statement below
    const cve = await cveRepo.findOneByCveId(id)
    const queryKeys = Object.keys(req.ctx.query)
    // Object.keys(req.ctx.query).forEach(k => {
    for (let i = 0; i < queryKeys.length; i++) {
      const k = queryKeys[i]
      const key = k.toLowerCase()

      // Ok to change owning_cna if there is an existing record, but not state
      if (key === 'state') {
        if (cve) {
          return res.status(403).json(error.cannotChangeCveIdWithRecord(id))
        }
        state = req.ctx.query.state

        // Don't allow state transition to RESERVED if org has no remaining quota
        if (state === 'RESERVED' && remainingQuota <= 0) {
          return res.status(403).json(error.modifyCveIdNoQuota())
        }
      } else if (key === 'org') {
        newOrgShortName = req.ctx.query.org
      }
    }
    // })

    let result = await cveIdRepo.findOneByCveId(id)
    if (!result) {
      return res.status(404).json(error.cveIdNotFound(id))
    }

    const cveId = JSON.parse(JSON.stringify(result)) // clone cve id returned from the db
    delete cveId._id
    delete cveId.time

    if (state) {
      cveId.state = state
    }

    if (newOrgShortName) {
      orgUUID = await orgRepo.getOrgUUID(newOrgShortName)

      if (!orgUUID) {
        logger.info({ uuid: req.ctx.uuid, message: id + ' could not be reassigned to ' + newOrgShortName + ' in MongoDB because it does not exist.' })
        return res.status(404).json(error.orgDne(newOrgShortName, 'org', 'query'))
      }

      cveId.owning_cna = orgUUID
    }

    await cveIdRepo.findOneAndUpdate({ cve_id: id }, cveId) // update cve id
    const agt = setAggregateObj({ cve_id: id })
    result = await cveIdRepo.aggregate(agt)
    result = result.length > 0 ? result[0] : null

    const responseMessage = {
      message: id + ' was successfully updated.',
      updated: result
    }

    const payload = {
      action: 'update_cveid',
      change: id + ' was successfully updated.',
      req_UUID: req.ctx.uuid,
      org_UUID: await orgRepo.getOrgUUID(req.ctx.org),
      cve_id: result
    }
    payload.user_UUID = await userRepo.getUserUUID(req.ctx.user, payload.org_UUID)
    logger.info(JSON.stringify(payload))
    return res.status(200).json(responseMessage)
  } catch (err) {
    next(err)
  }
}

// Called by POST /cve-id-range/:year
async function createCveIdRange (req, res, next) {
  try {
    const CONSTANTS = getConstants()
    const year = req.ctx.params.year
    const cveIdRangeRepo = req.ctx.repositories.getCveIdRangeRepository()
    const orgRepo = req.ctx.repositories.getOrgRepository()
    const userRepo = req.ctx.repositories.getUserRepository()
    const result = await cveIdRangeRepo.findOne({ cve_year: year })

    if (result) {
      logger.info({ uuid: req.ctx.uuid, message: 'CVE Id Range document for year ' + year + ' already exists.' })
      return res.status(400).json(error.yearRangeExists(year))
    }

    const defaultDoc = CONSTANTS.DEFAULT_CVE_ID_RANGE
    defaultDoc.cve_year = year
    await cveIdRangeRepo.findOneAndUpdate({ cve_year: year }, defaultDoc, { upsert: true })
    const payload = {
      action: 'create_cveIdRange',
      change: 'CVE Id Range document for year ' + year + ' was created.',
      req_UUID: req.ctx.uuid,
      org_UUID: await orgRepo.getOrgUUID(req.ctx.org)
    }
    payload.user_UUID = await userRepo.getUserUUID(req.ctx.user, payload.org_UUID)

    logger.info(JSON.stringify(payload))
    return res.status(200).send()
  } catch (err) {
    next(err)
  }
}

async function priorityReservation (year, amount, shortName, orgShortName, requester, availableIds, res, req) {
  const CONSTANTS = getConstants()
  const cveIdRangeRepo = req.ctx.repositories.getCveIdRangeRepository()
  const reqUUID = req.ctx.uuid
  let result = await cveIdRangeRepo.findOne({ cve_year: year })
  let isFull = false

  // Cve Id Range for 'year' does not exists
  if (!result) {
    logger.info({ uuid: req.ctx.uuid, message: 'CVE IDs for year ' + year + ' cannot be reserved at this time.' })
    res.header(CONSTANTS.QUOTA_HEADER, availableIds)
    return res.status(403).json(error.cannotReserveForYear(year))
  }

  const endRange = parseInt(result.ranges.priority.end)
  result = await cveIdRangeRepo.findOneAndUpdate({ $and: [{ cve_year: year }, { 'ranges.priority.top_id': { $lt: endRange } }] }, { $inc: { 'ranges.priority.top_id': amount } }, { new: true })

  // priority id block is full, reserve id in sequential block
  if (!result) {
    isFull = true
    logger.info({ uuid: req.ctx.uuid, message: 'Priority id block is full for year ' + year + ', reserving id in sequential block.' })
    await sequentialReservation(year, amount, shortName, orgShortName, requester, availableIds, true, res, req)
  }

  if (!isFull) {
    const orgRepo = req.ctx.repositories.getOrgRepository()
    const userRepo = req.ctx.repositories.getUserRepository()
    const cveIdRepo = req.ctx.repositories.getCveIdRepository()
    const id = generateSequentialIds(year, result.ranges.priority.top_id, amount)
    const owningOrgUUID = await orgRepo.getOrgUUID(shortName)
    const orgUUID = await orgRepo.getOrgUUID(orgShortName)
    const requesterUUID = (await userRepo.findOneByUserNameAndOrgUUID(requester, orgUUID)).UUID

    let cveIdDocuments = []
    const cveIdDocumentsUUID = []
    const cveIdUUID = new CveId()
    const cveId = new CveId()

    cveIdUUID.cve_id = cveId.cve_id = id[0]
    cveIdUUID.cve_year = cveId.cve_year = year
    cveIdUUID.state = cveId.state = 'RESERVED'
    cveIdUUID.owning_cna = owningOrgUUID // the org who gets assigned the reserved CVE IDs
    cveId.owning_cna = shortName
    cveIdUUID.reserved = cveId.reserved = Date.now()
    cveIdUUID.requested_by = {
      cna: orgUUID, // the org who requested the CVE IDs
      user: requesterUUID
    }
    cveId.requested_by = {
      cna: orgShortName, // the org who requested the CVE IDs
      user: requester
    }

    cveIdDocumentsUUID.push(cveIdUUID) // list of cve ids with UUIDs
    cveIdDocuments.push(cveId) // list of cve ids with short name and username

    // Create a CveId document (reserve the CVE ID)
    await cveIdRepo.insertMany(cveIdDocumentsUUID) // reserve a cve id
    cveIdDocuments = cleanOutputSequential(cveIdDocuments)
    const payload = {
      action: 'priority-reservation',
      change: 'A priority CVE ID was reserved for \'' + shortName + '\' organization on behalf of \'' + orgShortName + '\' organization.',
      req_UUID: reqUUID,
      org_UUID: orgUUID,
      user_UUID: requesterUUID,
      amount: amount,
      start: cveIdDocumentsUUID[0].cve_id,
      stop: cveIdDocumentsUUID[cveIdDocumentsUUID.length - 1].cve_id
    }
    logger.info(JSON.stringify(payload))
    const q = availableIds - amount
    res.header(CONSTANTS.QUOTA_HEADER, q)
    return res.status(200).json({ cve_ids: cveIdDocuments, meta: { remaining_quota: q } })
  }
}

async function sequentialReservation (year, amount, shortName, orgShortName, requester, availableIds, isPriority, res, req) {
  const CONSTANTS = getConstants()
  const cveIdRangeRepo = req.ctx.repositories.getCveIdRangeRepository()
  const reqUUID = req.ctx.uuid
  let result = await cveIdRangeRepo.findOne({ cve_year: year })
  let isFull = false

  // Cve Id Range for 'year' does not exists
  if (!result) {
    logger.info({ uuid: req.ctx.uuid, message: 'CVE IDs for year ' + year + ' cannot be reserved at this time.' })
    res.header(CONSTANTS.QUOTA_HEADER, availableIds)
    return res.status(403).json(error.cannotReserveForYear(year))
  }

  const topId = parseInt(result.ranges.general.top_id)
  amount = parseInt(amount)
  result = await cveIdRangeRepo.findOneAndUpdate({ $and: [{ cve_year: year }, { 'ranges.general.end': { $gt: (topId + amount - 1) } }] }, { $inc: { 'ranges.general.top_id': amount } }, { new: true })
  // The cve id block is full for sequential CVE IDs
  if (!result) {
    isFull = true

    if (isPriority) {
      logger.error(JSON.stringify({ message: 'The cve id priority and sequential blocks are full for year ' + year + '. No more priority or sequential ids can be reserved at this time.' }))
    } else {
      logger.error(JSON.stringify({ message: 'The cve id sequential block is full for year ' + year + '. No more sequential ids can be reserved at this time.' }))
    }

    res.header(CONSTANTS.QUOTA_HEADER, availableIds)
    return res.status(403).json(error.yearRangeFull(year))
  }

  if (!isFull) {
    const orgRepo = req.ctx.repositories.getOrgRepository()
    const userRepo = req.ctx.repositories.getUserRepository()
    const cveIdRepo = req.ctx.repositories.getCveIdRepository()
    const ids = generateSequentialIds(year, result.ranges.general.top_id, amount)
    const owningOrgUUID = await orgRepo.getOrgUUID(shortName)
    const orgUUID = await orgRepo.getOrgUUID(orgShortName)
    const requesterUUID = (await userRepo.findOneByUserNameAndOrgUUID(requester, orgUUID)).UUID
    let cveIdDocuments = []
    const cveIdDocumentsUUID = []
    let cveIdUUID
    let cveId

    ids.forEach(id => {
      cveIdUUID = new CveId()
      cveId = new CveId()
      cveIdUUID.cve_id = cveId.cve_id = id
      cveIdUUID.cve_year = cveId.cve_year = year
      cveIdUUID.state = cveId.state = 'RESERVED'
      cveIdUUID.owning_cna = owningOrgUUID // the org who gets assigned the reserved CVE IDs
      cveId.owning_cna = shortName
      cveIdUUID.reserved = cveId.reserved = Date.now()
      cveIdUUID.requested_by = {
        cna: orgUUID, // the org who requested the CVE IDs
        user: requesterUUID
      }
      cveId.requested_by = {
        cna: orgShortName, // the org who requested the CVE IDs
        user: requester
      }

      cveIdDocumentsUUID.push(cveIdUUID) // list of cve ids with UUIDs
      cveIdDocuments.push(cveId) // list of cve ids with short name and username
    })

    // Create multiple CveId documents (reserve the CVE IDs)
    await cveIdRepo.insertMany(cveIdDocumentsUUID) // reserve cve ids
    cveIdDocuments = cleanOutputSequential(cveIdDocuments)
    const payload = {
      action: 'sequential-reservation',
      req_UUID: reqUUID,
      org_UUID: orgUUID,
      user_UUID: requesterUUID,
      amount: amount,
      start: cveIdDocumentsUUID[0].cve_id,
      stop: cveIdDocumentsUUID[cveIdDocumentsUUID.length - 1].cve_id
    }

    if (isPriority) {
      payload.change = 'The cve id priority block is full for year ' + year + '. A sequential CVE ID was reserved for \'' + shortName + '\' organization on behalf of \'' + orgShortName + '\' organization.'
    } else {
      payload.change = 'Sequential CVE IDs were reserved for \'' + shortName + '\' organization on behalf of \'' + orgShortName + '\' organization.'
    }

    logger.info(JSON.stringify(payload))
    const q = availableIds - amount
    res.header(CONSTANTS.QUOTA_HEADER, q)
    return res.status(200).json({ cve_ids: cveIdDocuments, meta: { remaining_quota: q } })
  }
}

async function nonSequentialReservation (year, amount, shortName, orgShortName, requester, availableIds, res, req) {
  const CONSTANTS = getConstants()
  const cveIdRepo = req.ctx.repositories.getCveIdRepository()
  const cveIdRangeRepo = req.ctx.repositories.getCveIdRangeRepository()
  const reqUUID = req.ctx.uuid
  let isFull = false
  let available
  amount = parseInt(amount)
  let availableLimit = Math.max(3 * amount, CONSTANTS.DEFAULT_AVAILABLE_POOL)
  let result = await cveIdRangeRepo.findOne({ cve_year: year })

  // Cve Id Range for 'year' does not exists
  if (!result) {
    logger.info({ uuid: req.ctx.uuid, message: 'CVE IDs for year ' + year + ' cannot be reserved at this time.' })
    res.header(CONSTANTS.QUOTA_HEADER, availableIds)
    return res.status(403).json(error.cannotReserveForYear(year))
  }

  available = await cveIdRepo.find({ cve_year: year, state: 'AVAILABLE' }, { limit: availableLimit }) // get available ids

  // Case 1: Not enough IDs in the 'AVAILABLE' pool
  if (available.length < availableLimit) {
    result = await incrementNonSequentialPool(availableLimit, result.ranges.general.end, result.ranges.general.top_id, amount, available.length, year, cveIdRangeRepo)
    isFull = result.isFull

    if (isFull) {
      logger.error(JSON.stringify({ message: 'The cve id non-sequential block is full for year ' + year + '. No more sequential ids can be reserved at this time.' }))
      res.header(CONSTANTS.QUOTA_HEADER, availableIds)
      return res.status(403).json(error.yearRangeFull(year))
    }

    await allocateAvailableCveIds(result.ids, year, req) // Pool was incremented. Create 'AVAILABLE' cve ids.
    available = await cveIdRepo.find({ cve_year: year, state: 'AVAILABLE' }, { limit: availableLimit }) // get available ids
  }

  // Case 2: Enough IDs in the 'AVAILABLE' pool
  const orgRepo = req.ctx.repositories.getOrgRepository()
  const userRepo = req.ctx.repositories.getUserRepository()
  let index
  let counter = 0
  const cveIdDocuments = []
  const cveIdDocumentsUUID = []
  const owningOrgUUID = await orgRepo.getOrgUUID(shortName)
  const orgUUID = await orgRepo.getOrgUUID(orgShortName)
  const requesterUUID = (await userRepo.findOneByUserNameAndOrgUUID(requester, orgUUID)).UUID
  const q = availableIds - amount

  while ((counter < amount) && !isFull) {
    index = getRandomInt(0, available.length) // get random index in the available array
    result = await reserveNonSequentialCveId(index, available, year, shortName, orgShortName, requester, owningOrgUUID, orgUUID, requesterUUID, cveIdRepo)

    if (result.isReserved) {
      logger.info({ uuid: req.ctx.uuid, message: result.cveId.cve_id + ' was reserved' })
      cveIdDocuments.push(result.cveId) // add reserved cve id to the array of reserved cve ids
      cveIdDocumentsUUID.push(result.cveIdUUID) // add reserved cve id UUID to the array of reserved cve ids
      available.splice(index, 1) // remove reserved cve id from the 'AVAILABLE' pool
      counter++
    } else {
      available = await cveIdRepo.find({ cve_year: year, state: 'AVAILABLE' }, { limit: availableLimit }) // get available ids
      availableLimit = Math.max(3 * (amount - counter), CONSTANTS.DEFAULT_AVAILABLE_POOL) // recalculate the available limit since some ids might have been reserved

      // Case 1: Not enough IDs in the 'AVAILABLE' pool
      if (available.length < availableLimit) {
        result = await cveIdRangeRepo.findOne({ cve_year: year })
        result = await incrementNonSequentialPool(availableLimit, result.ranges.general.end, result.ranges.general.top_id, amount - counter, available.length, year, cveIdRangeRepo)
        isFull = result.isFull

        if (isFull) {
          logger.info({ uuid: req.ctx.uuid, message: 'Only ' + counter + ' cve ids were reserved because there are not enough ids in the CVE ID non-sequential block. Non-sequential CVE IDs were reserved for \'' + shortName + '\' organization on behalf of \'' + orgShortName + '\' organization.', cve_ids: cveIdDocumentsUUID.map((doc) => { return doc.cve_id }) })
          res.header(CONSTANTS.QUOTA_HEADER, q)
          return res.status(206).json(error.reservedPartialAmount(counter, cveIdDocuments), { meta: { remaining_quota: q } })
        }

        await allocateAvailableCveIds(result.ids, year, req) // Pool was incremented. Create 'AVAILABLE' cve ids.
        available = await cveIdRepo.find({ cve_year: year, state: 'AVAILABLE' }, { limit: availableLimit }) // get available ids
      }
    }
  }

  const reservedCveIds = cveIdDocumentsUUID.map((doc) => {
    return doc.cve_id
  })

  if (!isFull) {
    const payload = {
      action: 'nonsequential-reservation',
      change: 'Non-sequential CVE IDs were reserved for \'' + shortName + '\' organization on behalf of \'' + orgShortName + '\' organization.',
      req_UUID: reqUUID,
      org_UUID: orgUUID,
      user_UUID: requesterUUID,
      amount: amount,
      cve_ids: reservedCveIds
    }

    logger.info(JSON.stringify(payload))
    res.header(CONSTANTS.QUOTA_HEADER, q)
    return res.status(200).json({ cve_ids: cveIdDocuments, meta: { remaining_quota: q } })
  }
}

async function incrementNonSequentialPool (availableLimit, end, top, amount, available, year, cveIdRR) {
  let increment = availableLimit - available + amount // the amount is the increment amount
  const cveIdRangeRepo = cveIdRR
  const endRange = parseInt(end)
  const topId = parseInt(top)

  // Cap increment to end of general CVE ID block
  if (endRange < (topId + increment)) {
    increment = endRange - topId
  }

  const result = {
    isFull: false,
    ids: null
  }

  if (increment > 0) {
    let r = await cveIdRangeRepo.findOneAndUpdate({ $and: [{ cve_year: year }, { 'ranges.general.end': { $gte: increment } }] }, { $inc: { 'ranges.general.top_id': increment } }, { new: true })

    // Cap increment because it went over end of general block
    if (r.ranges.general.top_id > endRange) {
      increment = increment - (r.ranges.general.top_id - endRange) // adjusting real increment
      r = await cveIdRangeRepo.findOneAndUpdate({ cve_year: year }, { $set: { 'ranges.general.top_id': endRange } }, { new: true }) // Cap top_id to end of the block
    }

    if (increment > 0) {
      result.ids = generateSequentialIds(year, r.ranges.general.top_id, increment)
    } else if (available < amount) { // You hit the end of the boundary
      result.isFull = true
    }
  } else if (available < amount) { // You hit the end of the boundary
    result.isFull = true
  }

  return result
}

async function allocateAvailableCveIds (ids, year, req) {
  if (ids) {
    const cveIdRepo = req.ctx.repositories.getCveIdRepository()
    const cveIdDocuments = []
    let cveId

    ids.forEach(id => {
      cveId = new CveId()
      cveId.cve_id = id
      cveId.cve_year = year
      cveId.state = 'AVAILABLE'
      cveId.owning_cna = 'N/A' // the org who gets assigned the reserved CVE IDs
      cveId.reserved = Date.now()
      cveId.requested_by = {
        cna: 'N/A', // the org who requested the CVE IDs
        user: 'N/A'
      }

      cveIdDocuments.push(cveId)
    })

    await cveIdRepo.insertMany(cveIdDocuments) // Save the 'AVAILABLE' cve ids
    logger.info({ uuid: req.ctx.uuid, message: 'The AVAILABLE pool was incremented by ' + cveIdDocuments.length + ' cve ids.' })
  }
}

async function reserveNonSequentialCveId (index, available, year, shortName, orgShortName, requester, owningOrgUUID, orgUUID, requesterUUID, cveIdR) {
  // Update available Cve Id
  const cveIdUUID = new CveId()
  const cveId = new CveId()
  const cveIdRepo = cveIdR
  cveIdUUID.cve_id = cveId.cve_id = available[index].cve_id
  cveIdUUID.cve_year = cveId.cve_year = year
  cveIdUUID.state = cveId.state = 'RESERVED'
  cveIdUUID.owning_cna = owningOrgUUID
  cveId.owning_cna = shortName // the org who gets assigned the reserved CVE IDs
  cveIdUUID.reserved = cveId.reserved = Date.now()
  cveIdUUID.requested_by = {
    cna: orgUUID, // the org who requested the CVE IDs
    user: requesterUUID
  }
  cveId.requested_by = {
    cna: orgShortName, // the org who requested the CVE IDs
    user: requester
  }

  const result = {
    cveId: cveId,
    cveIdUUID: cveIdUUID,
    isReserved: await cveIdRepo.findOneAndUpdate({ cve_id: cveIdUUID.cve_id, state: 'AVAILABLE' }, cveIdUUID, { new: true })
  }

  return result
}

async function getPayload (req, org) {
  const payload = {
    id_quota: org.policies.id_quota
  }
  const cveIdRepo = req.ctx.repositories.getCveIdRepository()

  const result = await cveIdRepo.countDocuments({ owning_cna: org.UUID, state: 'RESERVED' })
  payload.total_reserved = result
  payload.available = (payload.id_quota - payload.total_reserved)

  return payload
}

function cleanOutputSequential (cveIdDocuments) {
  cveIdDocuments.forEach((obj) => {
    const cveId = JSON.parse(JSON.stringify(obj))
    delete cveId.time

    if (cveId.__v !== undefined) {
      delete cveId.__v
    }
  })

  return cveIdDocuments
}

function generateSequentialIds (year, topId, amount) {
  const start = topId - amount + 1 // before the pool 'AVAILABLE' pool increment
  const end = topId // after the 'AVAILABLE' pool increment
  const ids = []

  for (let i = start; i < end + 1; i++) {
    ids.push('CVE-' + year + '-' + String(i).padStart(4, '0'))
  }

  return ids
}

function getRandomInt (min, max) {
  min = Math.ceil(min)
  max = Math.floor(max)

  return Math.floor(Math.random() * (max - min) + min) // The maximum is exclusive and the minimum is inclusive
}

function setAggregateObj (query) {
  return [
    {
      $match: query
    },
    {
      $lookup: {
        from: 'Org',
        localField: 'owning_cna',
        foreignField: 'UUID',
        as: 'ownerCna'
      }
    },
    {
      $unwind: '$ownerCna'
    },
    {
      $project: {
        _id: false,
        cve_id: true,
        cve_year: true,
        state: true,
        owning_cna: '$ownerCna.short_name',
        requested_by: true,
        reserved: true,
        time: true
      }
    },
    {
      $lookup: {
        from: 'User',
        localField: 'requested_by.user',
        foreignField: 'UUID',
        as: 'result'
      }
    },
    {
      $unwind: '$result'
    },
    {
      $project: {
        cve_id: true,
        cve_year: true,
        state: true,
        owning_cna: true,
        'requested_by.cna': true,
        'requested_by.user': '$result.username',
        reserved: true,
        time: true
      }
    },
    {
      $lookup: {
        from: 'Org',
        localField: 'requested_by.cna',
        foreignField: 'UUID',
        as: 'result'
      }
    },
    {
      $unwind: '$result'
    },
    {
      $project: {
        cve_id: true,
        cve_year: true,
        state: true,
        owning_cna: true,
        'requested_by.cna': '$result.short_name',
        'requested_by.user': true,
        reserved: true,
        time: true
      }
    }
  ]
}

// Smaller aggregation used in getFilteredCveId function
function setMinAggregateObj (query) {
  return [
    {
      $match: query
    },

    {
      $project: {
        _id: false,
        __v: false
      }
    }
  ]
}

module.exports = {
  CVEID_GET_FILTER: getFilteredCveId,
  CVEID_RESERVE: reserveCveId,
  CVEID_GET_SINGLE: getCveId,
  CVEID_UPDATE_SINGLE: modifyCveId,
  CVEID_RANGE_CREATE: createCveIdRange
}


text:
const { validationResult } = require('express-validator')
const errors = require('./error')
const error = new errors.CveIdControllerError()
const utils = require('../../utils/utils')

function parseGetParams (req, res, next) {
  utils.reqCtxMapping(req, 'query', ['page', 'state', 'cve_id_year', 'time_reserved.lt', 'time_reserved.gt', 'time_modified.lt', 'time_modified.gt'])
  utils.reqCtxMapping(req, 'params', ['id'])
  next()
}

function parsePostParams (req, res, next) {
  utils.reqCtxMapping(req, 'query', ['state', 'amount', 'batch_type', 'short_name', 'cve_year', 'org'])
  utils.reqCtxMapping(req, 'params', ['id', 'year'])
  next()
}

function parseError (req, res, next) {
  const err = validationResult(req).formatWith(({ location, msg, param, value, nestedErrors }) => {
    return { msg: msg, param: param, location: location }
  })
  if (!err.isEmpty()) {
    return res.status(400).json(error.badInput(err.array()))
  }
  next()
}

module.exports = {
  parseGetParams,
  parsePostParams,
  parseError
}

repo_name: CVEProject/cve-services

repo_language: JavaScript

file_name: Dockerfile

mime_type: text/plain


text:
const idrErr = require('../../utils/error')
const getConstants = require('../../constants').getConstants

class CveIdControllerError extends idrErr.IDRError {
  invalidState (state) { // cve-id
    const err = {}
    err.error = 'INVALID_STATE'
    err.message = `State '${state}' is invalid.`
    return err
  }

  invalidReservedState () { // cve-id
    const err = {}
    err.error = 'INVALID_RESERVED_STATE'
    err.message = 'Cannot change the state to RESERVED.'
    return err
  }

  invalidAmount () { // cve-id
    const err = {}
    err.error = 'INVALID_AMOUNT'
    err.message = 'The amount query parameter needs to be above 0.'
    return err
  }

  noAmount () { // cve-d
    const err = {}
    err.error = 'NO_AMOUNT'
    err.message = 'The amount query parameter is required and needs to be above 0.'
    return err
  }

  overNonSequentialMaxAmount () { // cve-id
    const err = {}
    const CONSTANTS = getConstants()
    err.error = 'OVER_NONSEQUENTIAL_MAX_AMOUNT'
    err.message = 'The amount query parameter exceeds the maximum amount allowed. Only amounts of ' + CONSTANTS.NONSEQUENTIAL_MAX_AMOUNT + ' ids or less can be reserved at a time.'
    return err
  }

  noYear () { // cve-id
    const err = {}
    err.error = 'NO_YEAR'
    err.message = 'The cve_year query parameter is required [1999-present+1].'
    return err
  }

  noBatchType () { // cve-id
    const err = {}
    err.error = 'NO_BATCH_TYPE'
    err.message = 'The batch_type query parameter must be specified when reserving more than one CVE ID. Available values are: sequential, nonsequential, non-sequential.'
    return err
  }

  noOrgShortName () { // cve-id
    const err = {}
    err.error = 'NO_ORG_SHORTNAME'
    err.message = 'The short_name query parameter is required to reserve CVE IDs.'
    return err
  }

  orgCannotReserveForOther () { // cve-id
    const err = {}
    const CONSTANTS = getConstants()
    err.error = 'ORG_CANNOT_RESERVE_FOR_OTHER'
    err.message = 'The organization designated by the ' + CONSTANTS.AUTH_HEADERS.ORG + ' header is not allowed to reserve IDs for the organization specified by the short_name query parameter.'
    return err
  }

  invalidBatchType () { // cve-id
    const err = {}
    err.error = 'INVALID_BATCH_TYPE'
    err.message = 'The batch_type provided is invalid. Available values are sequential, nonsequential, non-sequential.'
    return err
  }

  cannotReserveForYear (year) { // cve-id
    const err = {}
    err.error = 'CANNOT_RESERVE_FOR_YEAR'
    err.message = `IDR is not currently configured to reserve IDs for year ${year}. If you believe you are receiving this message in error, please contact an administrator.`
    return err
  }

  yearRangeFull (year) { // cve-id
    const err = {}
    err.error = 'YEAR_RANGE_FULL'
    err.message = `IDR cannot currently reserve IDs for year ${year} as the range it's allowed to reserve from is full. Please contact an administrator to have the range extended.`
    return err
  }

  yearRangeExists (year) { // cve-id
    const err = {}
    err.error = 'YEAR_RANGE_EXISTS'
    err.message = `CVE Id Range document for year ${year} was not created because it already exists.`
    return err
  }

  reservationInProgress () { // cve-id
    const err = {}
    err.error = 'RESERVATION_IN_PROGRESS'
    err.message = 'An ID reservation request for this organization is already in progress.'
    return err
  }

  overIdQuota (details) { // cve-id
    const err = {}
    err.error = 'EXCEEDED_ID_QUOTA'
    err.message = 'The amount requested would exceed the organization\'s ID quota. No more IDs can be reserved until the number of IDs in the Reserved state goes below the ID quota or the ID quota is raised. If you feel you are receiving this message in error, please contact support here: https://cveform.mitre.org/'
    err.details = details
    return err
  }

  reservedPartialAmount (amount, ids) { // cve-id
    const err = {}
    err.error = 'RESERVED_PARTIAL_AMOUNT'
    err.message = 'Only a partial amount of CVE IDs were reserved because there are not enough IDs available in the configured range for IDR to reserve the full amount.'
    err.details = {}
    err.details.amount_reserved = amount
    err.cve_ids = ids
    return err
  }

  cannotChangeCveIdWithRecord (id) {
    const err = {
      error: 'CANNOT_CHANGE_CVE_ID_WITH_RECORD',
      message: `A record was found for ${id}. A CVE ID cannot be changed once a record exists for it. Instead, it changes according to the record.`
    }
    return err
  }

  modifyCveIdNoQuota () {
    const err = {
      error: 'TRANSITION_STATE_RESERVED_NO_QUOTA',
      message: 'A CVE ID state cannot be changed to RESERVED if the owning organization has no remaining quota.'
    }
    return err
  }
}

module.exports = {
  CveIdControllerError
}


text:
const express = require('express')
const router = express.Router()
const controller = require('./system.controller')

router.get('/health-check',
/*
  #swagger.tags = ['Utilities']
  #swagger.operationId = 'healthCheck'
  #swagger.summary = "Checks that the system is running (accessible to all users)"
  #swagger.description = "

Access Control

Endpoint is accessible to all

Expected Behavior

Returns a 200 response code when CVE Services are running
"
  #swagger.responses[200] = {
    description: 'Returns a 200 response code'
  }
*/
  controller.health_check)

module.exports = router


text:
async function healthCheck (req, res) {
  res.status(200).send()
}

module.exports = {
  health_check: healthCheck
}


text:
// Schemas relating to CVE records
async function getCreateCveRecordResponseSchema (req, res) {
  const createCveRecordResponseSchema = require('../../../schemas/cve/create-cve-record-response.json')
  res.json(createCveRecordResponseSchema)
  res.status(200)
}

async function getCreateCveRecordRejectionRequestSchema (req, res) {
  const createCveRecordRejectionRequestSchema = require('../../../schemas/cve/create-cve-record-rejection-request.json')
  res.json(createCveRecordRejectionRequestSchema)
  res.status(200)
}

async function getCreateCveRecordRejectionResponseSchema (req, res) {
  const createCveRecordRejectionResponseSchema = require('../../../schemas/cve/create-cve-record-rejection-response.json')
  res.json(createCveRecordRejectionResponseSchema)
  res.status(200)
}

async function getUpdateCveRecordRejectionRequestSchema (req, res) {
  const updateCveRecordRejectionRequestSchema = require('../../../schemas/cve/update-cve-record-rejection-request.json')
  res.json(updateCveRecordRejectionRequestSchema)
  res.status(200)
}

async function getUpdateCveRecordRejectionResponseSchema (req, res) {
  const updateCveRecordRejectionResponseSchema = require('../../../schemas/cve/update-cve-record-rejection-response.json')
  res.json(updateCveRecordRejectionResponseSchema)
  res.status(200)
}

async function getCveRecordMinimumSchema (req, res) {
  const cveRecordMinimumSchema = require('../../../schemas/cve/cve-record-minimum-request.json')
  res.json(cveRecordMinimumSchema)
  res.status(200)
}

async function getCveRecordResponseSchema (req, res) {
  const cveRecordSchema = require('../../../schemas/cve/get-cve-record-response.json')
  res.json(cveRecordSchema)
  res.status(200)
}

async function getFullCveRecordRequestSchema (req, res) {
  const getFullCveRecordRequestSchema = require('../../../schemas/cve/create-full-cve-record-request.json')
  res.json(getFullCveRecordRequestSchema)
  res.status(200)
}

async function getFullUpdateCveRecordResponseSchema (req, res) {
  const getFullUpdateCveRecordResponseSchema = require('../../../schemas/cve/update-full-cve-record-response.json')
  res.json(getFullUpdateCveRecordResponseSchema)
  res.status(200)
}

async function getListCveRecordsResponseSchema (req, res) {
  const listCveRecordsSchema = require('../../../schemas/cve/list-cve-records-response.json')
  res.json(listCveRecordsSchema)
  res.status(200)
}

async function getCursorCveRecordsResponseSchema (req, res) {
  const cursorCveRecordsSchema = require('../../../schemas/cve/cursor-cve-records-response.json')
  res.json(cursorCveRecordsSchema)
  res.status(200)
}

async function getUpdateCveRecordResponseSchema (req, res) {
  const updateCveRecordResponseSchema = require('../../../schemas/cve/update-cve-record-response.json')
  res.json(updateCveRecordResponseSchema)
  res.status(200)
}

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

async function getCnaFullSchema (req, res) {
  const cnaFullSchema = require('../../../schemas/cve/create-cve-record-cna-request.json')
  res.json(cnaFullSchema)
  res.status(200)
}

async function getAdpFullSchema (req, res) {
  const adpFullSchema = require('../../../schemas/cve/create-adp-record-adp-request.json')
  res.json(adpFullSchema)
  res.status(200)
}

async function getCnaSecretariatFullSchema (req, res) {
  const cnaFullSchemaSecretariat = require('../../../schemas/cve/create-cve-record-secretariat-request.json')
  res.json(cnaFullSchemaSecretariat)
  res.status(200)
}

// Schemas relating to CVE IDs
async function getCreateCveIdsResponseSchema (req, res) {
  const createCveIdsResponseSchema = require('../../../schemas/cve-id/create-cve-ids-response.json')
  res.json(createCveIdsResponseSchema)
  res.status(200)
}

async function getCreateCveIdsPartialResponseSchema (req, res) {
  const createCveIdsPartialResponseSchema = require('../../../schemas/cve-id/create-cve-ids-partial-response.json')
  res.json(createCveIdsPartialResponseSchema)
  res.status(200)
}

async function getCveIdResponseSchema (req, res) {
  const getCveIdResponseSchema = require('../../../schemas/cve-id/get-cve-id-response.json')
  res.json(getCveIdResponseSchema)
  res.status(200)
}

async function getListCveIdsResponseSchema (req, res) {
  const listCveIdsResponseSchema = require('../../../schemas/cve-id/list-cve-ids-response.json')
  res.json(listCveIdsResponseSchema)
  res.status(200)
}

async function getUpdateCVEIdResponseSchema (req, res) {
  const updateCveIdsResponseSchema = require('../../../schemas/cve-id/update-cve-id-response.json')
  res.json(updateCveIdsResponseSchema)
  res.status(200)
}

// Schemas relating to errors
async function getBadRequestSchema (req, res) {
  const badRequestSchema = require('../../../schemas/errors/bad-request.json')
  res.json(badRequestSchema)
  res.status(200)
}

async function getGenericErrorSchema (req, res) {
  const genericErrorSchema = require('../../../schemas/errors/generic.json')
  res.json(genericErrorSchema)
  res.status(200)
}

// Schemas relating to organizations

async function getCreateOrgRequestSchema (req, res) {
  const createOrgRequestSchema = require('../../../schemas/org/create-org-request.json')
  res.json(createOrgRequestSchema)
  res.status(200)
}

async function getCreateOrgResponseSchema (req, res) {
  const createOrgResponseSchema = require('../../../schemas/org/create-org-response.json')
  res.json(createOrgResponseSchema)
  res.status(200)
}

async function getListOrgsResponseSchema (req, res) {
  const listOrgsResponseSchema = require('../../../schemas/org/list-orgs-response.json')
  res.json(listOrgsResponseSchema)
  res.status(200)
}

async function getOrgResponseSchema (req, res) {
  const getOrgResponseSchema = require('../../../schemas/org/get-org-response.json')
  res.json(getOrgResponseSchema)
  res.status(200)
}

async function getOrgQuotaResponseSchema (req, res) {
  const getOrgQuotaResponseSchema = require('../../../schemas/org/get-org-quota-response.json')
  res.json(getOrgQuotaResponseSchema)
  res.status(200)
}

async function getUpdateOrgResponseSchema (req, res) {
  const updateOrgResponseSchema = require('../../../schemas/org/update-org-response.json')
  res.json(updateOrgResponseSchema)
  res.status(200)
}

// Schemas relating to users

async function getCreateUserRequestSchema (req, res) {
  const createUserRequestSchema = require('../../../schemas/user/create-user-request.json')
  res.json(createUserRequestSchema)
  res.status(200)
}

async function getCreateUserResponseSchema (req, res) {
  const createUserResponseSchema = require('../../../schemas/user/create-user-response.json')
  res.json(createUserResponseSchema)
  res.status(200)
}

async function getListUsersSchema (req, res) {
  const listUsersResponseSchema = require('../../../schemas/user/list-users-response.json')
  res.json(listUsersResponseSchema)
  res.status(200)
}

async function getUserResponseSchema (req, res) {
  const getUserResponseSchema = require('../../../schemas/user/get-user-response.json')
  res.json(getUserResponseSchema)
  res.status(200)
}

async function getResetSecretResponseSchema (req, res) {
  const resetSecretResponseSchema = require('../../../schemas/user/reset-secret-response.json')
  res.json(resetSecretResponseSchema)
  res.status(200)
}

async function getUpdateUserResponseSchema (req, res) {
  const updateUserResponseSchema = require('../../../schemas/user/update-user-response.json')
  res.json(updateUserResponseSchema)
  res.status(200)
}

module.exports = {
  getBadRequestSchema: getBadRequestSchema,
  getCreateCveRecordResponseSchema: getCreateCveRecordResponseSchema,
  getCreateCveRecordRejectionRequestSchema: getCreateCveRecordRejectionRequestSchema,
  getCreateCveRecordRejectionResponseSchema: getCreateCveRecordRejectionResponseSchema,
  getUpdateCveRecordRejectionRequestSchema: getUpdateCveRecordRejectionRequestSchema,
  getUpdateCveRecordRejectionResponseSchema: getUpdateCveRecordRejectionResponseSchema,
  getCveRecordMinimumSchema: getCveRecordMinimumSchema,
  getCveRecordResponseSchema: getCveRecordResponseSchema,
  getFullCveRecordRequestSchema: getFullCveRecordRequestSchema,
  getFullUpdateCveRecordResponseSchema: getFullUpdateCveRecordResponseSchema,
  getListCveRecordsResponseSchema: getListCveRecordsResponseSchema,
  getCursorCveRecordsResponseSchema: getCursorCveRecordsResponseSchema,
  getUpdateCveRecordResponseSchema: getUpdateCveRecordResponseSchema,
  getCreateCveIdsResponseSchema: getCreateCveIdsResponseSchema,
  getCreateCveIdsPartialResponseSchema: getCreateCveIdsPartialResponseSchema,
  getCveIdResponseSchema: getCveIdResponseSchema,
  getListCveIdsResponseSchema: getListCveIdsResponseSchema,
  getGenericErrorSchema: getGenericErrorSchema,
  getCreateOrgRequestSchema: getCreateOrgRequestSchema,
  getCreateOrgResponseSchema: getCreateOrgResponseSchema,
  getListOrgsResponseSchema: getListOrgsResponseSchema,
  getOrgResponseSchema: getOrgResponseSchema,
  getOrgQuotaResponseSchema: getOrgQuotaResponseSchema,
  getUpdateOrgResponseSchema: getUpdateOrgResponseSchema,
  getCreateUserRequestSchema: getCreateUserRequestSchema,
  getCreateUserResponseSchema: getCreateUserResponseSchema,
  getListUsersSchema: getListUsersSchema,
  getUserResponseSchema: getUserResponseSchema,
  getResetSecretResponseSchema: getResetSecretResponseSchema,
  getUpdateUserResponseSchema: getUpdateUserResponseSchema,
  getUpdateCVEIdResponseSchema: getUpdateCVEIdResponseSchema,
  getAdpMinimumSchema: getAdpMinimumSchema,
  getCnaFullSchema: getCnaFullSchema,
  getAdpFullSchema: getAdpFullSchema,
  getCnaSecretariatFullSchema: getCnaSecretariatFullSchema
}


text:
const express = require('express')
const router = express.Router()
const controller = require('./schemas.controller')

// Schemas relating to CVE records
router.get('/cve/create-cve-record-response.json', controller.getCreateCveRecordResponseSchema)
router.get('/cve/create-cve-record-rejection-request.json', controller.getCreateCveRecordRejectionRequestSchema)
router.get('/cve/create-cve-record-rejection-response.json', controller.getCreateCveRecordRejectionResponseSchema)
router.get('/cve/update-cve-record-rejection-request.json', controller.getUpdateCveRecordRejectionRequestSchema)
router.get('/cve/update-cve-record-rejection-response.json', controller.getUpdateCveRecordRejectionResponseSchema)
router.get('/cve/cve-record-minimum-request.json', controller.getCveRecordMinimumSchema)
router.get('/cve/get-cve-record-response.json', controller.getCveRecordResponseSchema)
router.get('/cve/list-cve-records-response.json', controller.getListCveRecordsResponseSchema)
router.get('/cve/cursor-cve-records-response.json', controller.getCursorCveRecordsResponseSchema)
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)
router.get('/cve/create-cve-record-cna-request.json', controller.getCnaFullSchema)
router.get('/cve/create-adp-record-adp-request.json', controller.getAdpFullSchema)
router.get('/cve/create-cve-record-secretariat-request.json', controller.getCnaSecretariatFullSchema)

// Schemas relating to CVE IDs
router.get('/cve-id/create-cve-ids-response.json', controller.getCreateCveIdsResponseSchema)
router.get('/cve-id/create-cve-ids-partial-response.json', controller.getCreateCveIdsPartialResponseSchema)
router.get('/cve-id/get-cve-id-response.json', controller.getCveIdResponseSchema)
router.get('/cve-id/list-cve-ids-response.json', controller.getListCveIdsResponseSchema)
router.get('/cve-id/update-cve-id-response.json', controller.getUpdateCVEIdResponseSchema)

// Schemas relating to errors
router.get('/errors/bad-request.json', controller.getBadRequestSchema)
router.get('/errors/generic.json', controller.getGenericErrorSchema)

// Schemas relating to organizations
router.get('/org/create-org-request.json', controller.getCreateOrgRequestSchema)
router.get('/org/create-org-response.json', controller.getCreateOrgResponseSchema)
router.get('/org/get-org-response.json', controller.getOrgResponseSchema)
router.get('/org/list-orgs-response.json', controller.getListOrgsResponseSchema)
router.get('/org/get-org-quota-response.json', controller.getOrgQuotaResponseSchema)
router.get('/org/update-org-response.json', controller.getUpdateOrgResponseSchema)

// Schemas relating to users
router.get('/user/create-user-request.json', controller.getCreateUserRequestSchema)
router.get('/user/create-user-response.json', controller.getCreateUserResponseSchema)
router.get('/user/get-user-response.json', controller.getUserResponseSchema)
router.get('/user/list-users-response.json', controller.getListUsersSchema)
router.get('/user/reset-secret-response.json', controller.getResetSecretResponseSchema)
router.get('/user/update-user-response.json', controller.getUpdateUserResponseSchema)

module.exports = router


text:
const { body, validationResult } = require('express-validator')
const errors = require('./error')
const error = new errors.CveControllerError()
const utils = require('../../utils/utils')
const fs = require('fs')
const RejectedSchema = JSON.parse(fs.readFileSync('src/middleware/Reject_5.0_Schema.json'))
const cnaContainerSchema = JSON.parse(fs.readFileSync('src/controller/cve.controller/cna_container_schema.json'))
const logger = require('../../middleware/logger')
const Ajv = require('ajv')
const addFormats = require('ajv-formats')
const ajv = new Ajv({ allErrors: true })
addFormats(ajv)
const validateRejected = ajv.compile(RejectedSchema)
const validateCnaContainer = ajv.compile(cnaContainerSchema)

function parsePostParams (req, res, next) {
  utils.reqCtxMapping(req, 'body', [])
  utils.reqCtxMapping(req, 'params', ['id'])
  next()
}

function parseGetParams (req, res, next) {
  utils.reqCtxMapping(req, 'query', ['page', 'time_modified.lt', 'time_modified.gt', 'state', 'count_only', 'assigner_short_name', 'assigner', 'cna_modified', 'adp_short_name', 'next_page', 'previous_page', 'limit'])
  utils.reqCtxMapping(req, 'params', ['id'])
  next()
}

function parseError (req, res, next) {
  const err = validationResult(req).formatWith(({ location, msg, param, value, nestedErrors }) => {
    return { msg: msg, param: param, location: location }
  })
  if (!err.isEmpty()) {
    return res.status(400).json(error.badInput(err.array()))
  }
  next()
}

/**
 * Custom body validation for unique English entries in the value of the array index passed.
 * (Schema validation checks a unique combination of lang + text value, so we don't here.)
 *
 * @param {String} langsIndex
 * @returns Result
 */
function validateUniqueEnglishEntry (langsIndex) {
  // The JSON format itself is enforced in the schema validation, so we only need
  // to check the fields if they were passed, so can use optional()
  return body(langsIndex).optional({ nullable: true }).isArray().custom((langsArr, { req, path }) => {
    // the passed in keys are ORs, not ANDs
    if (langsArr === undefined) {
      return true
    }

    if (hasSingleEnglishEntry(langsArr)) {
      return true
    } else {
      // duplicate found so send error
      throw new Error(`Cannot have more than one English language entry in '${path}'`)
    }
  })
}

/**
 * Check that the array passed contains only 1 unique English language code entry
 *
 *  - Pass: Duplicate non-English codes (fr and fr)
 *  - Pass: > 1 different English codes (en and en-ca)
 *  - Fail: > 1 same English codes (en & en, en-gb & en-gb)
 *
 * @param {Array} langsArr
 * @returns true
 * @throws Error
 */
function hasSingleEnglishEntry (langsArr) {
  const foundValues = new Set()

  for (const entry of langsArr) {
    const lang = entry.lang.toLowerCase()

    // ignore non-English
    if (!lang.startsWith('en')) {
      continue
    }

    // return early if a duplicate is found
    if (foundValues.has(lang)) {
      return false
    }

    // add each unique value to set
    foundValues.add(lang)
  }

  return true
}

/**
 * Temporary description validator that ensures description fields have at least 1 non-whitespace character
 *
 * @param {String} descIndex
 * @returns true
 * @throws Error
 */
function validateDescription (descIndex) {
  // For each index, check if it exists, then apply custom validator
  return body(descIndex).optional({ nullable: true }).isArray().custom((descriptions, { req, path }) => {
    // For each array of descriptions, check if at least one non-whitespace character
    for (const desc of descriptions) {
      // Some descriptions use 'value' for field name, problemTypes uses 'description'
      return (!!desc.value?.trim().length || !!desc.description?.trim().length)
    }
  })
}

function validateRejectBody (req, res, next) {
  const rejectBody = req.body
  const result = validateRejected(rejectBody) // validate function is based on custom schema

  if (!result) {
    const temp = validateRejected.errors
    const errors = []
    temp.forEach((error) => {
      if (error !== '') {
        errors.push(error)
      }
    })
    return res.status(400).json(error.invalidJsonSchema(errors))
  }
  next()
}

function validateCveCnaContainerJsonSchema (req, res, next) {
  const cnaContainer = req.body
  const result = validateCnaContainer(cnaContainer)
  if (!result) {
    logger.error(JSON.stringify({ uuid: req.ctx.uuid, message: 'CVE JSON schema validation FAILED.' }))
    const temp = validateCnaContainer.errors
    const errorsArray = []
    temp.forEach((error) => {
      if (error !== '') {
        errorsArray.push(error)
      }
    })
    return res.status(400).json(error.invalidCnaContainerJsonSchema(errorsArray))
  }
  logger.info(JSON.stringify({ uuid: req.ctx.uuid, message: 'SUCCESSFUL CVE JSON schema validation.' }))
  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,
  validateRejectBody
}


text:
const express = require('express')
const router = express.Router()
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 getConstants = require('../../constants').getConstants
const CONSTANTS = getConstants()
const CHOICES = [CONSTANTS.CVE_STATES.REJECTED, CONSTANTS.CVE_STATES.PUBLISHED]
const toDate = require('../../utils/utils').toDate

router.get('/cve/:id',
  /*
  #swagger.tags = ['CVE Record']
  #swagger.operationId = 'cveGetSingle'
  #swagger.summary = "Returns a CVE Record by CVE ID (accessible to all users)"
  #swagger.description = "

Access Control

Endpoint is accessible to all

Expected Behavior

All users: Retrieves the CVE Record specified
"
  #swagger.parameters['id'] = { description: 'The CVE ID for the Record to be retrieved' }
  #swagger.responses[200] = {
    description: 'The requested CVE Record',
    content: {
      "application/json": {
        schema: {
            oneOf: [
                { $ref: '../schemas/cve/get-cve-record-response.json' },
                { $ref: '../schemas/cve/create-cve-record-rejection-response.json' }
            ]
        },
        examples: {
            'Published Record': { $ref: '#/components/examples/publishedRecord' },
            'Rejected Record': { $ref: '#/components/examples/rejectedRecord' }
        }
      }
    }
  }

  #swagger.responses[400] = {
    description: 'Bad Request',
    content: {
      "application/json": {
        schema: { $ref: '../schemas/errors/bad-request.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[429] = {
    description: 'Too Many Requests',
    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.rateLimiter,
  param(['id']).isString().matches(CONSTANTS.CVE_ID_REGEX),
  parseError,
  parseGetParams,
  controller.CVE_GET_SINGLE)

router.get('/cve',
  /*
  #swagger.tags = ['CVE Record']
  #swagger.operationId = 'cveGetFiltered'
  #swagger.summary = "Retrieves all CVE Records after applying the query parameters as filters (accessible to Secretariat)"
  #swagger.description = "

Access Control

User must belong to an organization with the Secretariat role

Expected Behavior

Secretariat: Retrieves all CVE records for all organizations
"
  #swagger.parameters['$ref'] = [
    '#/components/parameters/cveRecordFilteredTimeModifiedLt',
    '#/components/parameters/cveRecordFilteredTimeModifiedGt',
    '#/components/parameters/cveState',
    '#/components/parameters/countOnly',
    '#/components/parameters/assignerShortName',
    '#/components/parameters/assigner',
    '#/components/parameters/pageQuery',
    '#/components/parameters/cnaModified',
    '#/components/parameters/adpShortName'
  ]
  #swagger.responses[200] = {
    description: 'A filtered list of CVE Records, along with pagination fields if results span multiple pages of data',
    content: {
      "application/json": {
        schema: {
            oneOf: [
                { $ref: '../schemas/cve/list-cve-records-response.json' },
                { $ref: '../schemas/cve/create-cve-record-rejection-response.json' }
            ]
        },
      }
    }
  }

  #swagger.responses[400] = {
    description: 'Bad Request',
    content: {
      "application/json": {
        schema: { $ref: '../schemas/errors/bad-request.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.onlySecretariatOrBulkDownload,
  query().custom((query) => { return mw.validateQueryParameterNames(query, ['page', 'time_modified.lt', 'time_modified.gt', 'state', 'count_only', 'assigner_short_name', 'assigner', 'cna_modified', 'adp_short_name']) }),
  query(['page']).optional().isInt({ min: CONSTANTS.PAGINATOR_PAGE }),
  query(['time_modified.lt']).optional().isString().trim().escape().customSanitizer(val => { return toDate(val) }).not().isEmpty().withMessage(errorMsgs.TIMESTAMP_FORMAT),
  query(['time_modified.gt']).optional().isString().trim().escape().customSanitizer(val => { return toDate(val) }).not().isEmpty().withMessage(errorMsgs.TIMESTAMP_FORMAT),
  query(['state']).optional().isString().trim().escape().customSanitizer(val => { return val.toUpperCase() }).isIn(CHOICES).withMessage(errorMsgs.CVE_FILTERED_STATES),
  query(['count_only']).optional().isBoolean({ loose: true }).withMessage(errorMsgs.COUNT_ONLY),
  query(['assigner_short_name']).optional().isString().trim().escape().notEmpty().isLength({ min: CONSTANTS.MIN_SHORTNAME_LENGTH, max: CONSTANTS.MAX_SHORTNAME_LENGTH }),
  query(['assigner']).optional().isString().trim().escape().notEmpty(),
  query(['cna_modified']).optional().isBoolean({ loose: true }).withMessage(errorMsgs.CNA_MODIFIED),
  query(['adp_short_name']).optional().isString().trim().escape().notEmpty().isLength({ min: CONSTANTS.MIN_SHORTNAME_LENGTH, max: CONSTANTS.MAX_SHORTNAME_LENGTH }),
  parseError,
  parseGetParams,
  controller.CVE_GET_FILTERED)

router.get('/cve_cursor',
  /*
  #swagger.tags = ['CVE Record']
  #swagger.operationId = 'cveGetFilteredCursor'
  #swagger.summary = "Retrieves all CVE Records after applying the query parameters as filters. Uses cursor pagination to paginate results (accessible to Secretariat)"
  #swagger.description = "

Access Control

User must belong to an organization with the Secretariat role

Expected Behavior

Secretariat: Retrieves all CVE records for all organizations
"
  #swagger.parameters['$ref'] = [
    '#/components/parameters/cveRecordFilteredTimeModifiedLt',
    '#/components/parameters/cveRecordFilteredTimeModifiedGt',
    '#/components/parameters/cveState',
    '#/components/parameters/countOnly',
    '#/components/parameters/assignerShortName',
    '#/components/parameters/assigner',
    '#/components/parameters/cnaModified',
    '#/components/parameters/adpShortName',
    '#/components/parameters/nextPage',
     '#/components/parameters/previousPage',
     '#/components/parameters/limit'

  ]
  #swagger.responses[200] = {
    description: 'A filtered list of CVE Records, along with pagination fields if results span multiple pages of data',
    content: {
      "application/json": {
        schema: {
            oneOf: [
                { $ref: '../schemas/cve/cursor-cve-records-response.json' },
                { $ref: '../schemas/cve/create-cve-record-rejection-response.json' }
            ]
        },
      }
    }
  }

  #swagger.responses[400] = {
    description: 'Bad Request',
    content: {
      "application/json": {
        schema: { $ref: '../schemas/errors/bad-request.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.onlySecretariatOrBulkDownload,
  query().custom((query) => { return mw.validateQueryParameterNames(query, ['time_modified.lt', 'time_modified.gt', 'state', 'count_only', 'assigner_short_name', 'assigner', 'cna_modified', 'adp_short_name', 'next_page', 'previous_page', 'limit']) }),
  query(['time_modified.lt']).optional().isString().trim().escape().customSanitizer(val => { return toDate(val) }).not().isEmpty().withMessage(errorMsgs.TIMESTAMP_FORMAT),
  query(['time_modified.gt']).optional().isString().trim().escape().customSanitizer(val => { return toDate(val) }).not().isEmpty().withMessage(errorMsgs.TIMESTAMP_FORMAT),
  query(['state']).optional().isString().trim().escape().customSanitizer(val => { return val.toUpperCase() }).isIn(CHOICES).withMessage(errorMsgs.CVE_FILTERED_STATES),
  query(['count_only']).optional().isBoolean({ loose: true }).withMessage(errorMsgs.COUNT_ONLY),
  query(['assigner_short_name']).optional().isString().trim().escape().notEmpty().isLength({ min: CONSTANTS.MIN_SHORTNAME_LENGTH, max: CONSTANTS.MAX_SHORTNAME_LENGTH }),
  query(['assigner']).optional().isString().trim().escape().notEmpty(),
  query(['cna_modified']).optional().isBoolean({ loose: true }).withMessage(errorMsgs.CNA_MODIFIED),
  query(['adp_short_name']).optional().isString().trim().escape().notEmpty().isLength({ min: CONSTANTS.MIN_SHORTNAME_LENGTH, max: CONSTANTS.MAX_SHORTNAME_LENGTH }),
  query(['limit']).optional().isString().trim().escape().notEmpty().isLength({ min: 1, max: CONSTANTS.PAGINATOR_OPTIONS.limit }),
  parseError,
  parseGetParams,
  controller.CVE_GET_FILTERED_CURSOR)

router.post('/cve/:id',
  /*
  #swagger.tags = ['CVE Record']
  #swagger.operationId = 'cveSubmit'
  #swagger.summary = "Creates a CVE Record from full CVE Record JSON for the specified ID (accessible to Secretariat.)"
  #swagger.description = "

Access Control

User must belong to an organization with the Secretariat role

Expected Behavior

Secretariat: Creates a CVE Record for any organization
"
  #swagger.parameters['id'] = { description: 'The CVE ID for the record being submitted' }
  #swagger.parameters['$ref'] = [
    '#/components/parameters/apiEntityHeader',
    '#/components/parameters/apiUserHeader',
    '#/components/parameters/apiSecretHeader'
  ]
  #swagger.requestBody = {
    required: true,
    content: {
      "application/json": {
        schema:{ $ref: '../schemas/cve/create-cve-record-secretariat-request.json' }
      }
    }
  }
  #swagger.responses[200] = {
    description: 'The CVE Record created',
    content: {
      "application/json": {
        schema: { $ref: '../schemas/cve/create-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.onlySecretariat,
  mw.validateCveJsonSchema,
  // 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']),
  param(['id']).isString().matches(CONSTANTS.CVE_ID_REGEX),
  parseError,
  parsePostParams,
  controller.CVE_SUBMIT)

router.put('/cve/:id',
  /*
  #swagger.tags = ['CVE Record']
  #swagger.operationId = 'cveUpdateSingle'
  #swagger.summary = "Updates a CVE Record from full CVE Record JSON for the specified ID (accessible to Secretariat.)"
  #swagger.description = "

Access Control

User must belong to an organization with the Secretariat role

Expected Behavior

Secretariat: Updates a CVE Record for any organization
"
  #swagger.parameters['id'] = { description: 'The CVE ID for the record being updated' }
  #swagger.parameters['$ref'] = [
    '#/components/parameters/apiEntityHeader',
    '#/components/parameters/apiUserHeader',
    '#/components/parameters/apiSecretHeader'
  ]
  #swagger.requestBody = {
    required: true,
    content: {
      "application/json": {
        schema:{ $ref: '../schemas/cve/create-cve-record-secretariat-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.onlySecretariat,
  mw.validateCveJsonSchema,
  // 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']),
  param(['id']).isString().matches(CONSTANTS.CVE_ID_REGEX),
  parseError,
  parsePostParams,
  controller.CVE_UPDATE_SINGLE)

router.post('/cve/:id/cna',
  /*
  #swagger.tags = ['CVE Record']
  #swagger.operationId = 'cveCnaCreateSingle'
  #swagger.summary = "Creates a CVE Record from CNA Container JSON for the specified ID (accessible to CNAs and Secretariat)"
  #swagger.description = "

Access Control

User must belong to an organization with the CNA or Secretariat role

Expected Behavior

CNA: Creates CVE Record for a CVE ID owned by their organization

Secretariat: Creates CVE Record for CVE IDs owned by any organization
"
  #swagger.parameters['id'] = { description: 'The CVE ID for the record being created' }
  #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/create-cve-record-cna-request.json' }
      }
    }
  }
  #swagger.responses[200] = {
    description: 'The CVE Record created',
    content: {
      "application/json": {
        schema: { $ref: '../schemas/cve/create-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.onlyCnas,
  validateCveCnaContainerJsonSchema,
  validateUniqueEnglishEntry('cnaContainer.descriptions'),
  validateDescription(['cnaContainer.descriptions', 'cnaContainer.problemTypes[0].descriptions']),
  param(['id']).isString().matches(CONSTANTS.CVE_ID_REGEX),
  parseError,
  parsePostParams,
  mw.cnaMustOwnID,
  controller.CVE_SUBMIT_CNA)

router.put('/cve/:id/cna',
  /*
  #swagger.tags = ['CVE Record']
  #swagger.operationId = 'cveCnaUpdateSingle'
  #swagger.summary = "Updates the CVE Record from CNA Container JSON for the specified ID (accessible to CNAs and Secretariat)"
  #swagger.description = "

Access Control

User must belong to an organization with the CNA or Secretariat role

Expected Behavior

CNA: Updates a CVE Record for records that are owned by their organization

Secretariat: Updates a CVE Record for records that are owned by any organization
"
  #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/create-cve-record-cna-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.onlyCnas,
  validateCveCnaContainerJsonSchema,
  validateUniqueEnglishEntry('cnaContainer.descriptions'),
  validateDescription(['cnaContainer.descriptions', 'cnaContainer.problemTypes[0].descriptions']),
  param(['id']).isString().matches(CONSTANTS.CVE_ID_REGEX),
  parseError,
  parsePostParams,
  mw.cnaMustOwnID,
  controller.CVE_UPDATE_CNA)

router.post('/cve/:id/reject',
  /*
  #swagger.tags = ['CVE Record']
  #swagger.operationId = 'cveCnaCreateReject'
  #swagger.summary = "Creates a rejected CVE Record for the specified ID if no record yet exists (accessible to CNAs and Secretariat)"
  #swagger.description = "

Access Control

User must belong to an organization with the CNA or Secretariat role

Expected Behavior

CNA: Creates a rejected CVE Record for a record owned by their organization

Secretariat: Creates a rejected CVE Record for a record owned by any organization
"
  #swagger.parameters['id'] = { description: 'The CVE ID for the record being rejected' }
  #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/create-cve-record-rejection-request.json' }
      }
    }
  }
  #swagger.responses[200] = {
    description: 'The rejected CVE Record',
    content: {
      "application/json": {
        schema: { $ref: '../schemas/cve/create-cve-record-rejection-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.onlyCnas,
  validateRejectBody,
  validateUniqueEnglishEntry(['cnaContainer.rejectedReasons']),
  validateDescription(['cnaContainer.rejectedReasons']),
  param(['id']).isString().matches(CONSTANTS.CVE_ID_REGEX),
  body(['cnaContainer.replacedBy']).optional().isArray(),
  parseError,
  parsePostParams,
  mw.cnaMustOwnID,
  controller.CVE_REJECT_RECORD)

router.put('/cve/:id/reject',
  /*
  #swagger.tags = ['CVE Record']
  #swagger.operationId = 'cveCnaUpdateReject'
  #swagger.summary = "Updates an existing CVE Record with a rejected record for the specified ID (accessible to CNAs and Secretariat)"
  #swagger.description = "

Access Control

User must belong to an organization with the CNA or Secretariat role

Expected Behavior

CNA: Updates a rejected CVE Record for a record owned by their organization

Secretariat: Updates a rejected CVE Record for a record owned by any organization
"
  #swagger.parameters['id'] = { description: 'The CVE ID for the record being rejected' }
  #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/update-cve-record-rejection-request.json' }
      }
    }
  }
  #swagger.responses[200] = {
    description: 'The rejected CVE Record',
    content: {
      "application/json": {
        schema: { $ref: '../schemas/cve/update-cve-record-rejection-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.onlyCnas,
  validateRejectBody,
  validateUniqueEnglishEntry('cnaContainer.rejectedReasons'),
  validateDescription(['cnaContainer.rejectedReasons']),
  param(['id']).isString().matches(CONSTANTS.CVE_ID_REGEX),
  body(['cnaContainer.replacedBy']).optional().isArray(),
  parseError,
  parsePostParams,
  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 = "

Access Control

User must belong to an organization with the ADP or Secretariat role

Expected Behavior

ADP: Updates a CVE Record for records that are owned by any organization

Secretariat: Updates a CVE Record for records that are owned by any organization
"
  #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/create-adp-record-adp-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