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,7 +1,7 @@
{
"openapi": "3.0.2",
"info": {
"version": "2.7.4",
"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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "cve-services",
"author": "Automation Working Group",
"version": "2.7.4",
"version": "2.7.5",
"license": "(CC0)",
"devDependencies": {
"@faker-js/faker": "^7.6.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -315,9 +315,9 @@ async function updateOrg (req, res, next) {

// Compare and set status accordingly
if (_.isEqual(cleanPending, cleanIncoming)) {
await reviewRepo.approveReviewOrgObject(pendingReview.uuid, { session })
await reviewRepo.approveReviewOrgObject(pendingReview.uuid, req.ctx.user, { session })
} else {
await reviewRepo.rejectReviewOrgObject(pendingReview.uuid, { session })
await reviewRepo.rejectReviewOrgObject(pendingReview.uuid, req.ctx.user, { session })
}
}
}
Expand Down Expand Up @@ -652,7 +652,10 @@ async function editConversationForOrg (req, res, next) {
}

// Make the edit
returnValue = await conversationRepo.editConversation(conversation.UUID, incomingParameters, userUUID, { session })
returnValue = await conversationRepo.editConversation(conversation.UUID, incomingParameters, { session })
if (!isSecretariat && returnValue) {
delete returnValue.author_id
}
await session.commitTransaction()
} catch (error) {
await session.abortTransaction()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ async function approveReviewObject (req, res, next) {

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

const reviewObj = await reviewRepo.approveReviewOrgObject(UUID, { session })
const reviewObj = await reviewRepo.approveReviewOrgObject(UUID, req.ctx.user, { session })
if (!reviewObj) {
await session.abortTransaction()
return res.status(404).json({ message: `Review object not approved with UUID ${UUID}` })
Expand Down Expand Up @@ -166,7 +166,7 @@ async function createReviewObject (req, res, next) {
await session.abortTransaction()
return res.status(400).json({ message: 'Invalid body parameters', errors: bodyValidation.errors })
}
createdReviewObj = await repo.createReviewOrgObject(body, { session })
createdReviewObj = await repo.createReviewOrgObject(body, req.ctx.user, { session })
await session.commitTransaction()
} catch (createErr) {
await session.abortTransaction()
Expand Down Expand Up @@ -233,7 +233,7 @@ async function rejectReviewObject (req, res, next) {
return res.status(404).json({ message: `No pending review object found with UUID ${UUID}` })
}

value = await reviewRepo.rejectReviewOrgObject(UUID, { session })
value = await reviewRepo.rejectReviewOrgObject(UUID, req.ctx.user, { session })
await session.commitTransaction()
} catch (rejectErr) {
await session.abortTransaction()
Expand Down
6 changes: 4 additions & 2 deletions src/middleware/schemas/CNAOrg.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@
},
"hard_quota": {
"type": "integer",
"minimum": 0
"minimum": 0,
"maximum": 100000
},
"soft_quota": {
"type": "integer",
"minimum": 0
"minimum": 0,
"maximum": 100000
},
"charter_or_scope": {
"$ref": "/BaseOrg#/definitions/uriType"
Expand Down
6 changes: 4 additions & 2 deletions src/middleware/schemas/SecretariatOrg.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@
},
"hard_quota": {
"type": "integer",
"minimum": 0
"minimum": 0,
"maximum": 100000
},
"soft_quota": {
"type": "integer",
"minimum": 0
"minimum": 0,
"maximum": 100000
}
},
"required": ["hard_quota"]
Expand Down
3 changes: 1 addition & 2 deletions src/model/conversation.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ const schema = {
visibility: String,
body: String,
posted_at: Date,
edited_at: Date,
editor_id: String
edited_at: Date
}

const ConversationSchema = new mongoose.Schema(schema, { collection: 'Conversation', timestamps: { createdAt: 'posted_at', updatedAt: 'last_updated' } })
Expand Down
9 changes: 9 additions & 0 deletions src/model/reviewobject.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ const schema = {
uuid: String,
target_object_uuid: String,
status: String,
requester: {
username: String
},
approver: {
username: String
},
rejector: {
username: String
},
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)
}

Expand Down
33 changes: 21 additions & 12 deletions src/repositories/baseOrgRepository.js
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ class BaseOrgRepository extends BaseRepository {
}

const data = { organizations: pg.itemsList }
if (pg.itemCount >= options.limit) {
if (!returnLegacyFormat || pg.itemCount >= options.limit) {
data.totalCount = pg.itemCount
data.itemsPerPage = pg.itemsPerPage
data.pageCount = pg.pageCount
Expand Down Expand Up @@ -397,6 +397,13 @@ class BaseOrgRepository extends BaseRepository {
const legacyOrgRepo = new OrgRepository()
const ReviewObjectRepository = require('./reviewObjectRepository')
const reviewObjectRepo = new ReviewObjectRepository()
let requestingUsername = null
if (requestingUserUUID) {
const BaseUserRepository = require('./baseUserRepository')
const userRepo = new BaseUserRepository()
const requestingUser = await userRepo.findUserByUUID(requestingUserUUID, options)
requestingUsername = requestingUser ? requestingUser.username : null
}

// generate a shared uuid
const sharedUUID = uuid.v4()
Expand Down Expand Up @@ -438,7 +445,7 @@ class BaseOrgRepository extends BaseRepository {
if (isSecretariat) {
registryObject = await SecretariatObjectToSave.save(options)
} else {
await reviewObjectRepo.createReviewOrgObject(registryObjectRaw, options)
await reviewObjectRepo.createReviewOrgObject(registryObjectRaw, requestingUsername, options)
}
} else if (registryObjectRaw.authority.includes('CNA')) {
// A special case, we should make sure we have the default quota if it is not set
Expand All @@ -452,23 +459,23 @@ class BaseOrgRepository extends BaseRepository {
if (isSecretariat) {
registryObject = await CNAObjectToSave.save(options)
} else {
await reviewObjectRepo.createReviewOrgObject(registryObjectRaw, options)
await reviewObjectRepo.createReviewOrgObject(registryObjectRaw, requestingUsername, options)
}
} else if (registryObjectRaw.authority.includes('ADP')) {
registryObjectRaw.hard_quota = 0
const adpObjectToSave = new ADPOrgModel(registryObjectRaw)
if (isSecretariat) {
registryObject = await adpObjectToSave.save(options)
} else {
await reviewObjectRepo.createReviewOrgObject(registryObjectRaw, options)
await reviewObjectRepo.createReviewOrgObject(registryObjectRaw, requestingUsername, options)
}
} else if (registryObjectRaw.authority.includes('BULK_DOWNLOAD')) {
registryObjectRaw.hard_quota = 0
const bulkDownloadObjectToSave = new BulkDownloadModel(registryObjectRaw)
if (isSecretariat) {
registryObject = await bulkDownloadObjectToSave.save(options)
} else {
await reviewObjectRepo.createReviewOrgObject(registryObjectRaw, options)
await reviewObjectRepo.createReviewOrgObject(registryObjectRaw, requestingUsername, options)
}
} else {
// Throw an Error instance so callers can catch and handle it properly
Expand Down Expand Up @@ -844,10 +851,13 @@ class BaseOrgRepository extends BaseRepository {
// Dealing with roles requires a bit of extra control.
const originalRoles = registryOrg.authority

const requestingUser = requestingUserUUID ? await userRepo.findUserByUUID(requestingUserUUID, options) : null
const requestingUsername = requestingUser ? requestingUser.username : null

const protectedFields = ['_id', 'UUID', '__v', '__t', 'created', 'last_updated', 'createdAt', 'updatedAt', 'users', 'admins']
if (isSecretariat || _.isEmpty(jointApprovalFieldsRegistry)) {
updatedLegacyOrg = legacyOrg.overwrite(_.mergeWith(_.pick(legacyOrg.toObject(), protectedFields), legacyObjectRaw, skipNulls))
updatedRegistryOrg = registryOrg.overwrite(_.mergeWith(_.pick(registryOrg.toObject(), protectedFields), registryObjectRaw, skipNulls))
updatedLegacyOrg = legacyOrg.overwrite(_.mergeWith(_.pick(legacyOrg.toObject(), protectedFields), _.omit(legacyObjectRaw, protectedFields), skipNulls))
updatedRegistryOrg = registryOrg.overwrite(_.mergeWith(_.pick(registryOrg.toObject(), protectedFields), _.omit(registryObjectRaw, protectedFields), skipNulls))
} else {
// Check if there are actual changes to joint approval fields compared to current org object (not current review)
// Only compare fields that are actually in the incoming data
Expand All @@ -862,19 +872,18 @@ class BaseOrgRepository extends BaseRepository {
if (reviewObject) {
await reviewObjectRepo.updateReviewOrgObject(jointApprovalRegistry, reviewObject.uuid, options)
} else {
await reviewObjectRepo.createReviewOrgObject(jointApprovalRegistry, options)
await reviewObjectRepo.createReviewOrgObject(jointApprovalRegistry, requestingUsername, options)
}
} else {
// If no changes between org and new object but a review object exists, remove it since joint approval is no longer needed
if (reviewObject) {
await reviewObjectRepo.rejectReviewOrgObject(reviewObject.uuid, options)
await reviewObjectRepo.rejectReviewOrgObject(reviewObject.uuid, requestingUsername, options)
}
}
updatedRegistryOrg = registryOrg.overwrite(_.mergeWith(_.pick(registryOrg.toObject(), [...protectedFields, ...jointApprovalFieldsRegistry]), _.omit(registryObjectRaw, jointApprovalFieldsRegistry), skipNulls))
updatedLegacyOrg = legacyOrg.overwrite(_.mergeWith(_.pick(legacyOrg.toObject(), [...protectedFields, ...jointApprovalFieldsLegacy]), _.omit(legacyObjectRaw, jointApprovalFieldsLegacy), skipNulls))
updatedRegistryOrg = registryOrg.overwrite(_.mergeWith(_.pick(registryOrg.toObject(), [...protectedFields, ...jointApprovalFieldsRegistry]), _.omit(registryObjectRaw, [...protectedFields, ...jointApprovalFieldsRegistry]), skipNulls))
updatedLegacyOrg = legacyOrg.overwrite(_.mergeWith(_.pick(legacyOrg.toObject(), [...protectedFields, ...jointApprovalFieldsLegacy]), _.omit(legacyObjectRaw, [...protectedFields, ...jointApprovalFieldsLegacy]), skipNulls))
}
// handle conversation
const requestingUser = await userRepo.findUserByUUID(requestingUserUUID, options)
const conversationArray = []
if (conversation) {
conversationArray.push(await conversationRepo.createConversation(registryOrg.UUID, conversation, requestingUser, isSecretariat, options))
Expand Down
61 changes: 29 additions & 32 deletions src/repositories/baseUserRepository.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,23 +98,15 @@ class BaseUserRepository extends BaseRepository {
* @returns {Promise<boolean>} True if the organization has the user, false otherwise.
*/
async orgHasUser (orgShortName, username, options = {}, isRegistryObject = true) {
// 1. Find all users with this username
const users = await BaseUser.find({ username }, null, options)
if (!users || users.length === 0) {
return false
}

// 2. Get all their UUIDs
const userUUIDs = users.map(u => u.UUID)

// 3. Find the org
// 1. Find the org
const org = await BaseOrgModel.findOne({ short_name: orgShortName }, null, options)
if (!org || !Array.isArray(org.users)) {
return false
}

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

/**
Expand All @@ -129,16 +121,12 @@ class BaseUserRepository extends BaseRepository {
*/
async findOneByUsernameAndOrgShortname (username, orgShortName, options = {}, isRegistryObject = true) {
const legacyUserRepo = new UserRepository()
const users = await BaseUser.find({ username: username }, null, options)
if (!users || users.length === 0) {
return null
}
const org = await BaseOrgModel.findOne({ short_name: orgShortName }, null, options)
if (!org || !Array.isArray(org.users)) {
return null
}
// users = users.map(user => user.toObject())
const user = users.find(user => org.users.includes(user.UUID))

const user = await BaseUser.findOne({ username: username, UUID: { $in: org.users } }, null, options)

if (!isRegistryObject && user) {
return await legacyUserRepo.findOneByUUID(user.UUID) || null
Expand All @@ -158,16 +146,13 @@ class BaseUserRepository extends BaseRepository {
*/
async findOneByUserNameAndOrgUUID (username, orgUUID, options = {}, isRegistryObject = true) {
const legacyUserRepo = new UserRepository()
const users = await BaseUser.find({ username: username }, null, options)
if (!users || users.length === 0) {
return null
}
const org = await BaseOrgModel.findOne({ UUID: orgUUID }, null, options)
if (!org || !Array.isArray(org.users)) {
return null
}

const user = users.find(user => org.users.includes(user.UUID))
const user = await BaseUser.findOne({ username: username, UUID: { $in: org.users } }, null, options)

if (!isRegistryObject && user) {
return await legacyUserRepo.findOneByUUID(user.UUID) || null
}
Expand Down Expand Up @@ -372,10 +357,15 @@ class BaseUserRepository extends BaseRepository {
registryObjectRaw.secret = secret
legacyObjectRaw.secret = secret

// Allow user to provide initial status, default to active
let isConsideredInactive = false
if (isRegistryObject && incomingUser.status === 'inactive') isConsideredInactive = true
if (!isRegistryObject && (incomingUser.active === false || String(incomingUser.active).toLowerCase() === 'false')) isConsideredInactive = true

// Registry Only Fields
registryObjectRaw.status = 'active'
registryObjectRaw.status = isConsideredInactive ? 'inactive' : 'active'
// Legacy Specific fields
legacyObjectRaw.active = true
legacyObjectRaw.active = !isConsideredInactive

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

const updatedRegistryUser = registryUser.overwrite(_.mergeWith(_.pick(registryUser.toObject(), protectedFieldsRegistry), registryObjectRaw, skipNulls))
const updatedLegacyUser = legacyUser.overwrite(_.mergeWith(_.pick(legacyUser.toObject(), protectedFieldsLegacy), legacyObjectRaw, skipNulls))
const updatedRegistryUser = registryUser.overwrite(_.mergeWith(_.pick(registryUser.toObject(), protectedFieldsRegistry), _.omit(registryObjectRaw, protectedFieldsRegistry), skipNulls))
const updatedLegacyUser = legacyUser.overwrite(_.mergeWith(_.pick(legacyUser.toObject(), protectedFieldsLegacy), _.omit(legacyObjectRaw, protectedFieldsLegacy), skipNulls))

if (updatedRegistryUser.status !== 'active') {
updatedRegistryUser.status = 'inactive'
updatedLegacyUser.active = false
} else {
updatedLegacyUser.active = true
}

try {
if (incomingUser.org_short_name) {
Expand Down Expand Up @@ -651,8 +648,8 @@ class BaseUserRepository extends BaseRepository {
suffix: legacyUser.name?.suffix
},
status: 'active',
created: legacyUser?.time?.created ?? null,
last_updated: legacyUser?.time?.modified ?? null
created: legacyUser?.time?.created,
last_updated: legacyUser?.time?.modified
}
}

Expand All @@ -678,8 +675,8 @@ class BaseUserRepository extends BaseRepository {
secret: registryUser.secret,
active: registryUser.status === 'active',
time: {
created: registryUser?.created ?? null,
modified: registryUser?.last_updated ?? null
created: registryUser?.created,
modified: registryUser?.last_updated
}
}
}
Expand Down Expand Up @@ -750,7 +747,7 @@ class BaseUserRepository extends BaseRepository {
if (item.users && item.users.length > 0) {
const populatedUsers = await Promise.all(
item.users.map(async (uuid) => {
const user = await this.findOneByUUID(uuid)
const user = await this.findUserByUUID(uuid)
return user ? user.toObject() : uuid // Return the user object if found, otherwise return the UUID
})
)
Expand Down
11 changes: 7 additions & 4 deletions src/repositories/conversationRepository.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,12 @@ class ConversationRepository extends BaseRepository {
UUID: 1
}
})
return conversations.map(convo => convo.toObject()).filter(conv => isSecretariat || conv.visibility === 'public')
return conversations.map(convo => convo.toObject()).filter(conv => isSecretariat || conv.visibility === 'public').map(conv => {
if (!isSecretariat) {
delete conv.author_id
}
return conv
})
}

async findByTargetUUIDAndIndex (targetUUID, index, options = {}) {
Expand Down Expand Up @@ -74,7 +79,6 @@ class ConversationRepository extends BaseRepository {
author_id: user.UUID,
author_name: getUserFullName(user),
author_role: isSecretariat ? 'Secretariat' : 'Partner',
editor_id: null,
edited_at: null,
visibility: !isSecretariat ? 'public' : (['public', 'private'].includes(body.visibility?.toLowerCase()) ? body.visibility.toLowerCase() : 'private'),
body: body.body
Expand All @@ -84,15 +88,14 @@ class ConversationRepository extends BaseRepository {
return result.toObject()
}

async editConversation (UUID, incomingParameters, userUUID, options = {}) {
async editConversation (UUID, incomingParameters, options = {}) {
const conversation = await this.findOneByUUID(UUID, options)
if (incomingParameters?.body) {
conversation.body = incomingParameters.body
}
if (incomingParameters?.visibility) {
conversation.visibility = incomingParameters.visibility
}
conversation.editor_id = userUUID
conversation.edited_at = Date.now()
const result = await conversation.save(options)
return result.toObject()
Expand Down
Loading
Loading