Skip to content

Commit

Permalink
Merge pull request #967 from CVEProject/prod-staging
Browse files Browse the repository at this point in the history
Updating Prod to v2.1.1-sd
  • Loading branch information
brettp committed Dec 19, 2022
2 parents 892646d + 94e650d commit 97453ff
Show file tree
Hide file tree
Showing 78 changed files with 4,645 additions and 2,212 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ API documentation is generated using [swagger-autogen](https://github.com/daviba

To ensure you are using the correct API specification the following endpoints can be used:
- [Test Instance](https://cveawg-test.mitre.org/api-docs/)
- [Production](https://cveawg.mitre.org/api-docs/openapi.json)
- [Production](https://cveawg.mitre.org/api-docs/)

Note: The specification file stored in GitHub will only be correct for that branch; there could be differences between branches and production.

Expand Down
116 changes: 55 additions & 61 deletions api-docs/openapi.json

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions docker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,9 @@ See the [API documentation](https://github.com/CVEProject/cve-services#api-docum
## Mongo DB

The `docker-compose.yml` file exposes the default Mongo port to the host: `localhost:27017`. You can connect using any Mongo viewer such as [Mongo Express](https://github.com/mongo-express/mongo-express) or [Compass](https://www.mongodb.com/try/download/compass) on the host.

## Running unit tests

You can run unit tests using the docker image by running the following command:

`docker exec -it cveawg npm run test`
4,074 changes: 3,802 additions & 272 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"name": "cve-services",
"author": "Automation Working Group",
"version": "0.0.3",
"version": "2.1.1",
"license": "(CC0)",
"devDependencies": {
"apidoc": "^0.17.7",
"apidoc": "^0.53.1",
"chai": "^4.2.0",
"chai-arrays": "^2.0.0",
"chai-http": "^4.3.0",
Expand Down Expand Up @@ -43,13 +43,15 @@
"mongoose": "^5.12.3",
"mongoose-aggregate-paginate-v2": "1.0.6",
"morgan": "^1.9.1",
"node-dev": "^4.0.0",
"node-dev": "^7.4.3",
"prompt-sync": "^4.2.0",
"replace-json-property": "^1.8.0",
"replace-in-file": "6.3.5",
"swagger-autogen": "^2.19.0",
"swagger-ui-express": "^4.3.0",
"uuid": "^8.3.2",
"uuid-apikey": "^1.5.1",
"validate-date": "^2.0.0",
"validator": ">=13.7.0",
"winston": "^3.2.1",
"yamljs": "^0.3.0"
Expand Down
175 changes: 98 additions & 77 deletions src/constants/index.js
Original file line number Diff line number Diff line change
@@ -1,82 +1,103 @@
const fs = require('fs')
const cveSchemaV5 = JSON.parse(fs.readFileSync('src/middleware/5.0_bundled_schema.json'))

module.exports = {
MONGOOSE_VALIDATION: {
Org_policies_id_quota_min: 0,
Org_policies_id_quota_min_message: 'Org.policies.id_quota cannot be a negative number.',
Org_policies_id_quota_max: 100000,
Org_policies_id_quota_max_message: 'Org.policies.id_quota cannot exceed maximum threshold.'
},
DEFAULT_ID_QUOTA: 1000,
DEFAULT_AVAILABLE_POOL: 100,
NONSEQUENTIAL_MAX_AMOUNT: 10,
CRYPTO_RANDOM_STRING_LENGTH: 36,
AUTH_ROLE_ENUM: {
SECRETARIAT: 'SECRETARIAT',
CNA: 'CNA',
ROOT_CNA: 'ROOT_CNA',
ADP: 'ADP'
},
ORG_ROLES: [
'CNA',
'SECRETARIAT',
'ROOT_CNA',
'ADP'
],
USER_ROLES: [
'ADMIN'
],
USER_ROLE_ENUM: {
ADMIN: 'ADMIN'
},
AUTH_HEADERS: {
ORG: 'CVE-API-ORG',
USER: 'CVE-API-USER',
KEY: 'CVE-API-KEY'
},
CVE_STATES: {
PUBLISHED: 'PUBLISHED',
RESERVED: 'RESERVED',
REJECTED: 'REJECTED',
AVAILABLE: 'AVAILABLE'
},
QUOTA_HEADER: 'CVE-API-REMAINING-QUOTA',
DEFAULT_CVE_ID_RANGE: {
cve_year: 2020,
ranges: {
priority: {
top_id: 0,
start: 0,
end: 20000
},
general: {
top_id: 20000,
start: 20000,
end: 50000000
/**
* Return default values.
*
* The object is created in this function to prevent accidental
* value re-assignment and still allow IDE type-hints and compiled regex
*
* @return {defaults}
*/
function getConstants () {
/**
* @constant
* @default
* @lends defaults
*/
const defaults = {
MONGOOSE_VALIDATION: {
Org_policies_id_quota_min: 0,
Org_policies_id_quota_min_message: 'Org.policies.id_quota cannot be a negative number.',
Org_policies_id_quota_max: 100000,
Org_policies_id_quota_max_message: 'Org.policies.id_quota cannot exceed maximum threshold.'
},
DEFAULT_ID_QUOTA: 1000,
DEFAULT_AVAILABLE_POOL: 100,
NONSEQUENTIAL_MAX_AMOUNT: 10,
CRYPTO_RANDOM_STRING_LENGTH: 36,
AUTH_ROLE_ENUM: {
SECRETARIAT: 'SECRETARIAT',
CNA: 'CNA',
ROOT_CNA: 'ROOT_CNA',
ADP: 'ADP'
},
ORG_ROLES: [
'CNA',
'SECRETARIAT',
'ROOT_CNA',
'ADP'
],
USER_ROLES: [
'ADMIN'
],
USER_ROLE_ENUM: {
ADMIN: 'ADMIN'
},
AUTH_HEADERS: {
ORG: 'CVE-API-ORG',
USER: 'CVE-API-USER',
KEY: 'CVE-API-KEY'
},
CVE_STATES: {
PUBLISHED: 'PUBLISHED',
RESERVED: 'RESERVED',
REJECTED: 'REJECTED',
AVAILABLE: 'AVAILABLE'
},
QUOTA_HEADER: 'CVE-API-REMAINING-QUOTA',
DEFAULT_CVE_ID_RANGE: {
cve_year: 2020,
ranges: {
priority: {
top_id: 0,
start: 0,
end: 20000
},
general: {
top_id: 20000,
start: 20000,
end: 50000000
}
}
},
PAGINATOR_HEADERS: {
PAGE: 'PAGINATOR-PAGE'
},
PAGINATOR_PAGE: 1,
PAGINATOR_OPTIONS: {
limit: 500,
useFacet: false,
customLabels: {
totalDocs: 'itemCount',
docs: 'itemsList',
limit: 'itemsPerPage',
page: 'currentPage',
totalPages: 'pageCount',
useFacet: false
}
}
},
PAGINATOR_HEADERS: {
PAGE: 'PAGINATOR-PAGE'
},
PAGINATOR_PAGE: 1,
PAGINATOR_OPTIONS: {
limit: 500,
useFacet: false,
customLabels: {
totalDocs: 'itemCount',
docs: 'itemsList',
limit: 'itemsPerPage',
page: 'currentPage',
totalPages: 'pageCount',
useFacet: false
}
},
MAX_SHORTNAME_LENGTH: 32,
MIN_SHORTNAME_LENGTH: 2,
CVE_ID_PATTERN: cveSchemaV5.definitions.cveId.pattern,
// Ajv's pattern validation uses the "u" (unicode) flag:
// https://ajv.js.org/json-schema.html#pattern
CVE_ID_REGEX: new RegExp(cveSchemaV5.definitions.cveId.pattern, 'u')
},
MAX_SHORTNAME_LENGTH: 32,
MIN_SHORTNAME_LENGTH: 2,
CVE_ID_PATTERN: cveSchemaV5.definitions.cveId.pattern,
// Ajv's pattern validation uses the "u" (unicode) flag:
// https://ajv.js.org/json-schema.html#pattern
CVE_ID_REGEX: new RegExp(cveSchemaV5.definitions.cveId.pattern, 'u')
}

return defaults
}

module.exports = {
getConstants
}
44 changes: 36 additions & 8 deletions src/controller/cve-id.controller/cve-id.controller.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
require('dotenv').config()
const CveId = require('../../model/cve-id')
const logger = require('../../middleware/logger')
const CONSTANTS = require('../../constants')
const getConstants = require('../../constants').getConstants
const errors = require('./error')
const error = new errors.CveIdControllerError()
const options = CONSTANTS.PAGINATOR_OPTIONS
options.sort = { owning_cna: 'asc', cve_id: 'asc' }

// 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
Expand Down Expand Up @@ -180,7 +189,7 @@ async function reserveCveId (req, res, next) {
} 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 > CONSTANTS.NONSEQUENTIAL_MAX_AMOUNT) {
if (amount > getConstants().NONSEQUENTIAL_MAX_AMOUNT) {
return res.status(403).json(error.overNonSequentialMaxAmount())
}
await nonSequentialReservation(year, amount, shortName, orgShortName, requester, payload.available, res, req)
Expand All @@ -197,14 +206,16 @@ async function reserveCveId (req, res, next) {
}

/* Expected behavior by role:
Secretariat: can retrieve all information for all CVE IDs
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
Expand Down Expand Up @@ -267,12 +278,19 @@ async function modifyCveId (req, res, next) {
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)

Object.keys(req.ctx.query).forEach(k => {
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
Expand All @@ -281,10 +299,16 @@ async function modifyCveId (req, res, next) {
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) {
Expand Down Expand Up @@ -338,6 +362,7 @@ async function modifyCveId (req, res, next) {
// 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()
Expand Down Expand Up @@ -368,6 +393,7 @@ async function createCveIdRange (req, res, next) {
}

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 })
Expand Down Expand Up @@ -443,6 +469,7 @@ async function priorityReservation (year, amount, shortName, orgShortName, reque
}

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 })
Expand Down Expand Up @@ -534,6 +561,7 @@ async function sequentialReservation (year, amount, shortName, orgShortName, req
}

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
Expand Down
22 changes: 1 addition & 21 deletions src/controller/cve-id.controller/cve-id.middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,6 @@ function parsePostParams (req, res, next) {
next()
}

// Sanitizer for dates
function toDate (val) {
val = val.toUpperCase()
let value = val.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(|Z|((-|\+|\s)\d{2}:\d{2}))$/)
let result
if (value) {
value[0] = value[0].replace(' ', '+') // Re-add literal '+' which was stripped
result = new Date(value[0])
} else {
value = val.match(/^\d{4}-\d{2}-\d{2}$/)
if (value) {
result = new Date(`${value[0]}T00:00:00.000+00:00`)
} else {
result = null
}
}
return result
}

function parseError (req, res, next) {
const err = validationResult(req).formatWith(({ location, msg, param, value, nestedErrors }) => {
return { msg: msg, param: param, location: location }
Expand All @@ -47,6 +28,5 @@ function parseError (req, res, next) {
module.exports = {
parseGetParams,
parsePostParams,
parseError,
toDate
parseError
}
Loading

0 comments on commit 97453ff

Please sign in to comment.