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
2 changes: 1 addition & 1 deletion api-docs/openapi.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"openapi": "3.0.2",
"info": {
"info": {
"version": "2.7.0",
"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>",
Expand Down
1 change: 0 additions & 1 deletion src/controller/org.controller/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -639,7 +639,6 @@ router.put('/registry/org/:shortname',
*/
mw.useRegistry(),
mw.validateUser,
mw.onlySecretariat,
parseError,
parsePutParams,
registryOrgController.UPDATE_ORG
Expand Down
31 changes: 22 additions & 9 deletions src/repositories/baseOrgRepository.js
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,7 @@ class BaseOrgRepository extends BaseRepository {

const legacyOrg = await legacyOrgRepo.findOneByShortName(shortName, options)
let registryOrg = await this.findOneByShortName(shortName, options)
const originalRegistryOrgObject = registryOrg.toObject()

// Both legacy and registry
if (incomingParameters?.new_short_name) {
Expand Down Expand Up @@ -624,17 +625,15 @@ class BaseOrgRepository extends BaseRepository {
const auditRepo = new AuditRepository()
// Check if an audit document exists, if not we need to create one first and seed it with the existing org data
if (!(await auditRepo.findOneByTargetUUID(registryOrg.UUID, options))) {
const currentRegistryOrg = await this.findOneByShortName(shortName, options)
await auditRepo.appendToAuditHistoryForOrg(
registryOrg.UUID,
currentRegistryOrg.toObject(),
originalRegistryOrgObject,
requestingUserUUID,
options
)
}
// Get the org state before save for comparison
const beforeUpdateOrg = await this.findOneByShortName(shortName, options)
const beforeUpdateObject = beforeUpdateOrg.toObject()
const beforeUpdateObject = originalRegistryOrgObject
const afterUpdateObject = registryOrg.toObject()

// Clean objects for comparison (remove Mongoose metadata)
Expand Down Expand Up @@ -728,6 +727,7 @@ class BaseOrgRepository extends BaseRepository {
const conversationRepo = new ConversationRepository()
const legacyOrg = await legacyOrgRepo.findOneByShortName(shortName, options)
const registryOrg = await this.findOneByShortName(shortName, options)
const originalRegistryOrgObject = registryOrg.toObject()
// check to see if there is a PENDING review object:
const reviewObject = await reviewObjectRepo.getOrgReviewObjectByOrgShortname(shortName, isSecretariat, options)
const { conversation, ...incomingOrgBody } = incomingOrg
Expand All @@ -742,6 +742,22 @@ class BaseOrgRepository extends BaseRepository {
legacyObjectRaw = this.convertRegistryToLegacy(incomingOrgBody)
}

if (incomingOrg?.new_short_name) {
const newName = incomingOrg.new_short_name

// 1. Update the Mongoose instances
registryOrg.short_name = newName
legacyOrg.short_name = newName

// 2. Update the raw tracking objects so lodash.merge doesn't restore the old short_name
registryObjectRaw.short_name = newName
legacyObjectRaw.short_name = newName

// 3. Remove new_short_name from the raw objects so it doesn't merge into the DB
delete registryObjectRaw.new_short_name
delete legacyObjectRaw.new_short_name
delete incomingOrg.new_short_name // Keeping for existing logic
}
// Checking for joint approval fields
const jointApprovalFieldsRegistry = this.getJointApprovalFields(registryOrg, registryObjectRaw)
const jointApprovalFieldsLegacy = this.getJointApprovalFields(legacyOrg, legacyObjectRaw, true)
Expand All @@ -750,7 +766,6 @@ class BaseOrgRepository extends BaseRepository {
let jointApprovalRegistry = null

// If there are no joint approval fields, merge the original and updated objects. Otherwise, update the registry object and legacy object separately considering joint approval.

// Dealing with roles requires a bit of extra control.
const originalRoles = registryOrg.authority

Expand Down Expand Up @@ -795,17 +810,15 @@ class BaseOrgRepository extends BaseRepository {
const auditRepo = new AuditRepository()
// Check if an audit document exists, if not we need to create one first and seed it with the existing org data
if (!(await auditRepo.findOneByTargetUUID(registryOrg.UUID, options))) {
const currentRegistryOrg = await this.findOneByShortName(shortName, options)
await auditRepo.appendToAuditHistoryForOrg(
registryOrg.UUID,
currentRegistryOrg.toObject(),
originalRegistryOrgObject,
requestingUserUUID,
{ ...options, upsert: true }
)
}
// Get the org state before save for comparison
const beforeUpdateOrg = await this.findOneByShortName(shortName, options)
const beforeUpdateObject = beforeUpdateOrg.toObject()
const beforeUpdateObject = originalRegistryOrgObject
const afterUpdateObject = registryOrg.toObject()

// Clean objects for comparison (remove Mongoose metadata)
Expand Down
35 changes: 35 additions & 0 deletions test/integration-tests/registry-org/registryOrgCRUDTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,41 @@ describe('Testing /registryOrg endpoints', () => {
expect(res.body.updated.hard_quota).to.equal(createdOrg.hard_quota)
})
})
it('Updates a registry organization\'s short name and role simultaneously to verify read-after-write audit logic', async () => {
// First create a temporary org
const tempOrg = {
short_name: 'temp_org_for_update',
long_name: 'Temp Org',
authority: ['CNA'],
hard_quota: 10
}
await chai.request(app)
.post('/api/registry/org')
.set(secretariatHeaders)
.send(tempOrg)

// Now update it: change short_name and authority
await chai.request(app)
.put(`/api/registry/org/${tempOrg.short_name}`)
.set(secretariatHeaders)
.send({
...tempOrg,
new_short_name: 'temp_org_updated_name',
authority: ['SECRETARIAT']
})
.then((res, err) => {
expect(err).to.be.undefined
expect(res).to.have.status(200)
expect(res.body.message).to.equal(tempOrg.short_name + ' organization was successfully updated.')
expect(res.body.updated.short_name).to.equal('temp_org_updated_name')
expect(res.body.updated.authority).to.include('SECRETARIAT')
})

// Cleanup
await chai.request(app)
.delete('/api/registry/org/temp_org_updated_name')
.set(secretariatHeaders)
})
})
context('Negative Tests', () => {
it('Fails to update a registry organization that does not exist', async () => {
Expand Down
Loading