Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
75 changes: 74 additions & 1 deletion api-docs/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
},
"servers": [
{
"url": "https://cveawg-dev.mitre.org/api"
"url": "urlplaceholder"
}
],
"paths": {
Expand Down Expand Up @@ -3199,6 +3199,79 @@
}
}
},
"/registry/org/cnas": {
"get": {
"tags": [
"Registry Organization"
],
"summary": "Lists active CNAs in the CVE.org partner-list format (Secretariat only)",
"description": "<h2>Access Control</h2><p>User must belong to an organization with the <b>Secretariat</b> role.</p><h2>Expected Behavior</h2><p><b>Secretariat:</b> Retrieves the active CNA list built from registry organization data in the CVE.org partner-list format.</p>",
"operationId": "registryOrgActiveCnas",
"parameters": [
{
"$ref": "#/components/parameters/apiEntityHeader"
},
{
"$ref": "#/components/parameters/apiUserHeader"
},
{
"$ref": "#/components/parameters/apiSecretHeader"
}
],
"responses": {
"200": {
"description": "Returns active CNAs in the CVE.org partner-list format",
"content": {
"application/json": {
"schema": {
"$ref": "../schemas/registry-org/list-active-cnas-response.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"
}
}
}
}
}
}
},
"/registry/org/{shortname}/users": {
"get": {
"tags": [
Expand Down
98 changes: 98 additions & 0 deletions schemas/registry-org/list-active-cnas-response.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"link": {
"type": "object",
"required": ["label", "url"],
"properties": {
"label": { "type": "string" },
"url": { "type": "string" }
},
"additionalProperties": false
},
"policyLink": {
"type": "object",
"required": ["label", "language", "url"],
"properties": {
"label": { "type": "string" },
"language": { "type": "string" },
"url": { "type": "string" }
},
"additionalProperties": false
},
"email": {
"type": "object",
"required": ["label", "emailAddr"],
"properties": {
"label": { "type": "string" },
"emailAddr": { "type": "string" }
},
"additionalProperties": false
},
"contact": {
"type": "object",
"required": ["email", "contact", "form"],
"properties": {
"email": { "type": "array", "items": { "$ref": "#/definitions/email" } },
"contact": { "type": "array", "items": { "$ref": "#/definitions/link" } },
"form": { "type": "array", "items": { "$ref": "#/definitions/link" } }
},
"additionalProperties": false
},
"orgReference": {
"type": "object",
"required": ["shortName", "organizationName"],
"properties": {
"shortName": { "type": "string" },
"organizationName": { "type": "string" }
},
"additionalProperties": false
},
"role": {
"type": "object",
"required": ["helpText", "role"],
"properties": {
"helpText": { "type": "string" },
"role": { "type": "string" }
},
"additionalProperties": false
}
},
"type": "array",
"items": {
"type": "object",
"required": ["shortName", "cnaID", "organizationName", "scope", "contact", "disclosurePolicy", "securityAdvisories", "resources", "CNA", "country"],
"properties": {
"shortName": { "type": "string" },
"cnaID": { "type": "string" },
"organizationName": { "type": "string" },
"scope": { "type": "string" },
"contact": { "type": "array", "items": { "$ref": "#/definitions/contact" } },
"disclosurePolicy": { "type": "array", "items": { "$ref": "#/definitions/policyLink" } },
"securityAdvisories": {
"type": "object",
"required": ["alerts", "advisories"],
"properties": {
"alerts": { "type": "array", "items": { "$ref": "#/definitions/link" } },
"advisories": { "type": "array", "items": { "$ref": "#/definitions/link" } }
},
"additionalProperties": false
},
"resources": { "type": "array", "items": { "$ref": "#/definitions/link" } },
"CNA": {
"type": "object",
"required": ["isRoot", "root", "type", "TLR", "roles"],
"properties": {
"isRoot": { "type": "boolean" },
"root": { "$ref": "#/definitions/orgReference" },
"type": { "type": "array", "items": { "type": "string" } },
"TLR": { "$ref": "#/definitions/orgReference" },
"roles": { "type": "array", "items": { "$ref": "#/definitions/role" } }
},
"additionalProperties": false
},
"country": { "type": "string" }
},
"additionalProperties": false
}
}
58 changes: 58 additions & 0 deletions src/controller/registry.controller/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,64 @@ router.get('/registry/org',
registryOrgController.ALL_ORGS
)

router.get('/registry/org/cnas',
/*
#swagger.tags = ['Registry Organization']
#swagger.operationId = 'registryOrgActiveCnas'
#swagger.summary = 'Lists active CNAs in the CVE.org partner-list format (Secretariat only)'
#swagger.description = '<h2>Access Control</h2><p>User must belong to an organization with the <b>Secretariat</b> role.</p><h2>Expected Behavior</h2><p><b>Secretariat:</b> Retrieves the active CNA list built from registry organization data in the CVE.org partner-list format.</p>'
#swagger.parameters['$ref'] = [
'#/components/parameters/apiEntityHeader',
'#/components/parameters/apiUserHeader',
'#/components/parameters/apiSecretHeader'
]
#swagger.responses[200] = {
description: 'Returns active CNAs in the CVE.org partner-list format',
content: {
'application/json': {
schema: { $ref: '../schemas/registry-org/list-active-cnas-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[500] = {
description: 'Internal Server Error',
content: {
'application/json': {
schema: { $ref: '../schemas/errors/generic.json' }
}
}
}
*/
mw.useRegistry(),
mw.validateUser,
Comment thread
david-rocca marked this conversation as resolved.
Dismissed
mw.onlySecretariat,
registryOrgController.ACTIVE_CNAS
)

router.get('/registry/org/:shortname/users',
/*
#swagger.tags = ['Registry User']
Expand Down
70 changes: 70 additions & 0 deletions src/controller/registry.controller/org.registry.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,74 @@ async function getAllOrgs (req, res, next) {
}
}

function asUrlEntries (urls, label) {
return (urls || []).filter(Boolean).map(url => ({ label, url: url.trim() }))
}

function asPublicOrgReference (org) {
return org
? { shortName: org.short_name || 'n/a', organizationName: org.long_name || 'n/a' }
: { shortName: 'n/a', organizationName: 'n/a' }
}

function mapAuthorityRoles (authority, isRoot, isTopLevelRoot) {
const roles = []
if (isRoot) roles.push({ helpText: '', role: isTopLevelRoot ? 'Top-Level Root' : 'Root' })
if (authority.includes('CNA')) roles.push({ helpText: '', role: isRoot ? 'CNA-LR' : 'CNA' })
if (authority.includes('ADP')) roles.push({ helpText: '', role: 'ADP' })
if (authority.includes('SECRETARIAT')) roles.push({ helpText: '', role: 'Secretariat' })
return roles.length ? roles : [{ helpText: '', role: 'CNA' }]
}

function mapActiveCnaToPublicFormat (org) {
const emails = (org.contact_info?.emails || []).filter(Boolean).map(emailAddr => ({ label: 'Email', emailAddr }))
const contacts = asUrlEntries(org.contact_info?.websites, 'Website')
const authority = org.authority || []
const isRoot = org.__t === 'RootOrg' || authority.includes('ROOT')
const isTopLevelRoot = isRoot && String(org.top_level_root).toLowerCase() === 'true'

return {
shortName: org.short_name || '',
cnaID: org.partner_number || '',
organizationName: org.long_name || '',
scope: org.charter_or_scope || '',
contact: [{ email: emails, contact: contacts, form: [] }],
disclosurePolicy: asUrlEntries((org.disclosure_policy || '').split(';'), 'Policy').map(policy => ({ ...policy, language: '' })),
securityAdvisories: { alerts: [], advisories: asUrlEntries(org.advisory_locations, 'Advisories') },
resources: [],
CNA: {
isRoot,
root: isRoot ? asPublicOrgReference() : asPublicOrgReference(org._root),
type: org.partner_role_type || [],
TLR: isTopLevelRoot ? asPublicOrgReference() : asPublicOrgReference(org._tlr),
roles: mapAuthorityRoles(authority, isRoot, isTopLevelRoot)
},
country: org.partner_country || ''
}
}

/**
* Retrieves active CNA partners in the CVE.org partner-list response format.
*
* @async
* @function getActiveCnas
* @param {object} req - The Express request object.
* @param {object} res - The Express response object.
* @param {function} next - The next middleware function.
* @returns {Promise<void>} - A promise that resolves when the response is sent.
* @description This endpoint is restricted to Secretariat users and reads active CNA data from the registry database.
* Called by GET /api/registry/org/cnas
*/
async function getActiveCnas (req, res, next) {
try {
const repo = req.ctx.repositories.getBaseOrgRepository()
const activeCnas = await repo.getActiveCnas()
return res.status(200).json(activeCnas.map(mapActiveCnaToPublicFormat))
} catch (err) {
next(err)
}
}

/**
* Retrieves information about a specific registry organization.
*
Expand Down Expand Up @@ -1005,6 +1073,8 @@ async function editConversationForOrg (req, res, next) {

module.exports = {
ALL_ORGS: getAllOrgs,
ACTIVE_CNAS: getActiveCnas,
mapActiveCnaToPublicFormat,
SINGLE_ORG: getOrg,
CREATE_ORG: createOrg,
UPDATE_ORG: updateOrg,
Expand Down
Loading
Loading