From 327d5034d3192dcfd79ce91c99067d5fda528f7f Mon Sep 17 00:00:00 2001 From: James Dalphond Date: Mon, 10 Aug 2026 09:48:01 -0400 Subject: [PATCH 01/10] feat(registry-org): add ROOT metadata, CNA last-resort flag, and integration coverage --- schemas/registry-org/CNAOrg.json | 3 ++ schemas/registry-org/RootOrg.json | 9 ++++ .../create-registry-org-request.json | 8 +++ .../create-registry-org-response.json | 8 +++ .../get-registry-org-response.json | 8 +++ .../list-registry-orgs-response.json | 8 +++ .../update-registry-org-request.json | 8 +++ .../update-registry-org-response.json | 8 +++ src/constants/index.js | 4 +- src/model/cnaorg.js | 1 + src/model/rootorg.js | 5 +- .../registry-org/registryOrgCRUDTest.js | 19 ++++++- .../registry-org/rootOrgTest.js | 50 ++++++++++++++++++- 13 files changed, 134 insertions(+), 5 deletions(-) diff --git a/schemas/registry-org/CNAOrg.json b/schemas/registry-org/CNAOrg.json index c852f1e79..8e32aa932 100644 --- a/schemas/registry-org/CNAOrg.json +++ b/schemas/registry-org/CNAOrg.json @@ -89,6 +89,9 @@ "minimum": 0, "maximum": 100000 }, + "is_last_resort": { + "type": "boolean" + }, "oversees": { "type": "array", "uniqueItems": true, diff --git a/schemas/registry-org/RootOrg.json b/schemas/registry-org/RootOrg.json index a6e8a9d7e..a60691ee5 100644 --- a/schemas/registry-org/RootOrg.json +++ b/schemas/registry-org/RootOrg.json @@ -107,6 +107,15 @@ }, "top_level_root": { "$ref": "/BaseOrg#/properties/top_level_root" + }, + "is_top_level_root": { + "type": "boolean" + }, + "charter_or_scope": { + "type": "string" + }, + "disclosure_policy": { + "type": "string" } }, "required": [ diff --git a/schemas/registry-org/create-registry-org-request.json b/schemas/registry-org/create-registry-org-request.json index b1917cf18..2e583abc4 100644 --- a/schemas/registry-org/create-registry-org-request.json +++ b/schemas/registry-org/create-registry-org-request.json @@ -44,6 +44,14 @@ "type": "string", "description": "Indicates if the organization is a root or top-level root" }, + "is_top_level_root": { + "type": "boolean", + "description": "Indicates whether a ROOT organization is top-level" + }, + "is_last_resort": { + "type": "boolean", + "description": "Indicates whether a CNA organization is a CNA of last resort." + }, "users": { "type": "array", "items": { diff --git a/schemas/registry-org/create-registry-org-response.json b/schemas/registry-org/create-registry-org-response.json index 2071119a1..5161b509d 100644 --- a/schemas/registry-org/create-registry-org-response.json +++ b/schemas/registry-org/create-registry-org-response.json @@ -77,6 +77,14 @@ "type": "string", "description": "Indicates if the organization is a root or top-level root" }, + "is_top_level_root": { + "type": "boolean", + "description": "Indicates whether a ROOT organization is top-level" + }, + "is_last_resort": { + "type": "boolean", + "description": "Indicates whether a CNA organization is a CNA of last resort" + }, "users": { "type": "array", "items": { diff --git a/schemas/registry-org/get-registry-org-response.json b/schemas/registry-org/get-registry-org-response.json index 86fc75326..a1ba1fc1a 100644 --- a/schemas/registry-org/get-registry-org-response.json +++ b/schemas/registry-org/get-registry-org-response.json @@ -41,6 +41,14 @@ "type": "string", "description": "Indicates if the organization is a root or top-level root" }, + "is_top_level_root": { + "type": "boolean", + "description": "Indicates whether a ROOT organization is top-level" + }, + "is_last_resort": { + "type": "boolean", + "description": "Indicates whether a CNA organization is a CNA of last resort" + }, "reports_to": { "type": [ "string", diff --git a/schemas/registry-org/list-registry-orgs-response.json b/schemas/registry-org/list-registry-orgs-response.json index e4956e545..40bc74add 100644 --- a/schemas/registry-org/list-registry-orgs-response.json +++ b/schemas/registry-org/list-registry-orgs-response.json @@ -70,6 +70,14 @@ "type": "string", "description": "Indicates if the organization is a root or top-level root" }, + "is_top_level_root": { + "type": "boolean", + "description": "Indicates whether a ROOT organization is top-level" + }, + "is_last_resort": { + "type": "boolean", + "description": "Indicates whether a CNA organization is a CNA of last resort" + }, "reports_to": { "type": [ "string", diff --git a/schemas/registry-org/update-registry-org-request.json b/schemas/registry-org/update-registry-org-request.json index f388a40f7..60cbe400a 100644 --- a/schemas/registry-org/update-registry-org-request.json +++ b/schemas/registry-org/update-registry-org-request.json @@ -60,6 +60,14 @@ "type": "string", "description": "Indicates if the organization is a root or top-level root" }, + "is_top_level_root": { + "type": "boolean", + "description": "Indicates whether a ROOT organization is top-level" + }, + "is_last_resort": { + "type": "boolean", + "description": "Indicates whether a CNA organization is a CNA of last resort" + }, "users": { "type": "array", "items": { diff --git a/schemas/registry-org/update-registry-org-response.json b/schemas/registry-org/update-registry-org-response.json index 4662e4872..387ef9f87 100644 --- a/schemas/registry-org/update-registry-org-response.json +++ b/schemas/registry-org/update-registry-org-response.json @@ -66,6 +66,14 @@ "type": "string", "description": "Indicates if the organization is a root or top-level root" }, + "is_top_level_root": { + "type": "boolean", + "description": "Indicates whether a ROOT organization is top-level" + }, + "is_last_resort": { + "type": "boolean", + "description": "Indicates whether a CNA organization is a CNA of last resort" + }, "users": { "type": "array", "items": { diff --git a/src/constants/index.js b/src/constants/index.js index 4a6c1d38b..89eb0c8a8 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -44,7 +44,7 @@ function getConstants () { USER_ROLES: [ 'ADMIN' ], - JOINT_APPROVAL_FIELDS: ['short_name', 'long_name', 'authority', 'aliases', 'oversees', 'top_level_root', 'charter_or_scope', 'product_list', 'disclosure_policy', 'partner_role_type', 'partner_number', 'program_data.cve_website_update_date', 'program_data.cve_website_update_needed', 'program_data.status', 'advisory_locations', 'advisory_location_require_credentials', 'vulnerability_advisory_location_for_web_scraping', 'tl_root_start_date', 'is_cna_discussion_list', 'id_quota'], + JOINT_APPROVAL_FIELDS: ['short_name', 'long_name', 'authority', 'aliases', 'oversees', 'top_level_root', 'is_top_level_root', 'is_last_resort', 'charter_or_scope', 'product_list', 'disclosure_policy', 'partner_role_type', 'partner_number', 'program_data.cve_website_update_date', 'program_data.cve_website_update_needed', 'program_data.status', 'advisory_locations', 'advisory_location_require_credentials', 'vulnerability_advisory_location_for_web_scraping', 'tl_root_start_date', 'is_cna_discussion_list', 'id_quota'], JOINT_APPROVAL_FIELDS_LEGACY: ['short_name', 'name', 'authority.active_roles', 'policies.id_quota'], ORG_EXCLUDED_FIELDS: ['__t', '__v', '_id', 'inUse', 'in_use'], ORG_RESTRICTED_FIELDS: ['program_data'], @@ -55,6 +55,8 @@ function getConstants () { 'program_data.cve_website_update_needed', 'program_data.status', 'top_level_root', + 'is_top_level_root', + 'is_last_resort', 'oversees' ], USER_ROLE_ENUM: { diff --git a/src/model/cnaorg.js b/src/model/cnaorg.js index 2e7519c1e..20a7e083a 100644 --- a/src/model/cnaorg.js +++ b/src/model/cnaorg.js @@ -14,6 +14,7 @@ const validate = ajv.compile(CnaOrgSchema) const schema = { oversees: [String], id_quota: Number, + is_last_resort: Boolean, charter_or_scope: String, disclosure_policy: String, product_list: String diff --git a/src/model/rootorg.js b/src/model/rootorg.js index 1517ec65c..ad4dbeea9 100644 --- a/src/model/rootorg.js +++ b/src/model/rootorg.js @@ -12,7 +12,10 @@ ajv.addSchema(BaseOrgSchema) const validate = ajv.compile(RootOrgSchema) const schema = { - oversees: [String] + oversees: [String], + is_top_level_root: Boolean, + charter_or_scope: String, + disclosure_policy: String } const options = { discriminatorKey: 'kind' } diff --git a/test/integration-tests/registry-org/registryOrgCRUDTest.js b/test/integration-tests/registry-org/registryOrgCRUDTest.js index 8b0cf5320..c8d6b21db 100644 --- a/test/integration-tests/registry-org/registryOrgCRUDTest.js +++ b/test/integration-tests/registry-org/registryOrgCRUDTest.js @@ -19,7 +19,8 @@ const testRegistryOrg = { partner_number: 'Initial Partner Number', partner_country: 'US', advisory_locations: ['https://example.com/advisories'], - charter_or_scope: 'This is a normal string, not a URI' + charter_or_scope: 'This is a normal string, not a URI', + is_last_resort: true } let createdOrg @@ -82,6 +83,9 @@ describe('Testing /registry/org endpoints', () => { expect(res.body.created).to.haveOwnProperty('charter_or_scope') expect(res.body.created.charter_or_scope).to.equal(testRegistryOrg.charter_or_scope) + expect(res.body.created).to.haveOwnProperty('is_last_resort') + expect(res.body.created.is_last_resort).to.equal(true) + expect(res.body.created).to.haveOwnProperty('program_data') expect(res.body.created.program_data.status).to.equal('inactive') expect(res.body.created.program_data).to.haveOwnProperty('partner_inactive_date') @@ -671,7 +675,8 @@ describe('Testing /registry/org endpoints', () => { partner_role_type: ['Researcher', 'Vendor'], partner_number: 'Updated Partner Number', partner_country: 'UK', - advisory_locations: ['https://example.com/updated_advisories'] + advisory_locations: ['https://example.com/updated_advisories'], + is_last_resort: false }) .then((res, err) => { expect(err).to.be.undefined @@ -708,6 +713,16 @@ describe('Testing /registry/org endpoints', () => { expect(res.body.updated).to.haveOwnProperty('advisory_locations') expect(res.body.updated.advisory_locations).to.deep.equal(['https://example.com/updated_advisories']) + expect(res.body.updated.is_last_resort).to.equal(false) + }) + }) + it('Retrieves the CNA last-resort flag', async () => { + await chai.request(app) + .get('/api/registry/org/registry_org_test') + .set(secretariatHeaders) + .then((res) => { + expect(res).to.have.status(200) + expect(res.body.is_last_resort).to.equal(false) }) }) it('Allows Secretariat to update program_data', async () => { diff --git a/test/integration-tests/registry-org/rootOrgTest.js b/test/integration-tests/registry-org/rootOrgTest.js index 18c4bd7e1..5774cb011 100644 --- a/test/integration-tests/registry-org/rootOrgTest.js +++ b/test/integration-tests/registry-org/rootOrgTest.js @@ -13,7 +13,10 @@ let rootAdminHeaders const testRootOrg = { short_name: 'root_org_test_4', long_name: 'Root Org Test', - authority: ['ROOT'] + authority: ['ROOT'], + is_top_level_root: true, + charter_or_scope: 'Coordinates CVE Numbering Authorities.', + disclosure_policy: 'Discloses vulnerabilities after coordinated remediation.' } let createdOrg let reportingOrg @@ -31,6 +34,9 @@ describe('Testing ROOT Organization Type', () => { expect(res).to.have.status(200) expect(res.body.message).to.equal(testRootOrg.short_name + ' organization was successfully created.') expect(res.body.created.authority).to.deep.equal(['ROOT']) + expect(res.body.created.is_top_level_root).to.equal(true) + expect(res.body.created.charter_or_scope).to.equal(testRootOrg.charter_or_scope) + expect(res.body.created.disclosure_policy).to.equal(testRootOrg.disclosure_policy) createdOrg = res.body.created delete createdOrg.created delete createdOrg.last_updated @@ -55,6 +61,34 @@ describe('Testing ROOT Organization Type', () => { expect(res.body.message).to.equal('Parameters were invalid') }) }) + + it('Secretariat can update and retrieve ROOT-specific fields', async () => { + await chai.request(app) + .put(`/api/registry/org/${testRootOrg.short_name}`) + .set(secretariatHeaders) + .send({ + ...createdOrg, + is_top_level_root: false, + charter_or_scope: 'Updated ROOT charter.', + disclosure_policy: 'Updated ROOT disclosure policy.' + }) + .then((res) => { + expect(res).to.have.status(200) + expect(res.body.updated.is_top_level_root).to.equal(false) + expect(res.body.updated.charter_or_scope).to.equal('Updated ROOT charter.') + expect(res.body.updated.disclosure_policy).to.equal('Updated ROOT disclosure policy.') + }) + + await chai.request(app) + .get(`/api/registry/org/${testRootOrg.short_name}`) + .set(secretariatHeaders) + .then((res) => { + expect(res).to.have.status(200) + expect(res.body.is_top_level_root).to.equal(false) + expect(res.body.charter_or_scope).to.equal('Updated ROOT charter.') + expect(res.body.disclosure_policy).to.equal('Updated ROOT disclosure policy.') + }) + }) }) context('Managing reports-to relationships', () => { @@ -238,6 +272,20 @@ describe('Testing ROOT Organization Type', () => { }) }) + it('ROOT admin cannot edit is_top_level_root', async () => { + await chai.request(app) + .put(`/api/registry/org/${testRootOrg.short_name}`) + .set(rootAdminHeaders) + .send({ + ...createdOrg, + is_top_level_root: true + }) + .then((res) => { + expect(res).to.have.status(403) + expect(res.body.error).to.equal('SECRETARIAT_ONLY') + }) + }) + it('ROOT admin cannot set oversees through the standard PUT endpoint', async () => { await chai.request(app) .put(`/api/registry/org/${testRootOrg.short_name}`) From 2cf2dfef700f9b591e73e1a243a33ed3f0bddd38 Mon Sep 17 00:00:00 2001 From: James Dalphond Date: Mon, 10 Aug 2026 10:46:09 -0400 Subject: [PATCH 02/10] Removing secretariat only fields from joint approval array --- src/constants/index.js | 2 +- test/integration-tests/registry-org/rootOrgTest.js | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/constants/index.js b/src/constants/index.js index 89eb0c8a8..c4db3affc 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -44,7 +44,7 @@ function getConstants () { USER_ROLES: [ 'ADMIN' ], - JOINT_APPROVAL_FIELDS: ['short_name', 'long_name', 'authority', 'aliases', 'oversees', 'top_level_root', 'is_top_level_root', 'is_last_resort', 'charter_or_scope', 'product_list', 'disclosure_policy', 'partner_role_type', 'partner_number', 'program_data.cve_website_update_date', 'program_data.cve_website_update_needed', 'program_data.status', 'advisory_locations', 'advisory_location_require_credentials', 'vulnerability_advisory_location_for_web_scraping', 'tl_root_start_date', 'is_cna_discussion_list', 'id_quota'], + JOINT_APPROVAL_FIELDS: ['short_name', 'long_name', 'authority', 'aliases', 'oversees', 'top_level_root', 'charter_or_scope', 'product_list', 'disclosure_policy', 'partner_role_type', 'partner_number', 'program_data.cve_website_update_date', 'program_data.cve_website_update_needed', 'program_data.status', 'advisory_locations', 'advisory_location_require_credentials', 'vulnerability_advisory_location_for_web_scraping', 'tl_root_start_date', 'is_cna_discussion_list', 'id_quota'], JOINT_APPROVAL_FIELDS_LEGACY: ['short_name', 'name', 'authority.active_roles', 'policies.id_quota'], ORG_EXCLUDED_FIELDS: ['__t', '__v', '_id', 'inUse', 'in_use'], ORG_RESTRICTED_FIELDS: ['program_data'], diff --git a/test/integration-tests/registry-org/rootOrgTest.js b/test/integration-tests/registry-org/rootOrgTest.js index 5774cb011..05f9d1fba 100644 --- a/test/integration-tests/registry-org/rootOrgTest.js +++ b/test/integration-tests/registry-org/rootOrgTest.js @@ -245,11 +245,13 @@ describe('Testing ROOT Organization Type', () => { }) it('ROOT admin can update their own org', async () => { + const rootAdminPayload = { ...createdOrg } + delete rootAdminPayload.is_top_level_root await chai.request(app) .put(`/api/registry/org/${testRootOrg.short_name}`) .set(rootAdminHeaders) .send({ - ...createdOrg, + ...rootAdminPayload, long_name: 'Updated Root Org Test' }) .then((res) => { @@ -287,11 +289,13 @@ describe('Testing ROOT Organization Type', () => { }) it('ROOT admin cannot set oversees through the standard PUT endpoint', async () => { + const rootAdminPayload = { ...createdOrg } + delete rootAdminPayload.is_top_level_root await chai.request(app) .put(`/api/registry/org/${testRootOrg.short_name}`) .set(rootAdminHeaders) .send({ - ...createdOrg, + ...rootAdminPayload, oversees: [reportingOrg.UUID] }) .then((res) => { From b6264c544739158cff7a2d7223449f8988b6e98f Mon Sep 17 00:00:00 2001 From: James Dalphond Date: Tue, 25 Aug 2026 10:14:56 -0400 Subject: [PATCH 03/10] version number bump --- api-docs/openapi.json | 2 +- package.json | 2 +- src/swagger.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api-docs/openapi.json b/api-docs/openapi.json index 84fe59bcf..3031f2cf1 100644 --- a/api-docs/openapi.json +++ b/api-docs/openapi.json @@ -1,7 +1,7 @@ { "openapi": "3.0.2", "info": { - "version": "2.8.4", + "version": "2.8.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 CVE Numbering Authorities (CNAs) should use one of the methods below to obtain credentials:
  • If your organization already has an Organizational Administrator (OA) account for the CVE Services, ask your admin for credentials
  • Contact your Root (Google, INCIBE, JPCERT/CC, or Red Hat) or Top-Level Root (CISA ICS or MITRE) to request credentials

CVE data is to be in the JSON 5.2 CVE Record format. Details of the JSON 5.2 schema are located here.

Contact the CVE Services team", "contact": { diff --git a/package.json b/package.json index 00409c63f..b63447db2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cve-services", "author": "Automation Working Group", - "version": "2.8.4", + "version": "2.8.5", "license": "(CC0)", "devDependencies": { "@faker-js/faker": "^7.6.0", diff --git a/src/swagger.js b/src/swagger.js index 5edc22173..fd775f5cd 100644 --- a/src/swagger.js +++ b/src/swagger.js @@ -21,7 +21,7 @@ const fullCnaContainerRequest = require('../schemas/cve/create-cve-record-cna-re /* eslint-disable no-multi-str */ const doc = { info: { - version: '2.8.4', + version: '2.8.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 \ From 25a1b5c0d4707789e39a1106cf62e9d24b5245fa Mon Sep 17 00:00:00 2001 From: James Dalphond Date: Tue, 25 Aug 2026 10:14:56 -0400 Subject: [PATCH 04/10] version number bump --- api-docs/openapi.json | 2 +- package.json | 2 +- src/swagger.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api-docs/openapi.json b/api-docs/openapi.json index 84fe59bcf..3031f2cf1 100644 --- a/api-docs/openapi.json +++ b/api-docs/openapi.json @@ -1,7 +1,7 @@ { "openapi": "3.0.2", "info": { - "version": "2.8.4", + "version": "2.8.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 CVE Numbering Authorities (CNAs) should use one of the methods below to obtain credentials:
  • If your organization already has an Organizational Administrator (OA) account for the CVE Services, ask your admin for credentials
  • Contact your Root (Google, INCIBE, JPCERT/CC, or Red Hat) or Top-Level Root (CISA ICS or MITRE) to request credentials

CVE data is to be in the JSON 5.2 CVE Record format. Details of the JSON 5.2 schema are located here.

Contact the CVE Services team", "contact": { diff --git a/package.json b/package.json index 00409c63f..b63447db2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cve-services", "author": "Automation Working Group", - "version": "2.8.4", + "version": "2.8.5", "license": "(CC0)", "devDependencies": { "@faker-js/faker": "^7.6.0", diff --git a/src/swagger.js b/src/swagger.js index 5edc22173..fd775f5cd 100644 --- a/src/swagger.js +++ b/src/swagger.js @@ -21,7 +21,7 @@ const fullCnaContainerRequest = require('../schemas/cve/create-cve-record-cna-re /* eslint-disable no-multi-str */ const doc = { info: { - version: '2.8.4', + version: '2.8.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 \ From 230e5c4f71e4e715aff871e98c0b88ef70f4c6c0 Mon Sep 17 00:00:00 2001 From: James Dalphond Date: Mon, 10 Aug 2026 09:48:01 -0400 Subject: [PATCH 05/10] feat(registry-org): add ROOT metadata, CNA last-resort flag, and integration coverage --- schemas/registry-org/CNAOrg.json | 3 ++ schemas/registry-org/RootOrg.json | 9 ++++ .../create-registry-org-request.json | 8 +++ .../create-registry-org-response.json | 8 +++ .../get-registry-org-response.json | 8 +++ .../list-registry-orgs-response.json | 8 +++ .../update-registry-org-request.json | 8 +++ .../update-registry-org-response.json | 8 +++ src/constants/index.js | 4 +- src/model/cnaorg.js | 1 + src/model/rootorg.js | 5 +- .../registry-org/registryOrgCRUDTest.js | 19 ++++++- .../registry-org/rootOrgTest.js | 50 ++++++++++++++++++- 13 files changed, 134 insertions(+), 5 deletions(-) diff --git a/schemas/registry-org/CNAOrg.json b/schemas/registry-org/CNAOrg.json index c852f1e79..8e32aa932 100644 --- a/schemas/registry-org/CNAOrg.json +++ b/schemas/registry-org/CNAOrg.json @@ -89,6 +89,9 @@ "minimum": 0, "maximum": 100000 }, + "is_last_resort": { + "type": "boolean" + }, "oversees": { "type": "array", "uniqueItems": true, diff --git a/schemas/registry-org/RootOrg.json b/schemas/registry-org/RootOrg.json index a6e8a9d7e..a60691ee5 100644 --- a/schemas/registry-org/RootOrg.json +++ b/schemas/registry-org/RootOrg.json @@ -107,6 +107,15 @@ }, "top_level_root": { "$ref": "/BaseOrg#/properties/top_level_root" + }, + "is_top_level_root": { + "type": "boolean" + }, + "charter_or_scope": { + "type": "string" + }, + "disclosure_policy": { + "type": "string" } }, "required": [ diff --git a/schemas/registry-org/create-registry-org-request.json b/schemas/registry-org/create-registry-org-request.json index b1917cf18..2e583abc4 100644 --- a/schemas/registry-org/create-registry-org-request.json +++ b/schemas/registry-org/create-registry-org-request.json @@ -44,6 +44,14 @@ "type": "string", "description": "Indicates if the organization is a root or top-level root" }, + "is_top_level_root": { + "type": "boolean", + "description": "Indicates whether a ROOT organization is top-level" + }, + "is_last_resort": { + "type": "boolean", + "description": "Indicates whether a CNA organization is a CNA of last resort." + }, "users": { "type": "array", "items": { diff --git a/schemas/registry-org/create-registry-org-response.json b/schemas/registry-org/create-registry-org-response.json index 2071119a1..5161b509d 100644 --- a/schemas/registry-org/create-registry-org-response.json +++ b/schemas/registry-org/create-registry-org-response.json @@ -77,6 +77,14 @@ "type": "string", "description": "Indicates if the organization is a root or top-level root" }, + "is_top_level_root": { + "type": "boolean", + "description": "Indicates whether a ROOT organization is top-level" + }, + "is_last_resort": { + "type": "boolean", + "description": "Indicates whether a CNA organization is a CNA of last resort" + }, "users": { "type": "array", "items": { diff --git a/schemas/registry-org/get-registry-org-response.json b/schemas/registry-org/get-registry-org-response.json index 86fc75326..a1ba1fc1a 100644 --- a/schemas/registry-org/get-registry-org-response.json +++ b/schemas/registry-org/get-registry-org-response.json @@ -41,6 +41,14 @@ "type": "string", "description": "Indicates if the organization is a root or top-level root" }, + "is_top_level_root": { + "type": "boolean", + "description": "Indicates whether a ROOT organization is top-level" + }, + "is_last_resort": { + "type": "boolean", + "description": "Indicates whether a CNA organization is a CNA of last resort" + }, "reports_to": { "type": [ "string", diff --git a/schemas/registry-org/list-registry-orgs-response.json b/schemas/registry-org/list-registry-orgs-response.json index e4956e545..40bc74add 100644 --- a/schemas/registry-org/list-registry-orgs-response.json +++ b/schemas/registry-org/list-registry-orgs-response.json @@ -70,6 +70,14 @@ "type": "string", "description": "Indicates if the organization is a root or top-level root" }, + "is_top_level_root": { + "type": "boolean", + "description": "Indicates whether a ROOT organization is top-level" + }, + "is_last_resort": { + "type": "boolean", + "description": "Indicates whether a CNA organization is a CNA of last resort" + }, "reports_to": { "type": [ "string", diff --git a/schemas/registry-org/update-registry-org-request.json b/schemas/registry-org/update-registry-org-request.json index f388a40f7..60cbe400a 100644 --- a/schemas/registry-org/update-registry-org-request.json +++ b/schemas/registry-org/update-registry-org-request.json @@ -60,6 +60,14 @@ "type": "string", "description": "Indicates if the organization is a root or top-level root" }, + "is_top_level_root": { + "type": "boolean", + "description": "Indicates whether a ROOT organization is top-level" + }, + "is_last_resort": { + "type": "boolean", + "description": "Indicates whether a CNA organization is a CNA of last resort" + }, "users": { "type": "array", "items": { diff --git a/schemas/registry-org/update-registry-org-response.json b/schemas/registry-org/update-registry-org-response.json index 4662e4872..387ef9f87 100644 --- a/schemas/registry-org/update-registry-org-response.json +++ b/schemas/registry-org/update-registry-org-response.json @@ -66,6 +66,14 @@ "type": "string", "description": "Indicates if the organization is a root or top-level root" }, + "is_top_level_root": { + "type": "boolean", + "description": "Indicates whether a ROOT organization is top-level" + }, + "is_last_resort": { + "type": "boolean", + "description": "Indicates whether a CNA organization is a CNA of last resort" + }, "users": { "type": "array", "items": { diff --git a/src/constants/index.js b/src/constants/index.js index 4a6c1d38b..89eb0c8a8 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -44,7 +44,7 @@ function getConstants () { USER_ROLES: [ 'ADMIN' ], - JOINT_APPROVAL_FIELDS: ['short_name', 'long_name', 'authority', 'aliases', 'oversees', 'top_level_root', 'charter_or_scope', 'product_list', 'disclosure_policy', 'partner_role_type', 'partner_number', 'program_data.cve_website_update_date', 'program_data.cve_website_update_needed', 'program_data.status', 'advisory_locations', 'advisory_location_require_credentials', 'vulnerability_advisory_location_for_web_scraping', 'tl_root_start_date', 'is_cna_discussion_list', 'id_quota'], + JOINT_APPROVAL_FIELDS: ['short_name', 'long_name', 'authority', 'aliases', 'oversees', 'top_level_root', 'is_top_level_root', 'is_last_resort', 'charter_or_scope', 'product_list', 'disclosure_policy', 'partner_role_type', 'partner_number', 'program_data.cve_website_update_date', 'program_data.cve_website_update_needed', 'program_data.status', 'advisory_locations', 'advisory_location_require_credentials', 'vulnerability_advisory_location_for_web_scraping', 'tl_root_start_date', 'is_cna_discussion_list', 'id_quota'], JOINT_APPROVAL_FIELDS_LEGACY: ['short_name', 'name', 'authority.active_roles', 'policies.id_quota'], ORG_EXCLUDED_FIELDS: ['__t', '__v', '_id', 'inUse', 'in_use'], ORG_RESTRICTED_FIELDS: ['program_data'], @@ -55,6 +55,8 @@ function getConstants () { 'program_data.cve_website_update_needed', 'program_data.status', 'top_level_root', + 'is_top_level_root', + 'is_last_resort', 'oversees' ], USER_ROLE_ENUM: { diff --git a/src/model/cnaorg.js b/src/model/cnaorg.js index 2e7519c1e..20a7e083a 100644 --- a/src/model/cnaorg.js +++ b/src/model/cnaorg.js @@ -14,6 +14,7 @@ const validate = ajv.compile(CnaOrgSchema) const schema = { oversees: [String], id_quota: Number, + is_last_resort: Boolean, charter_or_scope: String, disclosure_policy: String, product_list: String diff --git a/src/model/rootorg.js b/src/model/rootorg.js index 1517ec65c..ad4dbeea9 100644 --- a/src/model/rootorg.js +++ b/src/model/rootorg.js @@ -12,7 +12,10 @@ ajv.addSchema(BaseOrgSchema) const validate = ajv.compile(RootOrgSchema) const schema = { - oversees: [String] + oversees: [String], + is_top_level_root: Boolean, + charter_or_scope: String, + disclosure_policy: String } const options = { discriminatorKey: 'kind' } diff --git a/test/integration-tests/registry-org/registryOrgCRUDTest.js b/test/integration-tests/registry-org/registryOrgCRUDTest.js index 8b0cf5320..c8d6b21db 100644 --- a/test/integration-tests/registry-org/registryOrgCRUDTest.js +++ b/test/integration-tests/registry-org/registryOrgCRUDTest.js @@ -19,7 +19,8 @@ const testRegistryOrg = { partner_number: 'Initial Partner Number', partner_country: 'US', advisory_locations: ['https://example.com/advisories'], - charter_or_scope: 'This is a normal string, not a URI' + charter_or_scope: 'This is a normal string, not a URI', + is_last_resort: true } let createdOrg @@ -82,6 +83,9 @@ describe('Testing /registry/org endpoints', () => { expect(res.body.created).to.haveOwnProperty('charter_or_scope') expect(res.body.created.charter_or_scope).to.equal(testRegistryOrg.charter_or_scope) + expect(res.body.created).to.haveOwnProperty('is_last_resort') + expect(res.body.created.is_last_resort).to.equal(true) + expect(res.body.created).to.haveOwnProperty('program_data') expect(res.body.created.program_data.status).to.equal('inactive') expect(res.body.created.program_data).to.haveOwnProperty('partner_inactive_date') @@ -671,7 +675,8 @@ describe('Testing /registry/org endpoints', () => { partner_role_type: ['Researcher', 'Vendor'], partner_number: 'Updated Partner Number', partner_country: 'UK', - advisory_locations: ['https://example.com/updated_advisories'] + advisory_locations: ['https://example.com/updated_advisories'], + is_last_resort: false }) .then((res, err) => { expect(err).to.be.undefined @@ -708,6 +713,16 @@ describe('Testing /registry/org endpoints', () => { expect(res.body.updated).to.haveOwnProperty('advisory_locations') expect(res.body.updated.advisory_locations).to.deep.equal(['https://example.com/updated_advisories']) + expect(res.body.updated.is_last_resort).to.equal(false) + }) + }) + it('Retrieves the CNA last-resort flag', async () => { + await chai.request(app) + .get('/api/registry/org/registry_org_test') + .set(secretariatHeaders) + .then((res) => { + expect(res).to.have.status(200) + expect(res.body.is_last_resort).to.equal(false) }) }) it('Allows Secretariat to update program_data', async () => { diff --git a/test/integration-tests/registry-org/rootOrgTest.js b/test/integration-tests/registry-org/rootOrgTest.js index 18c4bd7e1..5774cb011 100644 --- a/test/integration-tests/registry-org/rootOrgTest.js +++ b/test/integration-tests/registry-org/rootOrgTest.js @@ -13,7 +13,10 @@ let rootAdminHeaders const testRootOrg = { short_name: 'root_org_test_4', long_name: 'Root Org Test', - authority: ['ROOT'] + authority: ['ROOT'], + is_top_level_root: true, + charter_or_scope: 'Coordinates CVE Numbering Authorities.', + disclosure_policy: 'Discloses vulnerabilities after coordinated remediation.' } let createdOrg let reportingOrg @@ -31,6 +34,9 @@ describe('Testing ROOT Organization Type', () => { expect(res).to.have.status(200) expect(res.body.message).to.equal(testRootOrg.short_name + ' organization was successfully created.') expect(res.body.created.authority).to.deep.equal(['ROOT']) + expect(res.body.created.is_top_level_root).to.equal(true) + expect(res.body.created.charter_or_scope).to.equal(testRootOrg.charter_or_scope) + expect(res.body.created.disclosure_policy).to.equal(testRootOrg.disclosure_policy) createdOrg = res.body.created delete createdOrg.created delete createdOrg.last_updated @@ -55,6 +61,34 @@ describe('Testing ROOT Organization Type', () => { expect(res.body.message).to.equal('Parameters were invalid') }) }) + + it('Secretariat can update and retrieve ROOT-specific fields', async () => { + await chai.request(app) + .put(`/api/registry/org/${testRootOrg.short_name}`) + .set(secretariatHeaders) + .send({ + ...createdOrg, + is_top_level_root: false, + charter_or_scope: 'Updated ROOT charter.', + disclosure_policy: 'Updated ROOT disclosure policy.' + }) + .then((res) => { + expect(res).to.have.status(200) + expect(res.body.updated.is_top_level_root).to.equal(false) + expect(res.body.updated.charter_or_scope).to.equal('Updated ROOT charter.') + expect(res.body.updated.disclosure_policy).to.equal('Updated ROOT disclosure policy.') + }) + + await chai.request(app) + .get(`/api/registry/org/${testRootOrg.short_name}`) + .set(secretariatHeaders) + .then((res) => { + expect(res).to.have.status(200) + expect(res.body.is_top_level_root).to.equal(false) + expect(res.body.charter_or_scope).to.equal('Updated ROOT charter.') + expect(res.body.disclosure_policy).to.equal('Updated ROOT disclosure policy.') + }) + }) }) context('Managing reports-to relationships', () => { @@ -238,6 +272,20 @@ describe('Testing ROOT Organization Type', () => { }) }) + it('ROOT admin cannot edit is_top_level_root', async () => { + await chai.request(app) + .put(`/api/registry/org/${testRootOrg.short_name}`) + .set(rootAdminHeaders) + .send({ + ...createdOrg, + is_top_level_root: true + }) + .then((res) => { + expect(res).to.have.status(403) + expect(res.body.error).to.equal('SECRETARIAT_ONLY') + }) + }) + it('ROOT admin cannot set oversees through the standard PUT endpoint', async () => { await chai.request(app) .put(`/api/registry/org/${testRootOrg.short_name}`) From e8d121160ccbdec95d9b89f1ebdedaad6bfefb48 Mon Sep 17 00:00:00 2001 From: James Dalphond Date: Mon, 10 Aug 2026 10:46:09 -0400 Subject: [PATCH 06/10] Removing secretariat only fields from joint approval array --- src/constants/index.js | 2 +- test/integration-tests/registry-org/rootOrgTest.js | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/constants/index.js b/src/constants/index.js index 89eb0c8a8..c4db3affc 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -44,7 +44,7 @@ function getConstants () { USER_ROLES: [ 'ADMIN' ], - JOINT_APPROVAL_FIELDS: ['short_name', 'long_name', 'authority', 'aliases', 'oversees', 'top_level_root', 'is_top_level_root', 'is_last_resort', 'charter_or_scope', 'product_list', 'disclosure_policy', 'partner_role_type', 'partner_number', 'program_data.cve_website_update_date', 'program_data.cve_website_update_needed', 'program_data.status', 'advisory_locations', 'advisory_location_require_credentials', 'vulnerability_advisory_location_for_web_scraping', 'tl_root_start_date', 'is_cna_discussion_list', 'id_quota'], + JOINT_APPROVAL_FIELDS: ['short_name', 'long_name', 'authority', 'aliases', 'oversees', 'top_level_root', 'charter_or_scope', 'product_list', 'disclosure_policy', 'partner_role_type', 'partner_number', 'program_data.cve_website_update_date', 'program_data.cve_website_update_needed', 'program_data.status', 'advisory_locations', 'advisory_location_require_credentials', 'vulnerability_advisory_location_for_web_scraping', 'tl_root_start_date', 'is_cna_discussion_list', 'id_quota'], JOINT_APPROVAL_FIELDS_LEGACY: ['short_name', 'name', 'authority.active_roles', 'policies.id_quota'], ORG_EXCLUDED_FIELDS: ['__t', '__v', '_id', 'inUse', 'in_use'], ORG_RESTRICTED_FIELDS: ['program_data'], diff --git a/test/integration-tests/registry-org/rootOrgTest.js b/test/integration-tests/registry-org/rootOrgTest.js index 5774cb011..05f9d1fba 100644 --- a/test/integration-tests/registry-org/rootOrgTest.js +++ b/test/integration-tests/registry-org/rootOrgTest.js @@ -245,11 +245,13 @@ describe('Testing ROOT Organization Type', () => { }) it('ROOT admin can update their own org', async () => { + const rootAdminPayload = { ...createdOrg } + delete rootAdminPayload.is_top_level_root await chai.request(app) .put(`/api/registry/org/${testRootOrg.short_name}`) .set(rootAdminHeaders) .send({ - ...createdOrg, + ...rootAdminPayload, long_name: 'Updated Root Org Test' }) .then((res) => { @@ -287,11 +289,13 @@ describe('Testing ROOT Organization Type', () => { }) it('ROOT admin cannot set oversees through the standard PUT endpoint', async () => { + const rootAdminPayload = { ...createdOrg } + delete rootAdminPayload.is_top_level_root await chai.request(app) .put(`/api/registry/org/${testRootOrg.short_name}`) .set(rootAdminHeaders) .send({ - ...createdOrg, + ...rootAdminPayload, oversees: [reportingOrg.UUID] }) .then((res) => { From 6a78f40391c871d8393c74643ecd3ef1c3cd9f9f Mon Sep 17 00:00:00 2001 From: James Dalphond Date: Tue, 25 Aug 2026 10:14:56 -0400 Subject: [PATCH 07/10] version number bump --- api-docs/openapi.json | 2 +- package.json | 2 +- src/swagger.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api-docs/openapi.json b/api-docs/openapi.json index 84fe59bcf..3031f2cf1 100644 --- a/api-docs/openapi.json +++ b/api-docs/openapi.json @@ -1,7 +1,7 @@ { "openapi": "3.0.2", "info": { - "version": "2.8.4", + "version": "2.8.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 CVE Numbering Authorities (CNAs) should use one of the methods below to obtain credentials:
  • If your organization already has an Organizational Administrator (OA) account for the CVE Services, ask your admin for credentials
  • Contact your Root (Google, INCIBE, JPCERT/CC, or Red Hat) or Top-Level Root (CISA ICS or MITRE) to request credentials

CVE data is to be in the JSON 5.2 CVE Record format. Details of the JSON 5.2 schema are located here.

Contact the CVE Services team", "contact": { diff --git a/package.json b/package.json index 00409c63f..b63447db2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cve-services", "author": "Automation Working Group", - "version": "2.8.4", + "version": "2.8.5", "license": "(CC0)", "devDependencies": { "@faker-js/faker": "^7.6.0", diff --git a/src/swagger.js b/src/swagger.js index 5edc22173..fd775f5cd 100644 --- a/src/swagger.js +++ b/src/swagger.js @@ -21,7 +21,7 @@ const fullCnaContainerRequest = require('../schemas/cve/create-cve-record-cna-re /* eslint-disable no-multi-str */ const doc = { info: { - version: '2.8.4', + version: '2.8.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 \ From 771cb6719f689182cf6a7ff89c45234ca66ee37e Mon Sep 17 00:00:00 2001 From: James Dalphond Date: Tue, 25 Aug 2026 12:27:41 -0400 Subject: [PATCH 08/10] feat(org): require joint approval for last-resort and top-level flags --- src/constants/index.js | 4 +--- src/controller/registry.controller/index.js | 2 ++ .../registry-org/rootOrgTest.js | 16 +++++++++++++--- .../review-object/reviewObjectTest.js | 2 ++ 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/constants/index.js b/src/constants/index.js index c4db3affc..3bc1dff19 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -44,7 +44,7 @@ function getConstants () { USER_ROLES: [ 'ADMIN' ], - JOINT_APPROVAL_FIELDS: ['short_name', 'long_name', 'authority', 'aliases', 'oversees', 'top_level_root', 'charter_or_scope', 'product_list', 'disclosure_policy', 'partner_role_type', 'partner_number', 'program_data.cve_website_update_date', 'program_data.cve_website_update_needed', 'program_data.status', 'advisory_locations', 'advisory_location_require_credentials', 'vulnerability_advisory_location_for_web_scraping', 'tl_root_start_date', 'is_cna_discussion_list', 'id_quota'], + JOINT_APPROVAL_FIELDS: ['short_name', 'long_name', 'authority', 'aliases', 'oversees', 'top_level_root', 'is_top_level_root', 'is_last_resort', 'charter_or_scope', 'product_list', 'disclosure_policy', 'partner_role_type', 'partner_number', 'program_data.cve_website_update_date', 'program_data.cve_website_update_needed', 'program_data.status', 'advisory_locations', 'advisory_location_require_credentials', 'vulnerability_advisory_location_for_web_scraping', 'tl_root_start_date', 'is_cna_discussion_list', 'id_quota'], JOINT_APPROVAL_FIELDS_LEGACY: ['short_name', 'name', 'authority.active_roles', 'policies.id_quota'], ORG_EXCLUDED_FIELDS: ['__t', '__v', '_id', 'inUse', 'in_use'], ORG_RESTRICTED_FIELDS: ['program_data'], @@ -55,8 +55,6 @@ function getConstants () { 'program_data.cve_website_update_needed', 'program_data.status', 'top_level_root', - 'is_top_level_root', - 'is_last_resort', 'oversees' ], USER_ROLE_ENUM: { diff --git a/src/controller/registry.controller/index.js b/src/controller/registry.controller/index.js index 2735f8cdf..7350a564d 100644 --- a/src/controller/registry.controller/index.js +++ b/src/controller/registry.controller/index.js @@ -557,6 +557,8 @@ router.put('/registry/org/:shortname',
  • aliases
  • oversees
  • top_level_root
  • +
  • is_top_level_root
  • +
  • is_last_resort
  • charter_or_scope
  • product_list
  • disclosure_policy
  • diff --git a/test/integration-tests/registry-org/rootOrgTest.js b/test/integration-tests/registry-org/rootOrgTest.js index 05f9d1fba..e61254eaf 100644 --- a/test/integration-tests/registry-org/rootOrgTest.js +++ b/test/integration-tests/registry-org/rootOrgTest.js @@ -274,7 +274,7 @@ describe('Testing ROOT Organization Type', () => { }) }) - it('ROOT admin cannot edit is_top_level_root', async () => { + it('ROOT admin can request a change to is_top_level_root through joint approval', async () => { await chai.request(app) .put(`/api/registry/org/${testRootOrg.short_name}`) .set(rootAdminHeaders) @@ -283,8 +283,18 @@ describe('Testing ROOT Organization Type', () => { is_top_level_root: true }) .then((res) => { - expect(res).to.have.status(403) - expect(res.body.error).to.equal('SECRETARIAT_ONLY') + expect(res).to.have.status(200) + expect(res.body.message).to.include('joint approval is required') + expect(res.body.updated.is_top_level_root).to.equal(false) + }) + + return chai.request(app) + .get(`/api/review/org/${testRootOrg.short_name}`) + .set(secretariatHeaders) + .then((res) => { + expect(res).to.have.status(200) + expect(res.body.status).to.equal('pending') + expect(res.body).to.have.nested.property('new_review_data.is_top_level_root', true) }) }) diff --git a/test/integration-tests/review-object/reviewObjectTest.js b/test/integration-tests/review-object/reviewObjectTest.js index d9fbe661f..a65412170 100644 --- a/test/integration-tests/review-object/reviewObjectTest.js +++ b/test/integration-tests/review-object/reviewObjectTest.js @@ -215,6 +215,7 @@ describe('Review Object Controller Integration Tests', () => { updateData.long_name = 'Approve Test Organization' updateData.authority = ['CNA'] updateData.id_quota = 1000 + updateData.is_last_resort = true updateData.contact_info = { websites: ['https://www.example.com'] } const res = await chai .request(app) @@ -232,6 +233,7 @@ describe('Review Object Controller Integration Tests', () => { expect(reviewRes.body).to.have.property('uuid') expect(reviewRes.body.status).to.equal('pending') expect(reviewRes.body).to.have.nested.property('new_review_data.long_name', 'Approve Test Organization') + expect(reviewRes.body).to.have.nested.property('new_review_data.is_last_resort', true) expect(reviewRes.body.new_review_data.contact_info.websites[0]).to.equal('https://www.example.com') approveTestReviewUUID = reviewRes.body.uuid }) From e8554632261b63ade67a879a666e712b9b9e676c Mon Sep 17 00:00:00 2001 From: James Dalphond Date: Tue, 25 Aug 2026 14:06:58 -0400 Subject: [PATCH 09/10] j-a fields --- src/constants/index.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/constants/index.js b/src/constants/index.js index 89eb0c8a8..3bc1dff19 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -55,8 +55,6 @@ function getConstants () { 'program_data.cve_website_update_needed', 'program_data.status', 'top_level_root', - 'is_top_level_root', - 'is_last_resort', 'oversees' ], USER_ROLE_ENUM: { From 403842b12b60c944402467f8f308cd9edb373734 Mon Sep 17 00:00:00 2001 From: James Dalphond Date: Wed, 26 Aug 2026 14:51:09 -0400 Subject: [PATCH 10/10] Require joint approval for organization contact and partner fields --- src/constants/index.js | 2 +- .../registryOrgWithJointReviewTest.js | 20 +++++++++++++++---- .../review-object/reviewObjectTest.js | 2 +- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/constants/index.js b/src/constants/index.js index 3bc1dff19..034b570dc 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -44,7 +44,7 @@ function getConstants () { USER_ROLES: [ 'ADMIN' ], - JOINT_APPROVAL_FIELDS: ['short_name', 'long_name', 'authority', 'aliases', 'oversees', 'top_level_root', 'is_top_level_root', 'is_last_resort', 'charter_or_scope', 'product_list', 'disclosure_policy', 'partner_role_type', 'partner_number', 'program_data.cve_website_update_date', 'program_data.cve_website_update_needed', 'program_data.status', 'advisory_locations', 'advisory_location_require_credentials', 'vulnerability_advisory_location_for_web_scraping', 'tl_root_start_date', 'is_cna_discussion_list', 'id_quota'], + JOINT_APPROVAL_FIELDS: ['short_name', 'long_name', 'authority', 'aliases', 'oversees', 'top_level_root', 'is_top_level_root', 'is_last_resort', 'charter_or_scope', 'product_list', 'disclosure_policy', 'contact_info', 'partner_role_type', 'partner_number', 'partner_country', 'program_data.cve_website_update_date', 'program_data.cve_website_update_needed', 'program_data.status', 'advisory_locations', 'advisory_location_require_credentials', 'vulnerability_advisory_location_for_web_scraping', 'industry', 'tl_root_start_date', 'is_cna_discussion_list', 'id_quota'], JOINT_APPROVAL_FIELDS_LEGACY: ['short_name', 'name', 'authority.active_roles', 'policies.id_quota'], ORG_EXCLUDED_FIELDS: ['__t', '__v', '_id', 'inUse', 'in_use'], ORG_RESTRICTED_FIELDS: ['program_data'], diff --git a/test/integration-tests/registry-org/registryOrgWithJointReviewTest.js b/test/integration-tests/registry-org/registryOrgWithJointReviewTest.js index 28cd37220..6ff18ccdd 100644 --- a/test/integration-tests/registry-org/registryOrgWithJointReviewTest.js +++ b/test/integration-tests/registry-org/registryOrgWithJointReviewTest.js @@ -175,7 +175,13 @@ describe('Testing Joint approval', () => { await chai.request(app) .put('/api/registry/org/non_secretariat_org') .set(nonAdminHeaders) - .send({ ...testRegistryOrgForReview, short_name: 'new_non_secretariat_org', contact_info: { websites: ['https://www.example.com'] } }) + .send({ + ...testRegistryOrgForReview, + short_name: 'new_non_secretariat_org', + contact_info: { websites: ['https://www.example.com'] }, + partner_country: 'US', + industry: 'Technology' + }) .then((res) => { expect(res).to.have.status(200) expect(res.body.message).to.contain('organization was successfully updated, but joint approval is required for some fields.') @@ -192,6 +198,9 @@ describe('Testing Joint approval', () => { expect(res.body).to.have.property('status', 'pending') expect(res.body.target_object_uuid).to.equal(orgUUID) expect(res.body.new_review_data.short_name).to.equal('new_non_secretariat_org') + expect(res.body.new_review_data.contact_info.websites).to.deep.equal(['https://www.example.com']) + expect(res.body.new_review_data.partner_country).to.equal('US') + expect(res.body.new_review_data.industry).to.equal('Technology') reviewUUID = res.body.uuid }) }) @@ -203,7 +212,9 @@ describe('Testing Joint approval', () => { expect(err).to.be.undefined expect(res).to.have.status(200) expect(res.body.short_name).to.equal('non_secretariat_org') - expect(res.body.contact_info.websites[0]).to.equal('https://www.example.com') + expect((res.body.contact_info && res.body.contact_info.websites) || []).to.not.include('https://www.example.com') + expect(res.body).to.not.haveOwnProperty('partner_country') + expect(res.body).to.not.haveOwnProperty('industry') }) }) it('Secretariat can approve the ORG review with body parameter', async function () { @@ -306,7 +317,7 @@ describe('Testing Joint approval', () => { expect(err).to.be.undefined expect(res).to.have.status(200) expect(res.body.short_name).to.equal('non_with_comments') - expect(res.body.contact_info.websites[0]).to.equal('https://www.example.com') + expect((res.body.contact_info && res.body.contact_info.websites) || []).to.not.include('https://www.example.com') }) }) it('Secretariat leaves a public comment on the org review', async () => { @@ -380,6 +391,7 @@ describe('Testing Joint approval', () => { expect(res).to.have.status(200) expect(res.body.short_name).to.equal('new_non_with_comments') expect(res.body.id_quota).to.equal(1000) + expect(res.body.contact_info.websites[0]).to.equal('https://www.example.com') }) }) }) @@ -500,7 +512,7 @@ describe('Testing Joint approval', () => { expect(res.body.message).to.contain('organization was successfully updated, but joint approval is required for some fields.') expect(res.body.updated.UUID).to.equal(orgUUID) expect(res.body.updated.short_name).to.equal(testRegistryOrgForNewShortNameReview.short_name) - expect(res.body.updated.contact_info.websites[0]).to.equal('https://www.example.com/new-short-name') + expect((res.body.updated.contact_info && res.body.updated.contact_info.websites) || []).to.not.include('https://www.example.com/new-short-name') }) }) it('Check to see if the new_short_name review was created', async () => { diff --git a/test/integration-tests/review-object/reviewObjectTest.js b/test/integration-tests/review-object/reviewObjectTest.js index a65412170..a22410f31 100644 --- a/test/integration-tests/review-object/reviewObjectTest.js +++ b/test/integration-tests/review-object/reviewObjectTest.js @@ -223,7 +223,7 @@ describe('Review Object Controller Integration Tests', () => { .set({ ...constants.nonSecretariatUserHeaders2 }) .send(updateData) expect(res).to.have.status(200) - expect(res.body.updated.contact_info.websites[0]).to.equal('https://www.example.com') + expect((res.body.updated.contact_info && res.body.updated.contact_info.websites) || []).to.not.include('https://www.example.com') const reviewRes = await chai .request(app)