Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 1 addition & 189 deletions api-docs/openapi.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"openapi": "3.0.2",
"info": {
"version": "2.8.0",
"version": "2.7.5",
"title": "CVE Services API",
"description": "The CVE Services API supports automation tooling for the CVE Program. Credentials are required for most service endpoints. Representatives of <a href='https://www.cve.org/ProgramOrganization/CNAs'>CVE Numbering Authorities (CNAs)</a> should use one of the methods below to obtain credentials: <ul><li>If your organization already has an Organizational Administrator (OA) account for the CVE Services, ask your admin for credentials</li> <li>Contact your Root (<a href='https://www.cve.org/PartnerInformation/ListofPartners/partner/Google'>Google</a>, <a href='https://www.cve.org/PartnerInformation/ListofPartners/partner/INCIBE'>INCIBE</a>, <a href='https://www.cve.org/PartnerInformation/ListofPartners/partner/jpcert'>JPCERT/CC</a>, or <a href='https://www.cve.org/PartnerInformation/ListofPartners/partner/redhat'>Red Hat</a>) or Top-Level Root (<a href='https://www.cve.org/PartnerInformation/ListofPartners/partner/icscert'>CISA ICS</a> or <a href='https://www.cve.org/PartnerInformation/ListofPartners/partner/mitre'>MITRE</a>) to request credentials </ul> <p>CVE data is to be in the JSON 5.2 CVE Record format. Details of the JSON 5.2 schema are located <a href='https://github.com/CVEProject/cve-schema/releases/tag/v5.2.0' target='_blank'>here</a>.</p> <a href='https://cveform.mitre.org/' class='link' target='_blank'>Contact the CVE Services team</a>",
"contact": {
Expand Down Expand Up @@ -5398,110 +5398,6 @@
}
}
},
"/review/{uuid}/approve": {
"put": {
"tags": [
"Review Object"
],
"summary": "Approves a review object and applies changes to the organization (accessible to Secretariat only)",
"description": " <h2>Access Control</h2> <p>User must belong to an organization with the <b>Secretariat</b> role</p>",
"operationId": "approveReviewObject",
"parameters": [
{
"name": "uuid",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "The UUID of the review object"
},
{
"$ref": "#/components/parameters/apiEntityHeader"
},
{
"$ref": "#/components/parameters/apiUserHeader"
},
{
"$ref": "#/components/parameters/apiSecretHeader"
}
],
"responses": {
"200": {
"description": "Returns the updated organization",
"content": {
"application/json": {
"schema": {
"type": "object",
"description": "The updated organization object"
}
}
}
},
"400": {
"description": "Bad Request",
"content": {
"application/json": {
"schema": {
"$ref": "../schemas/errors/bad-request.json"
}
}
}
},
"401": {
"description": "Not Authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "../schemas/errors/generic.json"
}
}
}
},
"403": {
"description": "Forbidden",
"content": {
"application/json": {
"schema": {
"$ref": "../schemas/errors/generic.json"
}
}
}
},
"404": {
"description": "Not Found",
"content": {
"application/json": {
"schema": {
"$ref": "../schemas/errors/generic.json"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "../schemas/errors/generic.json"
}
}
}
}
},
"requestBody": {
"required": false,
"content": {
"application/json": {
"schema": {
"type": "object",
"description": "Optional override data to apply instead of the review object data"
}
}
}
}
}
},
"/review/{uuid}/reject": {
"put": {
"tags": [
Expand Down Expand Up @@ -5593,90 +5489,6 @@
}
}
}
},
"/review/org/": {
"post": {
"tags": [
"Review Object"
],
"summary": "Creates a new review object (accessible to Secretariat only)",
"description": " <h2>Access Control</h2> <p>User must belong to an organization with the <b>Secretariat</b> role</p>",
"operationId": "createReviewObject",
"parameters": [
{
"$ref": "#/components/parameters/apiEntityHeader"
},
{
"$ref": "#/components/parameters/apiUserHeader"
},
{
"$ref": "#/components/parameters/apiSecretHeader"
}
],
"responses": {
"200": {
"description": "Returns the created review object",
"content": {
"application/json": {
"schema": {
"$ref": "../schemas/review/review.json"
}
}
}
},
"400": {
"description": "Bad Request",
"content": {
"application/json": {
"schema": {
"$ref": "../schemas/errors/bad-request.json"
}
}
}
},
"401": {
"description": "Not Authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "../schemas/errors/generic.json"
}
}
}
},
"403": {
"description": "Forbidden",
"content": {
"application/json": {
"schema": {
"$ref": "../schemas/errors/generic.json"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "../schemas/errors/generic.json"
}
}
}
}
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"description": "The review object data"
}
}
}
}
}
}
},
"components": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ async function createConversationForTargetUUID (req, res, next) {

const repo = req.ctx.repositories.getConversationRepository()
const userRepo = req.ctx.repositories.getBaseUserRepository()
const orgRepo = req.ctx.repositories.getBaseOrgRepository()
const requesterOrg = req.ctx.org
const requesterUsername = req.ctx.user
const targetUUID = req.params.uuid
Expand All @@ -48,7 +49,8 @@ async function createConversationForTargetUUID (req, res, next) {
return res.status(400).json(error.invalidConversationObject())
}

const result = await repo.createConversation(targetUUID, body, user, true, { session })
const isSecretariat = await orgRepo.isSecretariatByShortName(req.ctx.org)
const result = await repo.createConversation(targetUUID, body, user, isSecretariat, { session })
await session.commitTransaction()
if (!result) {
return res.status(500).json({ message: 'Failed to create conversation' })
Expand Down
2 changes: 1 addition & 1 deletion src/controller/conversation.controller/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ router.post('/conversation/target/:uuid',
}
*/
mw.validateUser,
mw.onlySecretariat,
mw.onlySecretariatOrAdmin,
param(['uuid']).isUUID(4),
controller.createConversationForTargetUUID
)
Expand Down
7 changes: 7 additions & 0 deletions src/controller/org.controller/error.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ class OrgControllerError extends idrErr.IDRError {
return err
}

aliasCollision (conflictingString) {
const err = {}
err.error = 'ALIAS_COLLISION'
err.message = `The organization could not be created or updated because the string '${conflictingString}' is already in use as a short_name, name, or alias by another organization.`
return err
}

userExists (username) { // org
const err = {}
err.error = 'USER_EXISTS'
Expand Down
28 changes: 26 additions & 2 deletions src/controller/org.controller/org.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,16 @@ async function createOrg (req, res, next) {
return res.status(400).json(error.orgExists(body?.short_name))
}

// Check for alias collisions
const collisionString = await repo.checkAliasCollisions(body?.short_name, body?.name, body?.aliases, null, { session })
if (collisionString) {
logger.info({
uuid: req.ctx.uuid,
message: `${body?.short_name} organization was not created because the string '${collisionString}' collides with another organization's short_name, name, or alias.`
})
await session.abortTransaction()
return res.status(400).json(error.aliasCollision(collisionString))
}
const userRepo = req.ctx.repositories.getBaseUserRepository()
const isSecretariat = await repo.isSecretariatByShortName(req.ctx.org, { session })
const requestingUserUUID = await userRepo.getUserUUID(req.ctx.user, req.ctx.org, { session })
Expand Down Expand Up @@ -363,6 +373,18 @@ async function updateOrg (req, res, next) {
return res.status(403).json(error.duplicateShortname(queryParametersJson.new_short_name))
}

// Check for alias collisions
const shortNameToExclude = shortNameUrlParameter
const collisionString = await orgRepository.checkAliasCollisions(queryParametersJson.new_short_name || shortNameUrlParameter, queryParametersJson.name, queryParametersJson.aliases, shortNameToExclude, { session })
if (collisionString) {
logger.info({
uuid: req.ctx.uuid,
message: `${shortNameUrlParameter} organization could not be updated because the string '${collisionString}' collides with another organization's short_name, name, or alias.`
})
await session.abortTransaction()
return res.status(400).json(error.aliasCollision(collisionString))
}

const userRepo = req.ctx.repositories.getBaseUserRepository()
const requestingUserUUID = await userRepo.getUserUUID(req.ctx.user, req.ctx.org, { session })
const isSecretariat = await orgRepository.isSecretariatByShortName(req.ctx.org, { session })
Expand Down Expand Up @@ -472,7 +494,8 @@ async function createUser (req, res, next) {
return res.status(400).json(error.userLimitReached())
}

returnValue = await userRepo.createUser(orgShortName, body, { session, upsert: true }, !!req.useRegistry)
const requestingUserUUID = await userRepo.getUserUUID(req.ctx.user, req.ctx.org, { session })
returnValue = await userRepo.createUser(orgShortName, body, { session, upsert: true }, !!req.useRegistry, requestingUserUUID)
await session.commitTransaction()
} catch (error) {
await session.abortTransaction()
Expand Down Expand Up @@ -647,7 +670,8 @@ async function updateUser (req, res, next) {
}
}

const payload = await userRepo.updateUser(usernameParams, shortNameParams, queryParametersJson, { session }, !!req.useRegistry)
const requestingUserUUID = await userRepo.getUserUUID(req.ctx.user, req.ctx.org, { session })
const payload = await userRepo.updateUser(usernameParams, shortNameParams, queryParametersJson, { session }, !!req.useRegistry, requestingUserUUID)
await session.commitTransaction()
return res.status(200).json({ message: `${usernameParams} was successfully updated.`, updated: payload })
} catch (err) {
Expand Down
7 changes: 7 additions & 0 deletions src/controller/registry-org.controller/error.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ class RegistryOrgControllerError extends idrErr.IDRError {
return err
}

aliasCollision (conflictingString) {
const err = {}
err.error = 'ALIAS_COLLISION'
err.message = `The organization could not be created or updated because the string '${conflictingString}' is already in use as a short_name, name, or alias by another organization.`
return err
}

userExists (username) { // org
const err = {}
err.error = 'USER_EXISTS'
Expand Down
28 changes: 26 additions & 2 deletions src/controller/registry-org.controller/registry-org.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ async function getOrg (req, res, next) {
// fetch conversation
const conversation = await conversationRepo.getAllByTargetUUID(returnValue.UUID, isSecretariat)
if (isSecretariat) {
returnValue.conversation = conversation?.length ? _.map(conversation, c => _.omit(c, ['__v', '_id', 'UUID', 'previous_conversation_uuid', 'next_conversation_uuid', 'target_uuid'])) : undefined
returnValue.conversation = conversation?.length ? _.map(conversation, c => _.omit(c, ['__v', '_id', 'previous_conversation_uuid', 'next_conversation_uuid', 'target_uuid'])) : undefined
} else {
returnValue.conversation = conversation?.length ? _.map(conversation, c => _.omit(c, ['__v', '_id', 'UUID', 'previous_conversation_uuid', 'next_conversation_uuid', 'target_uuid', 'visibility'])) : undefined
}
Expand Down Expand Up @@ -177,6 +177,17 @@ async function createOrg (req, res, next) {
return res.status(400).json(error.orgExists(body?.short_name))
}

// Check for alias collisions
const collisionString = await repo.checkAliasCollisions(body?.short_name, body?.name, body?.aliases, null, { session })
if (collisionString) {
logger.info({
uuid: req.ctx.uuid,
message: `${body?.short_name} organization was not created because the string '${collisionString}' collides with another organization's short_name, name, or alias.`
})
await session.abortTransaction()
return res.status(400).json(error.aliasCollision(collisionString))
}

const userRepo = req.ctx.repositories.getBaseUserRepository()
const requestingUserUUID = await userRepo.getUserUUID(req.ctx.user, req.ctx.org, { session })
// Create the org – repo.createOrg will handle field mapping
Expand Down Expand Up @@ -327,6 +338,18 @@ async function updateOrg (req, res, next) {
return res.status(400).json(error.duplicateShortname(body?.short_name))
}

// Check for alias collisions
const shortNameToExclude = shortName
const collisionString = await repo.checkAliasCollisions(body?.short_name || shortName, body?.name, body?.aliases, shortNameToExclude, { session })
if (collisionString) {
logger.info({
uuid: req.ctx.uuid,
message: `${shortName} organization could not be updated because the string '${collisionString}' collides with another organization's short_name, name, or alias.`
})
await session.abortTransaction()
return res.status(400).json(error.aliasCollision(collisionString))
}

// Handle secretariat "stomping" of pending review objects
if (isSecretariat) {
const reviewRepo = req.ctx.repositories.getReviewObjectRepository()
Expand Down Expand Up @@ -591,7 +614,8 @@ async function createUserByOrg (req, res, next) {
return res.status(400).json(error.userLimitReached())
}

returnValue = await userRepo.createUser(orgShortName, body, { session, upsert: true }, true)
const requestingUserUUID = await userRepo.getUserUUID(req.ctx.user, req.ctx.org, { session })
returnValue = await userRepo.createUser(orgShortName, body, { session, upsert: true }, true, requestingUserUUID)
await session.commitTransaction()
} catch (error) {
await session.abortTransaction()
Expand Down
Loading
Loading