diff --git a/api-docs/openapi.json b/api-docs/openapi.json
index d96597b1c..01ab0fad9 100644
--- a/api-docs/openapi.json
+++ b/api-docs/openapi.json
@@ -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 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 16da323ab..8e0ec46bb 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/src/controller/registry-org.controller/registry-org.controller.js b/src/controller/registry-org.controller/registry-org.controller.js
index c5a42a065..64da4d038 100644
--- a/src/controller/registry-org.controller/registry-org.controller.js
+++ b/src/controller/registry-org.controller/registry-org.controller.js
@@ -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 })
}
}
}
@@ -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()
diff --git a/src/controller/review-object.controller/review-object.controller.js b/src/controller/review-object.controller/review-object.controller.js
index 8bf0b5540..7b4556d29 100644
--- a/src/controller/review-object.controller/review-object.controller.js
+++ b/src/controller/review-object.controller/review-object.controller.js
@@ -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}` })
@@ -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()
@@ -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()
diff --git a/src/middleware/schemas/CNAOrg.json b/src/middleware/schemas/CNAOrg.json
index c1188c8c4..5dcb3f3db 100644
--- a/src/middleware/schemas/CNAOrg.json
+++ b/src/middleware/schemas/CNAOrg.json
@@ -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"
diff --git a/src/middleware/schemas/SecretariatOrg.json b/src/middleware/schemas/SecretariatOrg.json
index 4e658b571..125ba92b1 100644
--- a/src/middleware/schemas/SecretariatOrg.json
+++ b/src/middleware/schemas/SecretariatOrg.json
@@ -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"]
diff --git a/src/model/conversation.js b/src/model/conversation.js
index fe304bf8f..a88e92250 100644
--- a/src/model/conversation.js
+++ b/src/model/conversation.js
@@ -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' } })
diff --git a/src/model/reviewobject.js b/src/model/reviewobject.js
index 52bdea788..ca29bc97a 100644
--- a/src/model/reviewobject.js
+++ b/src/model/reviewobject.js
@@ -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)
}
diff --git a/src/repositories/baseOrgRepository.js b/src/repositories/baseOrgRepository.js
index adc4b5f7f..a95065e7a 100644
--- a/src/repositories/baseOrgRepository.js
+++ b/src/repositories/baseOrgRepository.js
@@ -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
@@ -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()
@@ -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
@@ -452,7 +459,7 @@ 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
@@ -460,7 +467,7 @@ class BaseOrgRepository extends BaseRepository {
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
@@ -468,7 +475,7 @@ class BaseOrgRepository extends BaseRepository {
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
@@ -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
@@ -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))
diff --git a/src/repositories/baseUserRepository.js b/src/repositories/baseUserRepository.js
index 98a0f5289..be6164758 100644
--- a/src/repositories/baseUserRepository.js
+++ b/src/repositories/baseUserRepository.js
@@ -98,23 +98,15 @@ class BaseUserRepository extends BaseRepository {
* @returns {Promise} 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
}
/**
@@ -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
@@ -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
}
@@ -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)
@@ -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) {
@@ -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
}
}
@@ -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
}
}
}
@@ -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
})
)
diff --git a/src/repositories/conversationRepository.js b/src/repositories/conversationRepository.js
index 39e055563..02a61d807 100644
--- a/src/repositories/conversationRepository.js
+++ b/src/repositories/conversationRepository.js
@@ -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 = {}) {
@@ -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
@@ -84,7 +88,7 @@ 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
@@ -92,7 +96,6 @@ class ConversationRepository extends BaseRepository {
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()
diff --git a/src/repositories/reviewObjectRepository.js b/src/repositories/reviewObjectRepository.js
index 8ebcda1f6..0062da212 100644
--- a/src/repositories/reviewObjectRepository.js
+++ b/src/repositories/reviewObjectRepository.js
@@ -137,12 +137,15 @@ class ReviewObjectRepository extends BaseRepository {
return reviewObject || null
}
- async createReviewOrgObject (orgBody, options = {}) {
+ async createReviewOrgObject (orgBody, requesterUsername, options = {}) {
console.log('Creating review object for organization:', orgBody.UUID)
const reviewObjectRaw = {
uuid: uuid.v4(),
target_object_uuid: orgBody.UUID,
status: 'pending',
+ requester: {
+ username: requesterUsername
+ },
new_review_data: orgBody || {}
}
@@ -164,7 +167,7 @@ class ReviewObjectRepository extends BaseRepository {
return result.toObject()
}
- async approveReviewOrgObject (UUID, options = {}) {
+ async approveReviewOrgObject (UUID, approverUsername, options = {}) {
console.log('Approving review object with UUID:', UUID)
const reviewObject = await this.findOneByUUID(UUID, options)
if (!reviewObject) {
@@ -172,6 +175,11 @@ class ReviewObjectRepository extends BaseRepository {
}
reviewObject.status = 'approved'
+ if (approverUsername) {
+ reviewObject.approver = {
+ username: approverUsername
+ }
+ }
await reviewObject.save(options)
return reviewObject.toObject()
@@ -210,10 +218,17 @@ class ReviewObjectRepository extends BaseRepository {
const conversationRepository = new ConversationRepository()
for (const review of data.reviewObjects) {
- const conversations = await conversationRepository.getAllByTargetUUID(review.uuid, options)
+ let conversations = await conversationRepository.getAllByTargetUUID(review.uuid, isSecretariat, options)
// Filter conversations based on user role
if (conversations && conversations.length) {
+ // If non-secretariat, remove author_id
+ if (!isSecretariat) {
+ conversations = conversations.map(c => {
+ delete c.author_id
+ return c
+ })
+ }
review.conversation = conversations.filter(conv =>
isSecretariat || conv.visibility === 'public'
)
@@ -226,7 +241,7 @@ class ReviewObjectRepository extends BaseRepository {
return data
}
- async rejectReviewOrgObject (UUID, options = {}) {
+ async rejectReviewOrgObject (UUID, rejectorUsername, options = {}) {
console.log('Rejecting review object with UUID:', UUID)
const reviewObject = await this.findOneByUUID(UUID, options)
if (!reviewObject) {
@@ -234,6 +249,11 @@ class ReviewObjectRepository extends BaseRepository {
}
reviewObject.status = 'rejected'
+ if (rejectorUsername) {
+ reviewObject.rejector = {
+ username: rejectorUsername
+ }
+ }
await reviewObject.save(options)
return reviewObject.toObject()
diff --git a/src/scripts/migrate.js b/src/scripts/migrate.js
index 7fd7fa314..7f4bd5879 100644
--- a/src/scripts/migrate.js
+++ b/src/scripts/migrate.js
@@ -254,6 +254,7 @@ async function userHelper (db) {
username: doc.username,
secret: doc.secret,
name: doc.name,
+ status: doc.active ? 'active' : 'inactive',
created: doc.time.created,
created_by: 'system',
last_updated: doc.time.modified,
diff --git a/src/swagger.js b/src/swagger.js
index 4fc21c77e..5c39c6f01 100644
--- a/src/swagger.js
+++ b/src/swagger.js
@@ -22,7 +22,7 @@ const fullCnaContainerRequest = require('../schemas/cve/create-cve-record-cna-re
/* eslint-disable no-multi-str */
const doc = {
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 \
diff --git a/test/integration-tests/conversation/editConversationTest.js b/test/integration-tests/conversation/editConversationTest.js
index 7adff82e6..4dc371ddb 100644
--- a/test/integration-tests/conversation/editConversationTest.js
+++ b/test/integration-tests/conversation/editConversationTest.js
@@ -16,8 +16,6 @@ const orgAdminHeaders = {
describe('Testing Conversation endpoints', () => {
let org
- let secUserUUID
- let orgAdminUUID
// let rootConvoUUID
before(async () => {
@@ -38,7 +36,6 @@ describe('Testing Conversation endpoints', () => {
.then((res, err) => {
expect(err).to.be.undefined
expect(res).to.have.status(200)
- secUserUUID = res.body.UUID
})
await chai
@@ -48,7 +45,6 @@ describe('Testing Conversation endpoints', () => {
.then((res, err) => {
expect(err).to.be.undefined
expect(res).to.have.status(200)
- orgAdminUUID = res.body.UUID
})
// Do org update to create conversation as admin
@@ -103,9 +99,6 @@ describe('Testing Conversation endpoints', () => {
expect(res.body.updated).to.haveOwnProperty('body')
expect(res.body.updated.body).to.equal('admin test updated')
- expect(res.body.updated).to.haveOwnProperty('editor_id')
- expect(res.body.updated.editor_id).to.equal(orgAdminUUID)
-
expect(res.body.updated).to.haveOwnProperty('edited_at')
expect(res.body.updated.edited_at).to.not.be.null
@@ -133,9 +126,6 @@ describe('Testing Conversation endpoints', () => {
expect(res.body.updated).to.haveOwnProperty('body')
expect(res.body.updated.body).to.equal('secretariat test updated')
- expect(res.body.updated).to.haveOwnProperty('editor_id')
- expect(res.body.updated.editor_id).to.equal(secUserUUID)
-
expect(res.body.updated).to.haveOwnProperty('edited_at')
expect(res.body.updated.edited_at).to.not.be.null
@@ -163,9 +153,6 @@ describe('Testing Conversation endpoints', () => {
expect(res.body.updated).to.haveOwnProperty('body')
expect(res.body.updated.body).to.equal('admin test updated by secretariat')
- expect(res.body.updated).to.haveOwnProperty('editor_id')
- expect(res.body.updated.editor_id).to.equal(secUserUUID)
-
expect(res.body.updated).to.haveOwnProperty('edited_at')
expect(res.body.updated.edited_at).to.not.be.null
diff --git a/test/integration-tests/registry-org/registryOrgCRUDTest.js b/test/integration-tests/registry-org/registryOrgCRUDTest.js
index e1e7f04d3..db15adecc 100644
--- a/test/integration-tests/registry-org/registryOrgCRUDTest.js
+++ b/test/integration-tests/registry-org/registryOrgCRUDTest.js
@@ -112,6 +112,12 @@ describe('Testing /registryOrg endpoints', () => {
.set(secretariatHeaders)
.then((res) => {
expect(res).to.have.status(200)
+ expect(res.body).to.have.property('totalCount')
+ expect(res.body).to.have.property('itemsPerPage')
+ expect(res.body).to.have.property('pageCount')
+ expect(res.body).to.have.property('currentPage')
+ expect(res.body).to.have.property('prevPage')
+ expect(res.body).to.have.property('nextPage')
expect(res.body.organizations).to.be.an('array').that.is.not.empty
})
})
@@ -129,6 +135,58 @@ describe('Testing /registryOrg endpoints', () => {
expect(res.body).to.have.property('partner_country', createdOrg.partner_country)
})
})
+ it('Strips author_id from conversations for non-secretariats', async () => {
+ // 1. Get win_5 org UUID
+ let win5UUID
+ await chai.request(app)
+ .get('/api/registryOrg/win_5')
+ .set(secretariatHeaders)
+ .then((res) => {
+ expect(res).to.have.status(200)
+ win5UUID = res.body.UUID
+ })
+
+ // 2. Post a conversation as Secretariat to win_5
+ const conversationBody = {
+ visibility: 'public',
+ body: 'This is a test conversation for author_id stripping'
+ }
+ let postedConvoUUID
+ await chai.request(app)
+ .post(`/api/conversation/target/${win5UUID}`)
+ .set(secretariatHeaders)
+ .send(conversationBody)
+ .then((res) => {
+ expect(res).to.have.status(200)
+ postedConvoUUID = res.body.UUID
+ })
+
+ // 3. GET win_5 as Secretariat, verify author_id is present
+ await chai.request(app)
+ .get('/api/registryOrg/win_5')
+ .set(secretariatHeaders)
+ .then((res) => {
+ expect(res).to.have.status(200)
+ expect(res.body.conversation).to.be.an('array')
+ const convo = res.body.conversation.find(c => c.UUID === postedConvoUUID) || res.body.conversation[res.body.conversation.length - 1]
+ expect(convo).to.have.property('author_id')
+ expect(convo).to.have.property('body', 'This is a test conversation for author_id stripping')
+ })
+
+ // 4. GET win_5 as Non-Secretariat (author of win_5), verify author_id is stripped
+ await chai.request(app)
+ .get('/api/registry/org/win_5')
+ .set(constants.nonSecretariatUserHeaders)
+ .then((res) => {
+ expect(res).to.have.status(200)
+ expect(res.body.conversation).to.be.an('array')
+ // Remember non-secretariats don't see the UUID field returned in conversation so we can't search by UUID!
+ // We just check the latest one or search by body.
+ const convo = res.body.conversation.find(c => c.body === 'This is a test conversation for author_id stripping')
+ expect(convo).to.not.be.undefined
+ expect(convo).to.not.have.property('author_id')
+ })
+ })
})
context('Negative Tests', () => {
it('Fails to get a registry organization that does not exist', async () => {
@@ -269,6 +327,27 @@ describe('Testing /registryOrg endpoints', () => {
.delete(`/api/registryOrg/${subOrg.short_name}`)
.set(secretariatHeaders)
})
+ it('Ignores protected fields such as users and admins during an update', async () => {
+ const maliciousUsers = ['d41d8cd9-8f00-3204-a980-0998ecf8427e']
+ const maliciousAdmins = ['d41d8cd9-8f00-3204-a980-0998ecf8427e']
+
+ await chai.request(app)
+ .put(`/api/registryOrg/${createdOrg.short_name}`)
+ .set(secretariatHeaders)
+ .send({
+ ...createdOrg,
+ users: maliciousUsers,
+ admins: maliciousAdmins
+ })
+ .then((res, err) => {
+ expect(err).to.be.undefined
+ expect(res).to.have.status(200)
+
+ // Ensure the response body.updated does not contain the malicious data
+ expect(res.body.updated.users || []).to.not.include(maliciousUsers[0])
+ expect(res.body.updated.admins || []).to.not.include(maliciousAdmins[0])
+ })
+ })
})
context('Negative Tests', () => {
it('Fails to update a registry organization that does not exist', async () => {
@@ -324,6 +403,19 @@ describe('Testing /registryOrg endpoints', () => {
expect(res.body.details[0].msg).to.equal('reports_to must not be present')
})
})
+ it('Fails to update a registry organization with an invalidly high quota', async () => {
+ await chai.request(app)
+ .put(`/api/registryOrg/${createdOrg.short_name}`)
+ .set(secretariatHeaders)
+ .send({
+ ...createdOrg,
+ hard_quota: 1000000
+ })
+ .then((res) => {
+ expect(res).to.have.status(400)
+ expect(res.body.message).to.equal('Parameters were invalid')
+ })
+ })
})
})
context('Testing DELETE /registryOrg endpoint', () => {
diff --git a/test/integration-tests/review-object/review-object.repository.test.js b/test/integration-tests/review-object/review-object.repository.test.js
index 197d3640d..8e2d15820 100644
--- a/test/integration-tests/review-object/review-object.repository.test.js
+++ b/test/integration-tests/review-object/review-object.repository.test.js
@@ -39,6 +39,42 @@ describe('ReviewObjectRepository Tests', function () {
})
})
+ describe('create/approve/reject ReviewObject metadata', function () {
+ const orgBody = { UUID: 'some-org-uuid', short_name: 'some-org' }
+ const requesterUsername = 'req-user'
+ const approverUsername = 'app-user'
+
+ let createdUUID
+
+ it('should store requester username on create', async () => {
+ const result = await repository.createReviewOrgObject(orgBody, requesterUsername)
+ expect(result).to.exist
+ expect(result.requester).to.exist
+ expect(result.requester.username).to.equal(requesterUsername)
+ createdUUID = result.uuid
+ })
+
+ it('should store approver username on approve', async () => {
+ const result = await repository.approveReviewOrgObject(createdUUID, approverUsername)
+ expect(result).to.exist
+ expect(result.approver).to.exist
+ expect(result.approver.username).to.equal(approverUsername)
+ expect(result.status).to.equal('approved')
+ })
+
+ it('should store rejector username on reject', async () => {
+ const rejectOrgBody = { UUID: 'another-org-uuid', short_name: 'another-org' }
+ const rejectCreated = await repository.createReviewOrgObject(rejectOrgBody, requesterUsername)
+ const rejectorUsername = 'rej-user'
+ const result = await repository.rejectReviewOrgObject(rejectCreated.uuid, rejectorUsername)
+ expect(result).to.exist
+ expect(result.rejector).to.exist
+ expect(result.rejector.username).to.equal(rejectorUsername)
+ expect(result.approver).to.not.exist
+ expect(result.status).to.equal('rejected')
+ })
+ })
+
describe('findOneByUUIDWithConversation', function () {
it('should return the pending review object when pending=true', async () => {
const result = await repository.findOneByUUIDWithConversation(testUUID, true, true)
diff --git a/test/integration-tests/user/createUserTest.js b/test/integration-tests/user/createUserTest.js
index a2050a67e..54cb887c3 100644
--- a/test/integration-tests/user/createUserTest.js
+++ b/test/integration-tests/user/createUserTest.js
@@ -115,4 +115,32 @@ describe('Testing create user endpoint', () => {
done()
})
})
+ it('Should default new user status to active when no active/status field is provided', (done) => {
+ const noActiveBody = { ...body, username: 'noActiveUser' }
+ delete noActiveBody.active
+ chai.request(app)
+ .post('/api/org/range_4/user')
+ .set(constants.headers)
+ .send(noActiveBody)
+ .end((err, res) => {
+ expect(err).to.be.null
+ expect(res).to.have.status(200)
+ expect(res.body.created.active).to.be.true
+ done()
+ })
+ })
+ it('Should create an inactive user if explicitly requested', (done) => {
+ const inactiveBody = { ...registryBody, username: 'inactiveUser', status: 'inactive' }
+ delete inactiveBody.active
+ chai.request(app)
+ .post('/api/registry/org/range_4/user')
+ .set(constants.headers)
+ .send(inactiveBody)
+ .end((err, res) => {
+ expect(err).to.be.null
+ expect(res).to.have.status(200)
+ expect(res.body.created.status).to.equal('inactive')
+ done()
+ })
+ })
})
diff --git a/test/integration-tests/user/updateUserTest.js b/test/integration-tests/user/updateUserTest.js
index 610438687..da96d74d8 100644
--- a/test/integration-tests/user/updateUserTest.js
+++ b/test/integration-tests/user/updateUserTest.js
@@ -37,6 +37,63 @@ describe('Testing Edit user endpoint', () => {
expect(res).to.have.status(200)
})
})
+ it('Ignores protected fields such as UUID during an update', async () => {
+ let user
+ await chai.request(app).get('/api/registry/org/win_5/user/jasminesmith@win_5.com').set(constants.headers).then((res) => { user = res.body })
+
+ const maliciousUUID = 'd41d8cd9-8f00-3204-a980-0998ecf8427e'
+ const originalUUID = user.UUID
+
+ await chai.request(app)
+ .put('/api/registry/org/win_5/user/jasminesmith@win_5.com')
+ .set(constants.headers)
+ .send({
+ ...user,
+ UUID: maliciousUUID
+ })
+ .then((res, err) => {
+ expect(err).to.be.undefined
+ expect(res).to.have.status(200)
+
+ expect(res.body.updated).to.haveOwnProperty('UUID')
+ expect(res.body.updated.UUID).to.not.equal(maliciousUUID)
+ expect(res.body.updated.UUID).to.equal(originalUUID)
+ })
+ })
+ it('Should clamp status to inactive if a status other than active is provided', async () => {
+ let user
+ await chai.request(app).get('/api/registry/org/win_5/user/jasminesmith@win_5.com').set(constants.headers).then((res) => { user = res.body })
+
+ await chai.request(app)
+ .put('/api/registry/org/win_5/user/jasminesmith@win_5.com')
+ .set(constants.headers)
+ .send({
+ ...user,
+ status: 'inactive'
+ })
+ .then((res, err) => {
+ expect(err).to.be.undefined
+ expect(res).to.have.status(200)
+
+ expect(res.body.updated).to.haveOwnProperty('status')
+ expect(res.body.updated.status).to.equal('inactive')
+ })
+ // reset jasmine to active
+ await chai.request(app)
+ .put('/api/registry/org/win_5/user/jasminesmith@win_5.com')
+ .set(constants.headers)
+ .send({
+ ...user,
+ status: 'active'
+ })
+ .then((res, err) => {
+ expect(err).to.be.undefined
+ expect(res).to.have.status(200)
+
+ expect(res.body.updated).to.haveOwnProperty('status')
+ expect(res.body.updated.status).to.equal('active')
+ })
+ })
it('Should return an error when admin is required', async () => {
await chai.request(app)
.put('/api/org/win_5/user/jasminesmith@win_5.com?new_username=NewUsername')
diff --git a/test/unit-tests/org/orgCreateTest.js b/test/unit-tests/org/orgCreateTest.js
index 9b9485ffd..70a83f0bf 100644
--- a/test/unit-tests/org/orgCreateTest.js
+++ b/test/unit-tests/org/orgCreateTest.js
@@ -136,6 +136,7 @@ describe('Testing the ORG_CREATE_SINGLE controller', () => {
getBaseOrgRepository = sinon.stub().returns(baseOrgRepo)
baseUserRepo = new BaseUserRepository()
getBaseUserRepository = sinon.stub().returns(baseUserRepo)
+ sinon.stub(BaseUserRepository.prototype, 'findUserByUUID').resolves({ username: 'test_user' })
})
// Restore all stubs after each test