Skip to content

Commit c37b744

Browse files
authored
Merge pull request #1761 from CVEProject/v2.7.5_feature
V2.7.5 feature
2 parents b55cef8 + 343d047 commit c37b744

20 files changed

Lines changed: 326 additions & 80 deletions

File tree

api-docs/openapi.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"openapi": "3.0.2",
33
"info": {
4-
"version": "2.7.4",
4+
"version": "2.7.5",
55
"title": "CVE Services API",
66
"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>",
77
"contact": {

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "cve-services",
33
"author": "Automation Working Group",
4-
"version": "2.7.4",
4+
"version": "2.7.5",
55
"license": "(CC0)",
66
"devDependencies": {
77
"@faker-js/faker": "^7.6.0",

src/controller/registry-org.controller/registry-org.controller.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -315,9 +315,9 @@ async function updateOrg (req, res, next) {
315315

316316
// Compare and set status accordingly
317317
if (_.isEqual(cleanPending, cleanIncoming)) {
318-
await reviewRepo.approveReviewOrgObject(pendingReview.uuid, { session })
318+
await reviewRepo.approveReviewOrgObject(pendingReview.uuid, req.ctx.user, { session })
319319
} else {
320-
await reviewRepo.rejectReviewOrgObject(pendingReview.uuid, { session })
320+
await reviewRepo.rejectReviewOrgObject(pendingReview.uuid, req.ctx.user, { session })
321321
}
322322
}
323323
}
@@ -652,7 +652,10 @@ async function editConversationForOrg (req, res, next) {
652652
}
653653

654654
// Make the edit
655-
returnValue = await conversationRepo.editConversation(conversation.UUID, incomingParameters, userUUID, { session })
655+
returnValue = await conversationRepo.editConversation(conversation.UUID, incomingParameters, { session })
656+
if (!isSecretariat && returnValue) {
657+
delete returnValue.author_id
658+
}
656659
await session.commitTransaction()
657660
} catch (error) {
658661
await session.abortTransaction()

src/controller/review-object.controller/review-object.controller.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ async function approveReviewObject (req, res, next) {
9595

9696
const requestingUserUUID = await userRepo.getUserUUID(req.ctx.user, req.ctx.org, { session })
9797

98-
const reviewObj = await reviewRepo.approveReviewOrgObject(UUID, { session })
98+
const reviewObj = await reviewRepo.approveReviewOrgObject(UUID, req.ctx.user, { session })
9999
if (!reviewObj) {
100100
await session.abortTransaction()
101101
return res.status(404).json({ message: `Review object not approved with UUID ${UUID}` })
@@ -166,7 +166,7 @@ async function createReviewObject (req, res, next) {
166166
await session.abortTransaction()
167167
return res.status(400).json({ message: 'Invalid body parameters', errors: bodyValidation.errors })
168168
}
169-
createdReviewObj = await repo.createReviewOrgObject(body, { session })
169+
createdReviewObj = await repo.createReviewOrgObject(body, req.ctx.user, { session })
170170
await session.commitTransaction()
171171
} catch (createErr) {
172172
await session.abortTransaction()
@@ -233,7 +233,7 @@ async function rejectReviewObject (req, res, next) {
233233
return res.status(404).json({ message: `No pending review object found with UUID ${UUID}` })
234234
}
235235

236-
value = await reviewRepo.rejectReviewOrgObject(UUID, { session })
236+
value = await reviewRepo.rejectReviewOrgObject(UUID, req.ctx.user, { session })
237237
await session.commitTransaction()
238238
} catch (rejectErr) {
239239
await session.abortTransaction()

src/middleware/schemas/CNAOrg.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@
2020
},
2121
"hard_quota": {
2222
"type": "integer",
23-
"minimum": 0
23+
"minimum": 0,
24+
"maximum": 100000
2425
},
2526
"soft_quota": {
2627
"type": "integer",
27-
"minimum": 0
28+
"minimum": 0,
29+
"maximum": 100000
2830
},
2931
"charter_or_scope": {
3032
"$ref": "/BaseOrg#/definitions/uriType"

src/middleware/schemas/SecretariatOrg.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@
2020
},
2121
"hard_quota": {
2222
"type": "integer",
23-
"minimum": 0
23+
"minimum": 0,
24+
"maximum": 100000
2425
},
2526
"soft_quota": {
2627
"type": "integer",
27-
"minimum": 0
28+
"minimum": 0,
29+
"maximum": 100000
2830
}
2931
},
3032
"required": ["hard_quota"]

src/model/conversation.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ const schema = {
1313
visibility: String,
1414
body: String,
1515
posted_at: Date,
16-
edited_at: Date,
17-
editor_id: String
16+
edited_at: Date
1817
}
1918

2019
const ConversationSchema = new mongoose.Schema(schema, { collection: 'Conversation', timestamps: { createdAt: 'posted_at', updatedAt: 'last_updated' } })

src/model/reviewobject.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@ const schema = {
66
uuid: String,
77
target_object_uuid: String,
88
status: String,
9+
requester: {
10+
username: String
11+
},
12+
approver: {
13+
username: String
14+
},
15+
rejector: {
16+
username: String
17+
},
918
new_review_data: Object // This should be a object containing the new org data in the format of the base org model or one of its descriminators (e.g. CNAOrg, ADPOrg)
1019
}
1120

src/repositories/baseOrgRepository.js

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ class BaseOrgRepository extends BaseRepository {
283283
}
284284

285285
const data = { organizations: pg.itemsList }
286-
if (pg.itemCount >= options.limit) {
286+
if (!returnLegacyFormat || pg.itemCount >= options.limit) {
287287
data.totalCount = pg.itemCount
288288
data.itemsPerPage = pg.itemsPerPage
289289
data.pageCount = pg.pageCount
@@ -397,6 +397,13 @@ class BaseOrgRepository extends BaseRepository {
397397
const legacyOrgRepo = new OrgRepository()
398398
const ReviewObjectRepository = require('./reviewObjectRepository')
399399
const reviewObjectRepo = new ReviewObjectRepository()
400+
let requestingUsername = null
401+
if (requestingUserUUID) {
402+
const BaseUserRepository = require('./baseUserRepository')
403+
const userRepo = new BaseUserRepository()
404+
const requestingUser = await userRepo.findUserByUUID(requestingUserUUID, options)
405+
requestingUsername = requestingUser ? requestingUser.username : null
406+
}
400407

401408
// generate a shared uuid
402409
const sharedUUID = uuid.v4()
@@ -438,7 +445,7 @@ class BaseOrgRepository extends BaseRepository {
438445
if (isSecretariat) {
439446
registryObject = await SecretariatObjectToSave.save(options)
440447
} else {
441-
await reviewObjectRepo.createReviewOrgObject(registryObjectRaw, options)
448+
await reviewObjectRepo.createReviewOrgObject(registryObjectRaw, requestingUsername, options)
442449
}
443450
} else if (registryObjectRaw.authority.includes('CNA')) {
444451
// A special case, we should make sure we have the default quota if it is not set
@@ -452,23 +459,23 @@ class BaseOrgRepository extends BaseRepository {
452459
if (isSecretariat) {
453460
registryObject = await CNAObjectToSave.save(options)
454461
} else {
455-
await reviewObjectRepo.createReviewOrgObject(registryObjectRaw, options)
462+
await reviewObjectRepo.createReviewOrgObject(registryObjectRaw, requestingUsername, options)
456463
}
457464
} else if (registryObjectRaw.authority.includes('ADP')) {
458465
registryObjectRaw.hard_quota = 0
459466
const adpObjectToSave = new ADPOrgModel(registryObjectRaw)
460467
if (isSecretariat) {
461468
registryObject = await adpObjectToSave.save(options)
462469
} else {
463-
await reviewObjectRepo.createReviewOrgObject(registryObjectRaw, options)
470+
await reviewObjectRepo.createReviewOrgObject(registryObjectRaw, requestingUsername, options)
464471
}
465472
} else if (registryObjectRaw.authority.includes('BULK_DOWNLOAD')) {
466473
registryObjectRaw.hard_quota = 0
467474
const bulkDownloadObjectToSave = new BulkDownloadModel(registryObjectRaw)
468475
if (isSecretariat) {
469476
registryObject = await bulkDownloadObjectToSave.save(options)
470477
} else {
471-
await reviewObjectRepo.createReviewOrgObject(registryObjectRaw, options)
478+
await reviewObjectRepo.createReviewOrgObject(registryObjectRaw, requestingUsername, options)
472479
}
473480
} else {
474481
// Throw an Error instance so callers can catch and handle it properly
@@ -844,10 +851,13 @@ class BaseOrgRepository extends BaseRepository {
844851
// Dealing with roles requires a bit of extra control.
845852
const originalRoles = registryOrg.authority
846853

854+
const requestingUser = requestingUserUUID ? await userRepo.findUserByUUID(requestingUserUUID, options) : null
855+
const requestingUsername = requestingUser ? requestingUser.username : null
856+
847857
const protectedFields = ['_id', 'UUID', '__v', '__t', 'created', 'last_updated', 'createdAt', 'updatedAt', 'users', 'admins']
848858
if (isSecretariat || _.isEmpty(jointApprovalFieldsRegistry)) {
849-
updatedLegacyOrg = legacyOrg.overwrite(_.mergeWith(_.pick(legacyOrg.toObject(), protectedFields), legacyObjectRaw, skipNulls))
850-
updatedRegistryOrg = registryOrg.overwrite(_.mergeWith(_.pick(registryOrg.toObject(), protectedFields), registryObjectRaw, skipNulls))
859+
updatedLegacyOrg = legacyOrg.overwrite(_.mergeWith(_.pick(legacyOrg.toObject(), protectedFields), _.omit(legacyObjectRaw, protectedFields), skipNulls))
860+
updatedRegistryOrg = registryOrg.overwrite(_.mergeWith(_.pick(registryOrg.toObject(), protectedFields), _.omit(registryObjectRaw, protectedFields), skipNulls))
851861
} else {
852862
// Check if there are actual changes to joint approval fields compared to current org object (not current review)
853863
// Only compare fields that are actually in the incoming data
@@ -862,19 +872,18 @@ class BaseOrgRepository extends BaseRepository {
862872
if (reviewObject) {
863873
await reviewObjectRepo.updateReviewOrgObject(jointApprovalRegistry, reviewObject.uuid, options)
864874
} else {
865-
await reviewObjectRepo.createReviewOrgObject(jointApprovalRegistry, options)
875+
await reviewObjectRepo.createReviewOrgObject(jointApprovalRegistry, requestingUsername, options)
866876
}
867877
} else {
868878
// If no changes between org and new object but a review object exists, remove it since joint approval is no longer needed
869879
if (reviewObject) {
870-
await reviewObjectRepo.rejectReviewOrgObject(reviewObject.uuid, options)
880+
await reviewObjectRepo.rejectReviewOrgObject(reviewObject.uuid, requestingUsername, options)
871881
}
872882
}
873-
updatedRegistryOrg = registryOrg.overwrite(_.mergeWith(_.pick(registryOrg.toObject(), [...protectedFields, ...jointApprovalFieldsRegistry]), _.omit(registryObjectRaw, jointApprovalFieldsRegistry), skipNulls))
874-
updatedLegacyOrg = legacyOrg.overwrite(_.mergeWith(_.pick(legacyOrg.toObject(), [...protectedFields, ...jointApprovalFieldsLegacy]), _.omit(legacyObjectRaw, jointApprovalFieldsLegacy), skipNulls))
883+
updatedRegistryOrg = registryOrg.overwrite(_.mergeWith(_.pick(registryOrg.toObject(), [...protectedFields, ...jointApprovalFieldsRegistry]), _.omit(registryObjectRaw, [...protectedFields, ...jointApprovalFieldsRegistry]), skipNulls))
884+
updatedLegacyOrg = legacyOrg.overwrite(_.mergeWith(_.pick(legacyOrg.toObject(), [...protectedFields, ...jointApprovalFieldsLegacy]), _.omit(legacyObjectRaw, [...protectedFields, ...jointApprovalFieldsLegacy]), skipNulls))
875885
}
876886
// handle conversation
877-
const requestingUser = await userRepo.findUserByUUID(requestingUserUUID, options)
878887
const conversationArray = []
879888
if (conversation) {
880889
conversationArray.push(await conversationRepo.createConversation(registryOrg.UUID, conversation, requestingUser, isSecretariat, options))

src/repositories/baseUserRepository.js

Lines changed: 29 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -98,23 +98,15 @@ class BaseUserRepository extends BaseRepository {
9898
* @returns {Promise<boolean>} True if the organization has the user, false otherwise.
9999
*/
100100
async orgHasUser (orgShortName, username, options = {}, isRegistryObject = true) {
101-
// 1. Find all users with this username
102-
const users = await BaseUser.find({ username }, null, options)
103-
if (!users || users.length === 0) {
104-
return false
105-
}
106-
107-
// 2. Get all their UUIDs
108-
const userUUIDs = users.map(u => u.UUID)
109-
110-
// 3. Find the org
101+
// 1. Find the org
111102
const org = await BaseOrgModel.findOne({ short_name: orgShortName }, null, options)
112103
if (!org || !Array.isArray(org.users)) {
113104
return false
114105
}
115106

116-
// 4. Check if any UUID is present in org.users
117-
return userUUIDs.some(uuid => org.users.includes(uuid))
107+
// 2. Check if a user with this username exists in the org
108+
const user = await BaseUser.findOne({ username, UUID: { $in: org.users } }, null, options)
109+
return !!user
118110
}
119111

120112
/**
@@ -129,16 +121,12 @@ class BaseUserRepository extends BaseRepository {
129121
*/
130122
async findOneByUsernameAndOrgShortname (username, orgShortName, options = {}, isRegistryObject = true) {
131123
const legacyUserRepo = new UserRepository()
132-
const users = await BaseUser.find({ username: username }, null, options)
133-
if (!users || users.length === 0) {
134-
return null
135-
}
136124
const org = await BaseOrgModel.findOne({ short_name: orgShortName }, null, options)
137125
if (!org || !Array.isArray(org.users)) {
138126
return null
139127
}
140-
// users = users.map(user => user.toObject())
141-
const user = users.find(user => org.users.includes(user.UUID))
128+
129+
const user = await BaseUser.findOne({ username: username, UUID: { $in: org.users } }, null, options)
142130

143131
if (!isRegistryObject && user) {
144132
return await legacyUserRepo.findOneByUUID(user.UUID) || null
@@ -158,16 +146,13 @@ class BaseUserRepository extends BaseRepository {
158146
*/
159147
async findOneByUserNameAndOrgUUID (username, orgUUID, options = {}, isRegistryObject = true) {
160148
const legacyUserRepo = new UserRepository()
161-
const users = await BaseUser.find({ username: username }, null, options)
162-
if (!users || users.length === 0) {
163-
return null
164-
}
165149
const org = await BaseOrgModel.findOne({ UUID: orgUUID }, null, options)
166150
if (!org || !Array.isArray(org.users)) {
167151
return null
168152
}
169153

170-
const user = users.find(user => org.users.includes(user.UUID))
154+
const user = await BaseUser.findOne({ username: username, UUID: { $in: org.users } }, null, options)
155+
171156
if (!isRegistryObject && user) {
172157
return await legacyUserRepo.findOneByUUID(user.UUID) || null
173158
}
@@ -372,10 +357,15 @@ class BaseUserRepository extends BaseRepository {
372357
registryObjectRaw.secret = secret
373358
legacyObjectRaw.secret = secret
374359

360+
// Allow user to provide initial status, default to active
361+
let isConsideredInactive = false
362+
if (isRegistryObject && incomingUser.status === 'inactive') isConsideredInactive = true
363+
if (!isRegistryObject && (incomingUser.active === false || String(incomingUser.active).toLowerCase() === 'false')) isConsideredInactive = true
364+
375365
// Registry Only Fields
376-
registryObjectRaw.status = 'active'
366+
registryObjectRaw.status = isConsideredInactive ? 'inactive' : 'active'
377367
// Legacy Specific fields
378-
legacyObjectRaw.active = true
368+
legacyObjectRaw.active = !isConsideredInactive
379369

380370
// Get UUID of org, that is having the user added to it.
381371
const existingOrg = await baseOrgRepository.findOneByShortName(orgShortName)
@@ -536,8 +526,15 @@ class BaseUserRepository extends BaseRepository {
536526
const protectedFieldsRegistry = ['_id', 'UUID', '__v', 'secret', 'created', 'last_updated']
537527
const protectedFieldsLegacy = ['_id', 'UUID', '__v', 'secret', 'time', 'org_UUID']
538528

539-
const updatedRegistryUser = registryUser.overwrite(_.mergeWith(_.pick(registryUser.toObject(), protectedFieldsRegistry), registryObjectRaw, skipNulls))
540-
const updatedLegacyUser = legacyUser.overwrite(_.mergeWith(_.pick(legacyUser.toObject(), protectedFieldsLegacy), legacyObjectRaw, skipNulls))
529+
const updatedRegistryUser = registryUser.overwrite(_.mergeWith(_.pick(registryUser.toObject(), protectedFieldsRegistry), _.omit(registryObjectRaw, protectedFieldsRegistry), skipNulls))
530+
const updatedLegacyUser = legacyUser.overwrite(_.mergeWith(_.pick(legacyUser.toObject(), protectedFieldsLegacy), _.omit(legacyObjectRaw, protectedFieldsLegacy), skipNulls))
531+
532+
if (updatedRegistryUser.status !== 'active') {
533+
updatedRegistryUser.status = 'inactive'
534+
updatedLegacyUser.active = false
535+
} else {
536+
updatedLegacyUser.active = true
537+
}
541538

542539
try {
543540
if (incomingUser.org_short_name) {
@@ -651,8 +648,8 @@ class BaseUserRepository extends BaseRepository {
651648
suffix: legacyUser.name?.suffix
652649
},
653650
status: 'active',
654-
created: legacyUser?.time?.created ?? null,
655-
last_updated: legacyUser?.time?.modified ?? null
651+
created: legacyUser?.time?.created,
652+
last_updated: legacyUser?.time?.modified
656653
}
657654
}
658655

@@ -678,8 +675,8 @@ class BaseUserRepository extends BaseRepository {
678675
secret: registryUser.secret,
679676
active: registryUser.status === 'active',
680677
time: {
681-
created: registryUser?.created ?? null,
682-
modified: registryUser?.last_updated ?? null
678+
created: registryUser?.created,
679+
modified: registryUser?.last_updated
683680
}
684681
}
685682
}
@@ -750,7 +747,7 @@ class BaseUserRepository extends BaseRepository {
750747
if (item.users && item.users.length > 0) {
751748
const populatedUsers = await Promise.all(
752749
item.users.map(async (uuid) => {
753-
const user = await this.findOneByUUID(uuid)
750+
const user = await this.findUserByUUID(uuid)
754751
return user ? user.toObject() : uuid // Return the user object if found, otherwise return the UUID
755752
})
756753
)

0 commit comments

Comments
 (0)