Skip to content

Commit

Permalink
Merge pull request #1008 from CVEProject/dev
Browse files Browse the repository at this point in the history
Udate INT from DEV
  • Loading branch information
jdaigneau5 authored Feb 6, 2023
2 parents 75c1bb8 + 301322d commit 5c21ad5
Show file tree
Hide file tree
Showing 27 changed files with 318 additions and 78 deletions.
20 changes: 20 additions & 0 deletions api-docs/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,16 @@
}
}
},
"429": {
"description": "Too Many Requests",
"content": {
"application/json": {
"schema": {
"$ref": "/schemas/errors/generic.json"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
Expand Down Expand Up @@ -570,6 +580,16 @@
}
}
},
"429": {
"description": "Too Many Requests",
"content": {
"application/json": {
"schema": {
"$ref": "/schemas/errors/generic.json"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
Expand Down
2 changes: 1 addition & 1 deletion schemas/cve/list-cve-records-response.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"type": "integer",
"format": "int32"
},
"cve_records": {
"cveRecords": {
"type": "array",
"items": {
"type": "object",
Expand Down
4 changes: 3 additions & 1 deletion src/constants/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@ function getConstants () {
AUTH_ROLE_ENUM: {
SECRETARIAT: 'SECRETARIAT',
CNA: 'CNA',
BULK_DOWNLOAD: 'BULK_DOWNLOAD',
ROOT_CNA: 'ROOT_CNA',
ADP: 'ADP'
},
ORG_ROLES: [
'CNA',
'SECRETARIAT',
'BULK_DOWNLOAD',
'ROOT_CNA',
'ADP'
],
Expand Down Expand Up @@ -100,4 +102,4 @@ function getConstants () {

module.exports = {
getConstants
}
}
4 changes: 3 additions & 1 deletion src/controller/cve-id.controller/cve-id.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ async function getFilteredCveId (req, res, next) {
const cveIdRepo = req.ctx.repositories.getCveIdRepository()
const orgRepo = req.ctx.repositories.getOrgRepository()
const isSecretariat = await orgRepo.isSecretariat(orgShortName)
const isBulkDownload = await orgRepo.isBulkDownload(orgShortName)

Object.keys(req.ctx.query).forEach(k => {
const key = k.toLowerCase()
Expand Down Expand Up @@ -61,7 +62,8 @@ async function getFilteredCveId (req, res, next) {
state: { $ne: CONSTANTS.CVE_STATES.AVAILABLE }
}

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

Expand Down
3 changes: 2 additions & 1 deletion src/controller/cve.controller/cve.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,8 @@ async function submitCna (req, res, next) {
const additionalCveMetadataFields = {
assignerShortName: assignerShortName,
dateReserved: (cveId.reserved).toISOString(),
datePublished: dateUpdated
datePublished: dateUpdated,
dateUpdated: dateUpdated
}

const providerMetadata = createProviderMetadata(orgUuid, req.ctx.org, dateUpdated)
Expand Down
2 changes: 1 addition & 1 deletion src/controller/cve.controller/error.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ class CveControllerError extends idrErr.IDRError {
unableToStoreCveRecord () { // cve
const err = {}
err.error = 'UNABLE_TO_STORE_CVE_RECORD'
err.message = 'A problem occurred while saving the CVE Record'
err.message = 'A problem occurred while saving the CVE Record, ensure that x_ values do not start with $'
return err
}

Expand Down
2 changes: 1 addition & 1 deletion src/controller/cve.controller/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ router.get('/cve',
}
*/
mw.validateUser,
mw.onlySecretariat,
mw.onlySecretariatOrBulkDownload,
query().custom((query) => { return mw.validateQueryParameterNames(query, ['page', 'time_modified.lt', 'time_modified.gt', 'state', 'count_only', 'assigner_short_name', 'assigner']) }),
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),
Expand Down
4 changes: 2 additions & 2 deletions src/controller/org.controller/error.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ class OrgControllerError extends idrErr.IDRError {
return err
}

uuidProvided () { // org
uuidProvided (creationType) {
const err = {}
err.error = 'UUID_PROVIDED'
err.message = 'Providing UUIDs for user creation or update is not allowed.'
err.message = `Providing UUIDs for ${creationType} creation or update is not allowed.`
return err
}

Expand Down
2 changes: 0 additions & 2 deletions src/controller/org.controller/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,6 @@ router.post('/org',
mw.onlySecretariat,
body(['short_name']).isString().trim().escape().notEmpty().isLength({ min: CONSTANTS.MIN_SHORTNAME_LENGTH, max: CONSTANTS.MAX_SHORTNAME_LENGTH }),
body(['name']).isString().trim().escape().notEmpty(),
// TODO remove uuid - providing it throws an error in the controller
body(['uuid']).optional().isString().trim().escape(),
body(['authority.active_roles']).optional().customSanitizer(val => { return val.map(x => { return x.toUpperCase() }) }).custom(val => { return isOrgRole(val) }),
body(['policies.id_quota']).optional().not().isArray().isInt({ min: CONSTANTS.MONGOOSE_VALIDATION.Org_policies_id_quota_min, max: CONSTANTS.MONGOOSE_VALIDATION.Org_policies_id_quota_max }).withMessage(errorMsgs.ID_QUOTA),
parseError,
Expand Down
54 changes: 33 additions & 21 deletions src/controller/org.controller/org.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -241,25 +241,34 @@ async function createOrg (req, res, next) {
const newOrg = new Org()
const orgRepo = req.ctx.repositories.getOrgRepository()

Object.keys(req.ctx.body).forEach(k => {
for (const k in req.ctx.body) {
const key = k.toLowerCase()

if (key === 'short_name') {
newOrg.short_name = decodeEntities(req.ctx.body.short_name)
} else if (key === 'name') {
newOrg.name = decodeEntities(req.ctx.body.name)
} else if (key === 'authority') {
if (req.ctx.body.authority.active_roles) {
newOrg.authority.active_roles = req.ctx.body.authority.active_roles
}
} else if (key === 'policies') {
if (req.ctx.body.policies.id_quota) {
newOrg.policies.id_quota = req.ctx.body.policies.id_quota
}
} else if (key === 'uuid') {
return res.status(400).json(error.uuidProvided())
switch (key) {
case 'short_name':
newOrg.short_name = decodeEntities(req.ctx.body.short_name)
break

case 'name':
newOrg.name = decodeEntities(req.ctx.body.name)
break

case 'authority':
if ('active_roles' in req.ctx.body.authority) {
newOrg.authority.active_roles = req.ctx.body.authority.active_roles
}
break

case 'policies':
if ('id_quota' in req.ctx.body.policies) {
newOrg.policies.id_quota = req.ctx.body.policies.id_quota
}
break

case 'uuid':
return res.status(400).json(error.uuidProvided('org'))
}
})
}

let result = await orgRepo.findOneByShortName(newOrg.short_name) // Find org in MongoDB
if (result) {
Expand All @@ -269,9 +278,12 @@ async function createOrg (req, res, next) {

newOrg.inUse = false
newOrg.UUID = uuid.v4()
newOrg.authority.active_roles = [CONSTANTS.AUTH_ROLE_ENUM.CNA] // default role

if (!newOrg.policies.id_quota) {
if (newOrg.authority.active_roles.length === 0) { // default is to make the Org a CNA if no role is specified
newOrg.authority.active_roles = [CONSTANTS.AUTH_ROLE_ENUM.CNA]
}

if (newOrg.policies.id_quota === undefined) { // set to default quota if none is specified
newOrg.policies.id_quota = CONSTANTS.DEFAULT_ID_QUOTA
}

Expand Down Expand Up @@ -452,9 +464,9 @@ async function createUser (req, res, next) {
newUser.name.suffix = decodeEntities(req.ctx.body.name.suffix)
}
} else if (key === 'org_uuid') {
return res.status(400).json(error.uuidProvided())
return res.status(400).json(error.uuidProvided('org'))
} else if (key === 'uuid') {
return res.status(400).json(error.uuidProvided())
return res.status(400).json(error.uuidProvided('user'))
}
})

Expand Down Expand Up @@ -607,7 +619,7 @@ async function updateUser (req, res, next) {
// Check for correct privileges if the requested changes require them
if (changesRequirePrivilegedRole && !(isAdmin || isSecretariat)) {
logger.info({ uuid: req.ctx.uuid, message: 'The user could not be updated because ' + requesterUsername + ' user is not Org Admin or Secretariat to modify these fields.' })
return res.status(403).json(error.notOrgAdminOrSecretariat())
return res.status(403).json(error.notOrgAdminOrSecretariatUpdate())
}

// check if the new org exist
Expand Down
2 changes: 1 addition & 1 deletion src/controller/org.controller/org.middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ function parseError (req, res, next) {
function isValidUsername (val) {
const value = val.match(/^[A-Za-z0-9\-_@.]{3,128}$/)
if (value == null) {
throw new Error('Username should be 3-50 characters. Allowed characters are alphanumberic and -_@.')
throw new Error('Username should be 3-128 characters. Allowed characters are alphanumberic and -_@.')
}
return true
}
Expand Down
2 changes: 1 addition & 1 deletion src/middleware/error.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ class MiddlewareError extends idrErr.IDRError {
orgDoesNotOwnId (org, id) {
const err = {
error: 'ORG_DOES_NOT_OWN_ID',
message: `It appears that ${org} does not own ${id}`
message: `${org} does not own ${id}`
}
return err
}
Expand Down
28 changes: 27 additions & 1 deletion src/middleware/middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,31 @@ async function validateUser (req, res, next) {
}
}

// Checks that the requester belongs to an org that has the 'BULK_DOWNLOAD' role
async function onlySecretariatOrBulkDownload (req, res, next) {
const org = req.ctx.org
const orgRepo = req.ctx.repositories.getOrgRepository()
const CONSTANTS = getConstants()

try {
const isSec = await orgRepo.isSecretariat(org)
const isBulkDownload = await orgRepo.isBulkDownload(org)
if (!(isSec || isBulkDownload)) { // error message should only mention Secretariat
logger.info({ uuid: req.ctx.uuid, message: org + ' is NOT a ' + CONSTANTS.AUTH_ROLE_ENUM.SECRETARIAT })
return res.status(403).json(error.secretariatOnly())
}

logger.info({
uuid: req.ctx.uuid,
message: 'Confirmed ' + org + ' as a ' + CONSTANTS.AUTH_ROLE_ENUM.SECRETARIAT +
' or as a ' + CONSTANTS.AUTH_ROLE_ENUM.BULK_DOWNLOAD
})
next()
} catch (err) {
next(err)
}
}

// Checks that the requester belongs to an org that has the 'SECREATARIAT' role
async function onlySecretariat (req, res, next) {
const org = req.ctx.org
Expand All @@ -155,7 +180,7 @@ async function onlySecretariat (req, res, next) {
}
}

// Checks that the requester belongs to an org that has the 'SECREATARIAT' role or is a user with the 'ADMIN' role
// Checks that the requester belongs to an org that has the 'SECRETARIAT' role or is a user with the 'ADMIN' role
async function onlySecretariatOrAdmin (req, res, next) {
const org = req.ctx.org
const username = req.ctx.user
Expand Down Expand Up @@ -343,6 +368,7 @@ module.exports = {
optionallyValidateUser,
validateUser,
onlySecretariat,
onlySecretariatOrBulkDownload,
onlySecretariatOrAdmin,
onlyCnas,
onlyOrgWithRole,
Expand Down
4 changes: 4 additions & 0 deletions src/repositories/orgRepository.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ class OrgRepository extends BaseRepository {
async isSecretariatUUID (shortName) {
return utils.isSecretariatUUID(shortName)
}

async isBulkDownload (shortName) {
return utils.isBulkDownload(shortName)
}
}

module.exports = OrgRepository
9 changes: 8 additions & 1 deletion src/utils/error.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,14 @@ class IDRError {
notOrgAdminOrSecretariat () { // super
const err = {}
err.error = 'NOT_ORG_ADMIN_OR_SECRETARIAT'
err.message = 'This information can only be viewed or modified by the Secretariat or Org Admin.'
err.message = 'Users can only be created by the Secretariat or Org Admin.'
return err
}

notOrgAdminOrSecretariatUpdate () {
const err = {}
err.error = 'NOT_ORG_ADMIN_OR_SECRETARIAT_UPDATE'
err.message = 'Contact your org Admin to update fields other than your name.'
return err
}

Expand Down
18 changes: 18 additions & 0 deletions src/utils/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,23 @@ async function isSecretariatUUID (orgUUID) {
return result // org is not secretariat
}

async function isBulkDownload (shortName) {
let result = false
const CONSTANTS = getConstants()
const orgUUID = await getOrgUUID(shortName) // may be null if org does not exists
const bulkDownloadOrgs = await Org.find({ 'authority.active_roles': { $in: [CONSTANTS.AUTH_ROLE_ENUM.BULK_DOWNLOAD] } })

if (orgUUID) {
bulkDownloadOrgs.forEach((obj) => {
if (obj.UUID === orgUUID) {
result = true // org has the bulk download role
}
})
}

return result // org does not have bulk download as a role
}

async function isAdmin (requesterUsername, requesterShortName) {
let result = false
const CONSTANTS = getConstants()
Expand Down Expand Up @@ -140,6 +157,7 @@ function toDate (val) {

module.exports = {
isSecretariat,
isBulkDownload,
isAdmin,
isAdminUUID,
isSecretariatUUID,
Expand Down
27 changes: 27 additions & 0 deletions test-http/src/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,33 @@ def org_admin_headers():
'CVE-API-USER': user
}

@pytest.fixture
def bulk_download_user_headers():
""" create an org and user for BULK_DOWNLOAD testing, return user's headers """
letters = string.ascii_lowercase
org = ''.join(random.choice(letters)
for i in range(10)) # generate random org name
org_res = requests.post(
f'{env.AWG_BASE_URL}/api/org', # secretariat creates an org
headers=utils.BASE_HEADERS,
json={'name': org, 'short_name': org, 'authority': {'active_roles': ['BULK_DOWNLOAD']}, \
'policies': {'id_quota': 0}}
)
user = str(uuid.uuid4()) # dummy user name
user_res = requests.post(
# secretariat creates a user that belongs to the new org
f'{env.AWG_BASE_URL}/api/org/{org}/user',
headers=utils.BASE_HEADERS,
json={'username': user}
)

secret = json.loads(user_res.content.decode())[
'created']['secret'] # dummy user API key
return {
'CVE-API-KEY': secret,
'CVE-API-ORG': org,
'CVE-API-USER': user
}

@pytest.fixture
def reg_user_headers():
Expand Down
Loading

0 comments on commit 5c21ad5

Please sign in to comment.