From 08a7c604b822b6900b504d68d69e058cbce92ef6 Mon Sep 17 00:00:00 2001 From: James Dalphond Date: Mon, 17 Aug 2026 13:40:30 -0400 Subject: [PATCH 01/20] Moving legacy org reads to use BaseOrg but projected as the legacy format --- .../org.controller/org.controller.js | 41 ++-- src/controller/registry.controller/index.js | 2 +- .../org.registry.controller.js | 43 +++- .../user.registry.controller.js | 6 +- src/repositories/baseOrgRepository.js | 207 ++++++++++++------ src/repositories/baseUserRepository.js | 108 ++++++--- src/utils/orgCompatibility.js | 70 ++++++ src/utils/userCompatibility.js | 53 +++++ 8 files changed, 409 insertions(+), 121 deletions(-) create mode 100644 src/utils/orgCompatibility.js create mode 100644 src/utils/userCompatibility.js diff --git a/src/controller/org.controller/org.controller.js b/src/controller/org.controller/org.controller.js index 19fcd0bac..c75ff1310 100644 --- a/src/controller/org.controller/org.controller.js +++ b/src/controller/org.controller/org.controller.js @@ -9,7 +9,7 @@ const _ = require('lodash') const authContext = require('../../utils/authContext') const { LEGACY_FORMAT, REGISTRY_FORMAT } = require('../format.constants') -function getObjectFormatForRequest (req) { +function getObjectFormatForResetSecret (req) { return req.useRegistry ? REGISTRY_FORMAT : LEGACY_FORMAT } @@ -37,7 +37,7 @@ async function getOrgs (req, res, next) { options.sort = { short_name: 'asc' } options.page = req.ctx.query.page ? parseInt(req.ctx.query.page) : CONSTANTS.PAGINATOR_PAGE // if 'page' query parameter is not defined, set 'page' to the default page value - const returnValue = await repo.getAllOrgs({ ...options }, LEGACY_FORMAT) + const returnValue = await repo.getLegacyOrgs({ ...options }) logger.info({ uuid: req.ctx.uuid, message: 'The orgs were sent to the user.' }) return res.status(200).json(returnValue) @@ -66,17 +66,17 @@ async function getOrg (req, res, next) { let returnValue try { - const requesterOrg = await authContext.getRequesterOrg(req, repo, {}, LEGACY_FORMAT) + const requesterOrg = await authContext.getRequesterOrg(req, repo) // Ensure requester org exists if (!requesterOrg) { return res.status(404).json(error.orgDne(requesterOrgShortName, 'requesterOrgShortName', 'header')) } - const isSecretariat = await authContext.isRequesterSecretariat(req, repo, {}, LEGACY_FORMAT) + const isSecretariat = await authContext.isRequesterSecretariat(req, repo) const isRequesterSameOrg = identifierIsUUID ? requesterOrg.UUID === identifier - : await authContext.isRequesterSameOrg(req, repo, identifier, {}, LEGACY_FORMAT) + : await authContext.isRequesterSameOrg(req, repo, identifier) // Ensure that if the requester is not Secretariat, they can't view orgs other than their own if (!isRequesterSameOrg && !isSecretariat) { @@ -84,7 +84,7 @@ async function getOrg (req, res, next) { return res.status(403).json(error.notSameOrgOrSecretariat()) } - returnValue = await repo.getOrg(identifier, identifierIsUUID, {}, LEGACY_FORMAT) + returnValue = await repo.getLegacyOrg(identifier, identifierIsUUID) } catch (err) { // Handle the specific error thrown by BaseOrgRepository.getOrg if (err.message && err.message.includes('Unknown Org type requested')) { @@ -143,13 +143,13 @@ async function getUsers (req, res, next) { return res.status(404).json(error.orgDnePathParam(orgShortName)) } - const isSameOrg = await authContext.isRequesterSameOrg(req, orgRepo, { UUID: orgUUID, short_name: orgShortName }, {}, LEGACY_FORMAT) + const isSameOrg = await authContext.isRequesterSameOrg(req, orgRepo, { UUID: orgUUID, short_name: orgShortName }) if (!isSameOrg && !isSecretariat) { logger.info({ uuid: req.ctx.uuid, message: orgShortName + ' organization can only be viewed by the users of the same organization or the Secretariat.' }) return res.status(403).json(error.notSameOrgOrSecretariat()) } - const payload = await userRepo.getAllUsersByOrgShortname(orgShortName, options, LEGACY_FORMAT) + const payload = await userRepo.getLegacyUsersByOrgShortname(orgShortName, options) logger.info({ uuid: req.ctx.uuid, message: `The users of ${orgShortName} organization were sent to the user.` }) return res.status(200).json(payload) @@ -173,10 +173,10 @@ async function getUser (req, res, next) { const orgShortName = req.ctx.params.shortname const orgRepo = req.ctx.repositories.getBaseOrgRepository() - const isSecretariat = await authContext.isRequesterSecretariat(req, orgRepo, {}, LEGACY_FORMAT) + const isSecretariat = await authContext.isRequesterSecretariat(req, orgRepo) const orgUUID = await orgRepo.getOrgUUID(orgShortName) - const isSameOrg = await authContext.isRequesterSameOrg(req, orgRepo, { UUID: orgUUID, short_name: orgShortName }, {}, LEGACY_FORMAT) + const isSameOrg = await authContext.isRequesterSameOrg(req, orgRepo, { UUID: orgUUID, short_name: orgShortName }) if (!isSameOrg && !isSecretariat) { logger.info({ uuid: req.ctx.uuid, message: req.ctx.org + ' organization can only be viewed by that organization\'s users or the Secretariat.' }) return res.status(403).json(error.notSameOrgOrSecretariat()) @@ -189,14 +189,16 @@ async function getUser (req, res, next) { const userRepo = req.ctx.repositories.getBaseUserRepository() // This is simple, we can just call our function - const result = await userRepo.findOneByUsernameAndOrgShortname(username, orgShortName, {}, LEGACY_FORMAT) + const result = await userRepo.findLegacyUserByUsernameAndOrgShortname(username, orgShortName) if (!result) { logger.info({ uuid: req.ctx.uuid, message: username + ' does not exist.' }) return res.status(404).json(error.userDne(username)) } - const rawResult = result.toObject() + // The compatibility reader returns a plain legacy object; retain document support + // while this legacy controller remains available. + const rawResult = result.toObject ? result.toObject() : result delete rawResult._id delete rawResult.__v @@ -211,7 +213,7 @@ async function getUser (req, res, next) { /** * Get details on ID quota for an org with the specified org shortname. - * Called by GET /api/registry/org/{shortname}/id_quota, GET /api/org/{shortname}/id_quota + * Called by GET /api/org/{shortname}/id_quota * * @param {Object} req - The request object * @param {Object} res - The response object @@ -222,22 +224,23 @@ async function getOrgIdQuota (req, res, next) { try { const orgRepo = req.ctx.repositories.getBaseOrgRepository() const shortName = req.ctx.params.shortname - const objectFormat = getObjectFormatForRequest(req) - const isSecretariat = await authContext.isRequesterSecretariat(req, orgRepo, {}, objectFormat) - const isSameOrg = await authContext.isRequesterSameOrg(req, orgRepo, shortName, {}, objectFormat) + const isSecretariat = await authContext.isRequesterSecretariat(req, orgRepo) + const isSameOrg = await authContext.isRequesterSameOrg(req, orgRepo, shortName) if (!isSameOrg && !isSecretariat) { logger.info({ uuid: req.ctx.uuid, message: shortName + ' organization id quota can only be viewed by the users of the same organization or the Secretariat.' }) return res.status(403).json(error.notSameOrgOrSecretariat()) } - const org = await orgRepo.getOrg(shortName, false, {}, objectFormat) + // Legacy and registry quota payloads are already identical, so read BaseOrg + // directly instead of applying the legacy-org compatibility transformation. + const org = await orgRepo.findOneByShortName(shortName) if (!org) { // a null org can only happen if the requestor is the Secretariat logger.info({ uuid: req.ctx.uuid, message: shortName + ' organization does not exist.' }) return res.status(404).json(error.orgDnePathParam(shortName)) } - const returnPayload = await orgRepo.getOrgIdQuota(org, objectFormat) + const returnPayload = await orgRepo.getOrgIdQuota(org) logger.info({ uuid: req.ctx.uuid, message: 'The organization\'s id quota was returned to the user.', details: returnPayload }) return res.status(200).json(returnPayload) } catch (err) { @@ -680,7 +683,7 @@ async function resetSecret (req, res, next) { const orgRepo = req.ctx.repositories.getBaseOrgRepository() const userRepo = req.ctx.repositories.getBaseUserRepository() - const objectFormat = getObjectFormatForRequest(req) + const objectFormat = getObjectFormatForResetSecret(req) try { session.startTransaction({ readPreference: 'primary' }) diff --git a/src/controller/registry.controller/index.js b/src/controller/registry.controller/index.js index 7350a564d..97ef86c4f 100644 --- a/src/controller/registry.controller/index.js +++ b/src/controller/registry.controller/index.js @@ -267,7 +267,7 @@ router.get('/registry/org/:shortname/id_quota', query().custom((query) => { return mw.validateQueryParameterNames(query, ['']) }), parseError, parseGetParams, - controller.ORG_ID_QUOTA) + registryOrgController.ORG_ID_QUOTA) router.get('/registry/org/:identifier', /* diff --git a/src/controller/registry.controller/org.registry.controller.js b/src/controller/registry.controller/org.registry.controller.js index 56ff62871..171fc4dd8 100644 --- a/src/controller/registry.controller/org.registry.controller.js +++ b/src/controller/registry.controller/org.registry.controller.js @@ -130,7 +130,6 @@ async function getAllOrgs (req, res, next) { try { returnValue = await repo.getAllOrgs({ ...options }, REGISTRY_FORMAT, isSecretariat) - // fetch conversations for (let i = 0; i < returnValue.organizations.length; i++) { const conversation = await conversationRepo.getAllByTargetUUID(returnValue.organizations[i].UUID, isSecretariat) returnValue.organizations[i].conversation = conversation?.length ? conversation : undefined @@ -235,6 +234,40 @@ async function getOrg (req, res, next) { } } +/** + * Retrieves an organization's CVE ID quota. + * + * @param {object} req - The Express request object. + * @param {object} res - The Express response object. + * @param {function} next - The next middleware function. + * @returns {Promise} - A promise that resolves when the response is sent. + */ +async function getOrgIdQuota (req, res, next) { + try { + const orgRepo = req.ctx.repositories.getBaseOrgRepository() + const shortName = req.ctx.params.shortname + const isSecretariat = await authContext.isRequesterSecretariat(req, orgRepo) + const isSameOrg = await authContext.isRequesterSameOrg(req, orgRepo, shortName) + + if (!isSameOrg && !isSecretariat) { + logger.info({ uuid: req.ctx.uuid, message: shortName + ' organization id quota can only be viewed by the users of the same organization or the Secretariat.' }) + return res.status(403).json(error.notSameOrgOrSecretariat()) + } + + const org = await orgRepo.findOneByShortName(shortName) + if (!org) { + logger.info({ uuid: req.ctx.uuid, message: shortName + ' organization does not exist.' }) + return res.status(404).json(error.orgDnePathParam(shortName)) + } + + const returnPayload = await orgRepo.getOrgIdQuota(org) + logger.info({ uuid: req.ctx.uuid, message: 'The organization\'s id quota was returned to the user.', details: returnPayload }) + return res.status(200).json(returnPayload) + } catch (err) { + next(err) + } +} + /** * Creates a new registry organization. * @@ -748,7 +781,7 @@ async function removeOverseeRelationship (req, res, next) { * @param {object} req - The Express request object, containing the organization shortname in `req.ctx.params.shortname`. * @param {object} res - The Express response object. * @param {function} next - The next middleware function. - * @returns {Promise} - A promise that resolves when the response is sent. Response body includes 'role' field for admins. + * @returns {Promise} - A promise that resolves when the response is sent. Registry-format responses include 'role' for admins. * @description This endpoint is accessible to Secretariat only. It retrieves user information for any organization. * Called by GET /api/registry/org/:shortname/users */ @@ -782,10 +815,9 @@ async function getUsers (req, res, next) { return res.status(403).json(error.notSameOrgOrSecretariat()) } - // This should always return Registry typed - const payload = await userRepo.getAllUsersByOrgShortname(orgShortName, options, REGISTRY_FORMAT) + const payload = await userRepo.getAllUsersByOrgShortname(orgShortName, options) - // Hydrate the role field + // Hydrate the role field for registry responses. const org = await orgRepo.findOneByShortName(orgShortName) payload.users.forEach(user => { user.role = org.admins.includes(user.UUID) ? 'ADMIN' : user.role // Default to existing role if not admin @@ -1006,6 +1038,7 @@ async function editConversationForOrg (req, res, next) { module.exports = { ALL_ORGS: getAllOrgs, SINGLE_ORG: getOrg, + ORG_ID_QUOTA: getOrgIdQuota, CREATE_ORG: createOrg, UPDATE_ORG: updateOrg, DELETE_ORG: deleteOrg, diff --git a/src/controller/registry.controller/user.registry.controller.js b/src/controller/registry.controller/user.registry.controller.js index 2ffddf442..8cb5d393b 100644 --- a/src/controller/registry.controller/user.registry.controller.js +++ b/src/controller/registry.controller/user.registry.controller.js @@ -25,7 +25,7 @@ function removeImmutableUpdateFields (body) { * @param {object} req - The Express request object. * @param {object} res - The Express response object. * @param {function} next - The next middleware function. - * @returns {Promise} - A promise that resolves when the response is sent. Response body includes 'role' field for admins. + * @returns {Promise} - A promise that resolves when the response is sent. Registry-format responses include 'role' for admins. * @description This endpoint is accessible to Secretariat only. It retrieves a list of all registry users. * Called by GET /api/registry/users */ @@ -85,7 +85,7 @@ async function getAllUsers (req, res, next) { * @param {object} req - The Express request object. * @param {object} res - The Express response object. * @param {function} next - The next middleware function. - * @returns {Promise} - A promise that resolves when the response is sent. Response body includes 'role' field for admins. + * @returns {Promise} - A promise that resolves when the response is sent. Registry-format responses include 'role' for admins. * @description This endpoint is accessible to Secretariat only. It retrieves information about the specified registry user. * Called by GET /api/registry/org/:shortname/user/:username */ @@ -165,7 +165,7 @@ async function getUser (req, res, next) { } } - const user = result.toObject() + const user = result.toObject ? result.toObject() : result const userPayload = _.omit(user, ['secret', '_id', '__v']) if (org.admins?.includes(userPayload.UUID)) { userPayload.role = 'ADMIN' diff --git a/src/repositories/baseOrgRepository.js b/src/repositories/baseOrgRepository.js index 8585ef2aa..6a38ce680 100644 --- a/src/repositories/baseOrgRepository.js +++ b/src/repositories/baseOrgRepository.js @@ -17,9 +17,17 @@ const { handleAuthorityModelChange } = require('./baseOrgRepositoryHelpers') const { normalizeOrgCveWebsiteUpdateDate } = require('../utils/dateOnly') +const { toLegacyOrg, toBaseOrgInput } = require('../utils/orgCompatibility') const RegistryOrgResponseSchema = require('../../schemas/registry-org/get-registry-org-response.json') const INTERNAL_UNDERSCORE_FIELDS = ['_id', '__t', '__v'] +const LEGACY_SELECT_FIELD_MAP = { + name: 'long_name', + 'authority.active_roles': 'authority', + 'policies.id_quota': 'id_quota', + 'time.created': 'created', + 'time.modified': 'last_updated' +} function exactCaseInsensitiveRegex (value) { return new RegExp(`^${_.escapeRegExp(String(value))}$`, 'i') @@ -29,6 +37,52 @@ function isResponseExtensionField (key) { return key.startsWith('_') && !INTERNAL_UNDERSCORE_FIELDS.includes(key) } +function applyLegacySelect (legacyOrg, select) { + if (!select) return legacyOrg + + if (typeof select === 'string') { + const fields = select.trim().split(/\s+/).filter(Boolean) + const includedFields = fields.filter(field => !field.startsWith('-')) + const excludedFields = fields.filter(field => field.startsWith('-')).map(field => field.slice(1)) + + if (includedFields.length > 0) return _.pick(legacyOrg, includedFields) + return _.omit(legacyOrg, excludedFields) + } + + if (_.isPlainObject(select)) { + const includedFields = Object.entries(select) + .filter(([, include]) => include) + .map(([field]) => field) + const excludedFields = Object.entries(select) + .filter(([, include]) => !include) + .map(([field]) => field) + + if (includedFields.length > 0) return _.pick(legacyOrg, includedFields) + return _.omit(legacyOrg, excludedFields) + } + + return legacyOrg +} + +function toBaseOrgSelect (select) { + if (typeof select === 'string') { + return select.split(/\s+/).filter(Boolean).map(field => { + const prefix = field.startsWith('-') ? '-' : '' + const legacyField = prefix ? field.slice(1) : field + return `${prefix}${LEGACY_SELECT_FIELD_MAP[legacyField] || legacyField}` + }).join(' ') + } + + if (_.isPlainObject(select)) { + return Object.entries(select).reduce((mappedSelect, [field, value]) => { + mappedSelect[LEGACY_SELECT_FIELD_MAP[field] || field] = value + return mappedSelect + }, {}) + } + + return select +} + function maskObjectToSchemaProperties (obj, schema) { if (!_.isPlainObject(obj) || !schema?.properties) return obj @@ -60,12 +114,12 @@ function maskOrgForResponseSchema (orgObj, fieldsToPreserve = []) { } /** - * @function setAggregateOrgObj + * @function setAggregateLegacyOrgObj * @description Constructs the aggregation pipeline for legacy organization objects. * @param {object} query - The query object to match. * @returns {Array} The aggregation pipeline. */ -function setAggregateOrgObj (query) { +function setAggregateLegacyOrgObj (query) { return [ { $match: query @@ -75,10 +129,11 @@ function setAggregateOrgObj (query) { _id: false, UUID: true, short_name: true, - name: true, - 'authority.active_roles': true, - 'policies.id_quota': true, - time: true + long_name: true, + authority: true, + id_quota: true, + created: true, + last_updated: true } } ] @@ -178,10 +233,9 @@ class BaseOrgRepository extends BaseRepository { * @returns {Promise} The organization object. */ async findOneByShortNameWithSelect (shortName, select, options = {}, returnLegacyFormat = false) { - const OrgRepository = require('./orgRepository') - const legacyOrgRepo = new OrgRepository() - if (returnLegacyFormat) return await legacyOrgRepo.findOneByShortName(shortName, options, select) - return await BaseOrgModel.findOne({ short_name: shortName }, null, options).select(select) + const selectedFields = returnLegacyFormat ? toBaseOrgSelect(select) : select + const data = await BaseOrgModel.findOne({ short_name: shortName }, null, options).select(selectedFields) + return returnLegacyFormat ? applyLegacySelect(toLegacyOrg(data), select) : data } /** @@ -194,11 +248,8 @@ class BaseOrgRepository extends BaseRepository { * @returns {Promise} The organization object. */ async findOneByShortName (shortName, options = {}, returnLegacyFormat = false, projection = {}) { - const OrgRepository = require('./orgRepository') - const legacyOrgRepo = new OrgRepository() - if (returnLegacyFormat) return await legacyOrgRepo.findOneByShortName(shortName, options, projection) const data = await BaseOrgModel.findOne({ short_name: shortName }, projection, options) - return data + return returnLegacyFormat ? toLegacyOrg(data) : data } /** @@ -212,10 +263,8 @@ class BaseOrgRepository extends BaseRepository { * @returns {Promise} The organization object. */ async findOneByUUID (UUID, options = {}, returnLegacyFormat = false, projection = {}) { - const OrgRepository = require('./orgRepository') - const legacyOrgRepo = new OrgRepository() - if (returnLegacyFormat) return await legacyOrgRepo.findOneByUUID(UUID, options, projection) - return await BaseOrgModel.findOne({ UUID: UUID }, projection, options) + const data = await BaseOrgModel.findOne({ UUID: UUID }, projection, options) + return returnLegacyFormat ? toLegacyOrg(data) : data } /** @@ -528,17 +577,13 @@ class BaseOrgRepository extends BaseRepository { * @returns {Promise} Paginated result containing organizations and metadata. */ async getAllOrgs (options = {}, returnLegacyFormat = false, isSecretariat = false) { - const OrgRepository = require('./orgRepository') - const orgRepo = new OrgRepository() - let pg if (returnLegacyFormat) { - const agt = setAggregateOrgObj({}) - pg = await orgRepo.aggregatePaginate(agt, options) - } else { - const agt = setAggregateRegistryOrgObj({}, isSecretariat) - pg = await this.aggregatePaginate(agt, options) + return this.getLegacyOrgs(options) } + const agt = setAggregateRegistryOrgObj({}, isSecretariat) + const pg = await this.aggregatePaginate(agt, options) + // Strip nulls returned by DocumentDB to prevent schema validation errors if (pg.itemsList) { pg.itemsList = pg.itemsList.map(org => { @@ -546,12 +591,40 @@ class BaseOrgRepository extends BaseRepository { delete org.reports_to } normalizeOrgCveWebsiteUpdateDate(org, { output: true }) - return returnLegacyFormat ? org : filterOrg(org, isSecretariat, true) + return filterOrg(org, isSecretariat, true) }) } const data = { organizations: pg.itemsList } - if (!returnLegacyFormat || pg.itemCount >= options.limit) { + data.totalCount = pg.itemCount + data.itemsPerPage = pg.itemsPerPage + data.pageCount = pg.pageCount + data.currentPage = pg.currentPage + data.prevPage = pg.prevPage + data.nextPage = pg.nextPage + return data + } + + /** + * Retrieves organizations from BaseOrg and converts them to the legacy API + * representation. This is the compatibility read used by /api/org. + * + * @param {object} [options={}] - Pagination and query options. + * @returns {Promise} Paginated legacy organization response. + */ + async getLegacyOrgs (options = {}) { + const agt = setAggregateLegacyOrgObj({}) + const pg = await this.aggregatePaginate(agt, options) + + if (pg.itemsList) { + pg.itemsList = pg.itemsList.map(org => { + normalizeOrgCveWebsiteUpdateDate(org, { output: true }) + return toLegacyOrg(org) + }) + } + + const data = { organizations: pg.itemsList } + if (pg.itemCount >= options.limit) { data.totalCount = pg.itemCount data.itemsPerPage = pg.itemsPerPage data.pageCount = pg.pageCount @@ -591,13 +664,17 @@ class BaseOrgRepository extends BaseRepository { * @returns {Promise} The sanitized organization object. */ async getOrg (identifier, identifierIsUUID = false, options = {}, returnLegacyFormat = false, isSecretariat = false) { + if (returnLegacyFormat) { + return this.getLegacyOrg(identifier, identifierIsUUID, options) + } + const { deepRemoveEmpty } = require('../utils/utils') const projection = getOrgProjection(isSecretariat) const data = identifierIsUUID - ? await this.findOneByUUID(identifier, options, returnLegacyFormat, projection) - : await this.findOneByShortName(identifier, options, returnLegacyFormat, projection) + ? await this.findOneByUUID(identifier, options, false, projection) + : await this.findOneByShortName(identifier, options, false, projection) if (!data) return null - const result = data.toObject() + const result = data.toObject ? data.toObject() : data const parentOrg = await BaseOrgModel.findOne({ oversees: result.UUID }).select('UUID').lean() if (parentOrg) { @@ -623,24 +700,41 @@ class BaseOrgRepository extends BaseRepository { } } - if (returnLegacyFormat) { - normalizeOrgCveWebsiteUpdateDate(result, { output: true }) - return deepRemoveEmpty(result) - } return deepRemoveEmpty(filterOrg(result, isSecretariat, true)) } + /** + * Retrieves a BaseOrg and converts it to the legacy API representation. + * This is the compatibility read used by /api/org/:identifier. + * + * @param {string} identifier - The identifier (UUID or short name). + * @param {boolean} [identifierIsUUID=false] - True if identifier is a UUID. + * @param {object} [options={}] - Optional settings for the repository query. + * @returns {Promise} The legacy organization object. + */ + async getLegacyOrg (identifier, identifierIsUUID = false, options = {}) { + const { deepRemoveEmpty } = require('../utils/utils') + const projection = getOrgProjection(false) + const data = identifierIsUUID + ? await this.findOneByUUID(identifier, options, false, projection) + : await this.findOneByShortName(identifier, options, false, projection) + if (!data) return null + + const result = data.toObject ? data.toObject() : data + normalizeOrgCveWebsiteUpdateDate(result, { output: true }) + return deepRemoveEmpty(toLegacyOrg(result)) + } + /** * @async * @function getOrgIdQuota * @description Calculates the ID quota and availability for an organization. * @param {object} org - The organization object. - * @param {boolean} [useLegacy=false] - If true, uses legacy policies for calculation. * @returns {Promise} Object containing id_quota, total_reserved, and available. */ - async getOrgIdQuota (org, useLegacy = false) { + async getOrgIdQuota (org) { const returnPayload = { - id_quota: useLegacy ? org.policies.id_quota : org.id_quota, + id_quota: org.id_quota, total_reserved: null, available: null } @@ -1322,20 +1416,15 @@ class BaseOrgRepository extends BaseRepository { * @returns {object} The converted registry organization object. */ convertLegacyToRegistry (legacyOrg) { - let newRoles = [] - if (legacyOrg?.authority?.active_roles?.includes('SECRETARIAT')) { - newRoles.push('SECRETARIAT') - } else { - newRoles = legacyOrg?.authority?.active_roles - } + const baseOrg = toBaseOrgInput(legacyOrg) return { - long_name: legacyOrg?.name ?? null, - short_name: legacyOrg?.short_name ?? null, - UUID: legacyOrg?.UUID ?? null, - authority: newRoles || ['CNA'], - id_quota: legacyOrg?.policies?.id_quota ?? null, - created: legacyOrg?.time?.created ?? null, - last_updated: legacyOrg?.time?.modified ?? null + long_name: baseOrg.long_name ?? null, + short_name: baseOrg.short_name ?? null, + UUID: baseOrg.UUID ?? null, + authority: baseOrg.authority || ['CNA'], + id_quota: baseOrg.id_quota ?? null, + created: baseOrg.created ?? null, + last_updated: baseOrg.last_updated ?? null } } @@ -1346,21 +1435,7 @@ class BaseOrgRepository extends BaseRepository { * @returns {object} The converted legacy organization object. */ convertRegistryToLegacy (registryOrg) { - return { - name: registryOrg?.long_name ?? null, - short_name: registryOrg?.short_name ?? null, - UUID: registryOrg?.UUID ?? null, - authority: { - active_roles: registryOrg?.authority || ['CNA'] - }, - policies: { - id_quota: registryOrg?.id_quota ?? null - }, - time: { - created: registryOrg?.created ?? null, - modified: registryOrg?.last_updated ?? null - } - } + return toLegacyOrg(registryOrg) } } module.exports = BaseOrgRepository diff --git a/src/repositories/baseUserRepository.js b/src/repositories/baseUserRepository.js index ae84f9764..c5f281dc4 100644 --- a/src/repositories/baseUserRepository.js +++ b/src/repositories/baseUserRepository.js @@ -9,6 +9,7 @@ const cryptoRandomString = require('crypto-random-string') const UserRepository = require('./userRepository') const getConstants = require('../constants').getConstants const _ = require('lodash') +const { toLegacyUser } = require('../utils/userCompatibility') const skipNulls = (objValue, srcValue) => { if (_.isArray(objValue)) { @@ -129,7 +130,6 @@ class BaseUserRepository extends BaseRepository { * @returns {Promise} The user object or null if not found. */ async findOneByUsernameAndOrgShortname (username, orgShortName, options = {}, isLegacyObject = false) { - const legacyUserRepo = new UserRepository() const org = await BaseOrgModel.findOne({ short_name: orgShortName }, null, options) if (!org || !Array.isArray(org.users)) { return null @@ -138,11 +138,31 @@ class BaseUserRepository extends BaseRepository { const user = await BaseUser.findOne({ username: username, UUID: { $in: org.users } }, null, options) if (isLegacyObject && user) { - return await legacyUserRepo.findOneByUUID(user.UUID) || null + return toLegacyUser(user, org) } return user || null } + /** + * Retrieves a BaseUser by organization short name and converts it to the + * legacy API representation. This is the compatibility read used by + * /api/org/:shortname/user/:username. + * + * @param {string} username - The username to find. + * @param {string} orgShortName - The owning organization short name. + * @param {object} [options={}] - Optional settings for the repository query. + * @returns {Promise} The legacy user object or null if not found. + */ + async findLegacyUserByUsernameAndOrgShortname (username, orgShortName, options = {}) { + const org = await BaseOrgModel.findOne({ short_name: orgShortName }, null, options) + if (!org || !Array.isArray(org.users)) { + return null + } + + const user = await BaseUser.findOne({ username: username, UUID: { $in: org.users } }, null, options) + return user ? toLegacyUser(user, org) : null + } + /** * @async * @function findOneByUserNameAndOrgUUID @@ -845,38 +865,29 @@ class BaseUserRepository extends BaseRepository { * * @param {string} orgShortname - The short name of the organization. * @param {object} options - Pagination options (e.g., limit, page). - * @param {boolean} isLegacyObject - Whether to return users in the legacy format. * @returns {Promise} An object containing the list of users and pagination details. */ - async getAllUsersByOrgShortname (orgShortname, options = {}, isLegacyObject = false) { + async getAllUsersByOrgShortname (orgShortname, options = {}) { const CONSTANTS = getConstants() const baseOrgRepository = new BaseOrgRepository() - const userRepository = new UserRepository() const org = await baseOrgRepository.findOneByShortName(orgShortname) - const usersInOrg = org.toObject().users + const orgObject = toPlainObject(org) + const usersInOrg = orgObject.users - let agt = {} - let pg - if (isLegacyObject) { - agt = setAggregateUserObj({ org_UUID: org.UUID }) - pg = await userRepository.aggregatePaginate(agt, options) - } else { - // wtf - agt = [ - { - $match: { - UUID: { $in: usersInOrg } - } - }, - { - $project: { - secret: false, - _id: false - } + const agt = [ + { + $match: { + UUID: { $in: usersInOrg } } - ] - pg = await this.aggregatePaginate(agt, options) - } + }, + { + $project: { + secret: false, + _id: false + } + } + ] + const pg = await this.aggregatePaginate(agt, options) const payload = { users: pg.itemsList } @@ -892,6 +903,49 @@ class BaseUserRepository extends BaseRepository { return payload } + /** + * Retrieves users from BaseUser for an organization and converts them to + * the legacy API representation. This is the compatibility read used by + * /api/org/:shortname/users. + * + * @param {string} orgShortname - The owning organization short name. + * @param {object} [options={}] - Pagination options. + * @returns {Promise} Paginated legacy user response. + */ + async getLegacyUsersByOrgShortname (orgShortname, options = {}) { + const CONSTANTS = getConstants() + const baseOrgRepository = new BaseOrgRepository() + const org = await baseOrgRepository.findOneByShortName(orgShortname) + const orgObject = toPlainObject(org) + const usersInOrg = orgObject.users + const agt = [ + { + $match: { + UUID: { $in: usersInOrg } + } + }, + { + $project: { + secret: false, + _id: false + } + } + ] + const pg = await this.aggregatePaginate(agt, options) + const payload = { users: pg.itemsList.map(user => toLegacyUser(user, orgObject)) } + + if (pg.itemCount >= CONSTANTS.PAGINATOR_OPTIONS.limit) { + payload.totalCount = pg.itemCount + payload.itemsPerPage = pg.itemsPerPage + payload.pageCount = pg.pageCount + payload.currentPage = pg.currentPage + payload.prevPage = pg.prevPage + payload.nextPage = pg.nextPage + } + + return payload + } + /** * @async * @function populateUsers diff --git a/src/utils/orgCompatibility.js b/src/utils/orgCompatibility.js new file mode 100644 index 000000000..0d6963c55 --- /dev/null +++ b/src/utils/orgCompatibility.js @@ -0,0 +1,70 @@ +/** + * Compatibility conversions between the registry BaseOrg representation and + * the legacy Org API representation. These functions only transform values; + * they never persist or synchronize a second organization record. + */ + +function toPlainObject (value) { + return value?.toObject ? value.toObject() : value +} + +function hasOwn (object, property) { + return Object.prototype.hasOwnProperty.call(object || {}, property) +} + +function toLegacyOrg (baseOrg) { + if (!baseOrg) return null + + const org = toPlainObject(baseOrg) + return { + name: org.long_name ?? null, + short_name: org.short_name ?? null, + UUID: org.UUID ?? null, + authority: { + active_roles: Array.isArray(org.authority) ? [...org.authority] : ['CNA'] + }, + policies: { + id_quota: org.id_quota ?? null + }, + time: { + created: org.created ?? null, + modified: org.last_updated ?? null + } + } +} + +function toBaseOrgInput (legacyOrg) { + if (!legacyOrg) return null + + const org = toPlainObject(legacyOrg) + const baseOrg = {} + + if (hasOwn(org, 'name')) baseOrg.long_name = org.name + if (hasOwn(org, 'short_name')) baseOrg.short_name = org.short_name + if (hasOwn(org, 'UUID')) baseOrg.UUID = org.UUID + if (hasOwn(org.authority, 'active_roles')) baseOrg.authority = [...org.authority.active_roles] + if (hasOwn(org.policies, 'id_quota')) baseOrg.id_quota = org.policies.id_quota + if (hasOwn(org.time, 'created')) baseOrg.created = org.time.created + if (hasOwn(org.time, 'modified')) baseOrg.last_updated = org.time.modified + + return baseOrg +} + +function toBaseOrgUpdate (legacyParameters) { + if (!legacyParameters) return null + + const parameters = toPlainObject(legacyParameters) + const baseOrgUpdate = toBaseOrgInput(parameters) + + if (hasOwn(parameters, 'new_short_name')) { + baseOrgUpdate.short_name = parameters.new_short_name + } + + return baseOrgUpdate +} + +module.exports = { + toLegacyOrg, + toBaseOrgInput, + toBaseOrgUpdate +} diff --git a/src/utils/userCompatibility.js b/src/utils/userCompatibility.js new file mode 100644 index 000000000..b9df85223 --- /dev/null +++ b/src/utils/userCompatibility.js @@ -0,0 +1,53 @@ +/** + * Compatibility conversion between the registry BaseUser representation and + * the legacy User API representation. The owning BaseOrg supplies the legacy + * org UUID and the ADMIN role relationship. + */ + +function toPlainObject (value) { + return value?.toObject ? value.toObject() : value +} + +function definedValues (object) { + return Object.fromEntries(Object.entries(object).filter(([, value]) => value !== undefined)) +} + +function toLegacyUser (baseUser, owningBaseOrg) { + if (!baseUser) return null + + const user = toPlainObject(baseUser) + const org = toPlainObject(owningBaseOrg) || {} + const isAdmin = Array.isArray(org.admins) && org.admins.includes(user.UUID) + + const legacyUser = { + UUID: user.UUID, + username: user.username, + authority: { + active_roles: isAdmin ? ['ADMIN'] : [] + }, + active: user.status === 'active', + time: { + created: user.created, + modified: user.last_updated + } + } + + if (user.name !== undefined) { + legacyUser.name = definedValues({ + first: user.name?.first, + middle: user.name?.middle, + last: user.name?.last, + suffix: user.name?.suffix + }) + } + + if (org.UUID !== undefined) { + legacyUser.org_UUID = org.UUID + } + + return legacyUser +} + +module.exports = { + toLegacyUser +} From 73c31a73cd601d3e2141a35720dd786bda35e7f4 Mon Sep 17 00:00:00 2001 From: James Dalphond Date: Tue, 18 Aug 2026 09:22:04 -0400 Subject: [PATCH 02/20] Updating tests to cover moving to the compatibility layer. Also split out legacy and registry tests to more easily tell which files are which. --- test/unit-tests/org/baseOrgRepositoryTest.js | 45 +++++++--- .../org/{ => legacy}/orgCreateADPTest.js | 8 +- .../org/{ => legacy}/orgCreateTest.js | 24 +++--- .../org/{ => legacy}/orgGetAllTest.js | 16 ++-- .../org/{ => legacy}/orgGetIdQuotaTest.js | 14 ++-- .../org/{ => legacy}/orgGetSingleTest.js | 20 ++--- .../org/{ => legacy}/orgUpdateTest.js | 14 ++-- test/unit-tests/org/orgCompatibilityTest.js | 69 +++++++++++++++ .../registryOrgControllerTest.js | 2 +- .../registryOrgGetSingleTest.js | 2 +- .../registry}/registryOrgPayloadTest.js | 4 +- .../baseUserRepositoryFormatSemanticsTest.js | 83 +++++++++++++++++++ test/unit-tests/user/userCompatibilityTest.js | 58 +++++++++++++ test/unit-tests/user/userGetAllTest.js | 12 +-- test/unit-tests/user/userGetSingleTest.js | 8 +- 15 files changed, 307 insertions(+), 72 deletions(-) rename test/unit-tests/org/{ => legacy}/orgCreateADPTest.js (90%) rename test/unit-tests/org/{ => legacy}/orgCreateTest.js (93%) rename test/unit-tests/org/{ => legacy}/orgGetAllTest.js (94%) rename test/unit-tests/org/{ => legacy}/orgGetIdQuotaTest.js (95%) rename test/unit-tests/org/{ => legacy}/orgGetSingleTest.js (89%) rename test/unit-tests/org/{ => legacy}/orgUpdateTest.js (96%) create mode 100644 test/unit-tests/org/orgCompatibilityTest.js rename test/unit-tests/org/{ => registry}/registryOrgControllerTest.js (98%) rename test/unit-tests/org/{ => registry}/registryOrgGetSingleTest.js (99%) rename test/unit-tests/{registry-org => org/registry}/registryOrgPayloadTest.js (96%) create mode 100644 test/unit-tests/user/userCompatibilityTest.js diff --git a/test/unit-tests/org/baseOrgRepositoryTest.js b/test/unit-tests/org/baseOrgRepositoryTest.js index cad1297d7..8422d2a20 100644 --- a/test/unit-tests/org/baseOrgRepositoryTest.js +++ b/test/unit-tests/org/baseOrgRepositoryTest.js @@ -3,7 +3,6 @@ const sinon = require('sinon') const BaseOrgRepository = require('../../../src/repositories/baseOrgRepository') const BaseOrgModel = require('../../../src/model/baseorg') -const OrgRepository = require('../../../src/repositories/orgRepository') describe('Testing BaseOrgRepository', () => { afterEach(() => { @@ -52,21 +51,47 @@ describe('Testing BaseOrgRepository', () => { expect(query.$and[0].short_name.$not.source).to.equal('^Existing\\.Org$') }) - it('Should use the legacy OrgRepository instance for selected short name lookups in legacy format', async () => { + it('Should convert selected BaseOrg short name lookups to legacy format', async () => { const baseOrgRepo = new BaseOrgRepository() const options = { session: 'mock-session' } const select = 'UUID short_name' - const org = { UUID: 'org-uuid', short_name: 'mitre' } - const findOneByShortName = sinon.stub(OrgRepository.prototype, 'findOneByShortName').resolves(org) + const org = { + UUID: 'org-uuid', + short_name: 'mitre', + long_name: 'MITRE', + authority: ['SECRETARIAT'], + id_quota: 0 + } + const selectQuery = { select: sinon.stub().resolves(org) } + const findOneByShortName = sinon.stub(BaseOrgModel, 'findOne').returns(selectQuery) const result = await baseOrgRepo.findOneByShortNameWithSelect('mitre', select, options, true) - expect(result).to.equal(org) + expect(result).to.deep.equal({ UUID: 'org-uuid', short_name: 'mitre' }) expect(findOneByShortName.calledOnce).to.equal(true) - expect(findOneByShortName.firstCall.args).to.deep.equal([ - 'mitre', - options, - select - ]) + expect(findOneByShortName.firstCall.args).to.deep.equal([{ short_name: 'mitre' }, null, options]) + expect(selectQuery.select.calledOnceWith(select)).to.equal(true) + }) + + it('Should convert a BaseOrg result to legacy format only once', async () => { + const baseOrgRepo = new BaseOrgRepository() + const baseOrg = { + UUID: 'org-uuid', + short_name: 'mitre', + long_name: 'MITRE', + authority: ['SECRETARIAT'], + id_quota: 100 + } + sinon.stub(BaseOrgModel, 'findOne').resolves(baseOrg) + + const result = await baseOrgRepo.getLegacyOrg('mitre') + + expect(result).to.deep.equal({ + name: 'MITRE', + short_name: 'mitre', + UUID: 'org-uuid', + authority: { active_roles: ['SECRETARIAT'] }, + policies: { id_quota: 100 } + }) }) }) diff --git a/test/unit-tests/org/orgCreateADPTest.js b/test/unit-tests/org/legacy/orgCreateADPTest.js similarity index 90% rename from test/unit-tests/org/orgCreateADPTest.js rename to test/unit-tests/org/legacy/orgCreateADPTest.js index e982cd20d..470fd1068 100644 --- a/test/unit-tests/org/orgCreateADPTest.js +++ b/test/unit-tests/org/legacy/orgCreateADPTest.js @@ -6,10 +6,10 @@ const { faker } = require('@faker-js/faker') const expect = chai.expect const mongoose = require('mongoose') -const { ORG_CREATE_SINGLE } = require('../../../src/controller/org.controller/org.controller.js') -const CONSTANTS = require('../../../src/constants/index.js') -const BaseOrgRepository = require('../../../src/repositories/baseOrgRepository.js') -const BaseUserRepository = require('../../../src/repositories/baseUserRepository.js') +const { ORG_CREATE_SINGLE } = require('../../../../src/controller/org.controller/org.controller.js') +const CONSTANTS = require('../../../../src/constants/index.js') +const BaseOrgRepository = require('../../../../src/repositories/baseOrgRepository.js') +const BaseUserRepository = require('../../../../src/repositories/baseUserRepository.js') const stubAdpOrg = { short_name: 'adpOrg', diff --git a/test/unit-tests/org/orgCreateTest.js b/test/unit-tests/org/legacy/orgCreateTest.js similarity index 93% rename from test/unit-tests/org/orgCreateTest.js rename to test/unit-tests/org/legacy/orgCreateTest.js index a2d28ff7a..a2968f097 100644 --- a/test/unit-tests/org/orgCreateTest.js +++ b/test/unit-tests/org/legacy/orgCreateTest.js @@ -7,21 +7,21 @@ const expect = chai.expect const mongoose = require('mongoose') // Mock Repositories and Controller -const OrgRepository = require('../../../src/repositories/orgRepository.js') -const UserRepository = require('../../../src/repositories/userRepository.js') -const orgController = require('../../../src/controller/org.controller/org.controller.js') -const BaseOrgRepository = require('../../../src/repositories/baseOrgRepository.js') -const BaseUserRepository = require('../../../src/repositories/baseUserRepository.js') -const SecretariatOrgModel = require('../../../src/model/secretariatorg.js') -const CNAOrgModel = require('../../../src/model/cnaorg.js') -const ADPOrgModel = require('../../../src/model/adporg.js') -const Org = require('../../../src/model/org.js') -const AuditRepository = require('../../../src/repositories/auditRepository.js') +const OrgRepository = require('../../../../src/repositories/orgRepository.js') +const UserRepository = require('../../../../src/repositories/userRepository.js') +const orgController = require('../../../../src/controller/org.controller/org.controller.js') +const BaseOrgRepository = require('../../../../src/repositories/baseOrgRepository.js') +const BaseUserRepository = require('../../../../src/repositories/baseUserRepository.js') +const SecretariatOrgModel = require('../../../../src/model/secretariatorg.js') +const CNAOrgModel = require('../../../../src/model/cnaorg.js') +const ADPOrgModel = require('../../../../src/model/adporg.js') +const Org = require('../../../../src/model/org.js') +const AuditRepository = require('../../../../src/repositories/auditRepository.js') // Mocks for error messages and constants -const { OrgControllerError } = require('../../../src/controller/org.controller/error.js') +const { OrgControllerError } = require('../../../../src/controller/org.controller/error.js') const error = new OrgControllerError() -const { getConstants } = require('../../../src/constants/index.js') // Updated import +const { getConstants } = require('../../../../src/constants/index.js') // Updated import // --- Test Fixtures --- const orgFixtures = { diff --git a/test/unit-tests/org/orgGetAllTest.js b/test/unit-tests/org/legacy/orgGetAllTest.js similarity index 94% rename from test/unit-tests/org/orgGetAllTest.js rename to test/unit-tests/org/legacy/orgGetAllTest.js index d6dd4c1fd..4ce52cf04 100644 --- a/test/unit-tests/org/orgGetAllTest.js +++ b/test/unit-tests/org/legacy/orgGetAllTest.js @@ -9,12 +9,12 @@ chai.use(require('chai-http')) // Body Parser Middleware app.use(express.json()) // Allows us to handle raw JSON data app.use(express.urlencoded({ extended: false })) // Allows us to handle url encoded data -const middleware = require('../../../src/middleware/middleware') +const middleware = require('../../../../src/middleware/middleware') app.use(middleware.createCtxAndReqUUID) -const orgFixtures = require('./mockObjects.org') -const orgController = require('../../../src/controller/org.controller/org.controller') -const orgParams = require('../../../src/controller/org.controller/org.middleware') +const orgFixtures = require('../mockObjects.org') +const orgController = require('../../../../src/controller/org.controller/org.controller') +const orgParams = require('../../../../src/controller/org.controller/org.middleware') describe('Testing the GET /org endpoint in Org Controller', () => { let mockSession @@ -34,7 +34,7 @@ describe('Testing the GET /org endpoint in Org Controller', () => { context('Positive Tests', () => { it('Page query param not provided: should list non-paginated orgs because orgs fit in one page', async () => { class GetAllOrgs { - async getAllOrgs () { + async getLegacyOrgs () { return { organizations: orgFixtures.allOrgs } @@ -70,7 +70,7 @@ describe('Testing the GET /org endpoint in Org Controller', () => { const itemsPerPage = 5 class GetAllOrgs { - async getAllOrgs () { + async getLegacyOrgs () { const res = { organizations: [orgFixtures.allOrgs[0], orgFixtures.allOrgs[1], orgFixtures.allOrgs[3], orgFixtures.allOrgs[3], orgFixtures.allOrgs[4]], totalCount: orgFixtures.allOrgs.length, @@ -118,7 +118,7 @@ describe('Testing the GET /org endpoint in Org Controller', () => { const itemsPerPage = 5 class GetAllOrgs { - async getAllOrgs () { + async getLegacyOrgs () { const res = { organizations: [orgFixtures.allOrgs[5], orgFixtures.allOrgs[6], orgFixtures.allOrgs[7], orgFixtures.allOrgs[8]], totalCount: orgFixtures.allOrgs.length, @@ -164,7 +164,7 @@ describe('Testing the GET /org endpoint in Org Controller', () => { it('Page query param provided: should return an empty list because no org exists', async () => { class GetAllOrgs { - async getAllOrgs () { + async getLegacyOrgs () { const res = { organizations: [] } diff --git a/test/unit-tests/org/orgGetIdQuotaTest.js b/test/unit-tests/org/legacy/orgGetIdQuotaTest.js similarity index 95% rename from test/unit-tests/org/orgGetIdQuotaTest.js rename to test/unit-tests/org/legacy/orgGetIdQuotaTest.js index 18170e82f..0f1fba6ed 100644 --- a/test/unit-tests/org/orgGetIdQuotaTest.js +++ b/test/unit-tests/org/legacy/orgGetIdQuotaTest.js @@ -9,17 +9,17 @@ const mongoose = require('mongoose') // Body Parser Middleware app.use(express.json()) // Allows us to handle raw JSON data app.use(express.urlencoded({ extended: false })) // Allows us to handle url encoded data -const middleware = require('../../../src/middleware/middleware') +const middleware = require('../../../../src/middleware/middleware') app.use(middleware.createCtxAndReqUUID) -const Org = require('../../../src/model/org') -const getConstants = require('../../../src/constants').getConstants -const errors = require('../../../src/controller/org.controller/error') +const Org = require('../../../../src/model/org') +const getConstants = require('../../../../src/constants').getConstants +const errors = require('../../../../src/controller/org.controller/error') const error = new errors.OrgControllerError() -const orgFixtures = require('./mockObjects.org') -const orgController = require('../../../src/controller/org.controller/org.controller') -const orgParams = require('../../../src/controller/org.controller/org.middleware') +const orgFixtures = require('../mockObjects.org') +const orgController = require('../../../../src/controller/org.controller/org.controller') +const orgParams = require('../../../../src/controller/org.controller/org.middleware') function setOwningOrgAuthContext (req) { req.ctx.authenticated = true diff --git a/test/unit-tests/org/orgGetSingleTest.js b/test/unit-tests/org/legacy/orgGetSingleTest.js similarity index 89% rename from test/unit-tests/org/orgGetSingleTest.js rename to test/unit-tests/org/legacy/orgGetSingleTest.js index e3e8ead08..c0e90edaf 100644 --- a/test/unit-tests/org/orgGetSingleTest.js +++ b/test/unit-tests/org/legacy/orgGetSingleTest.js @@ -4,11 +4,11 @@ const expect = chai.expect const { faker } = require('@faker-js/faker') const mongoose = require('mongoose') -const { ORG_SINGLE } = require('../../../src/controller/org.controller/org.controller') -const BaseOrgRepository = require('../../../src/repositories/baseOrgRepository.js') -const BaseOrg = require('../../../src/model/baseorg.js') +const { ORG_SINGLE } = require('../../../../src/controller/org.controller/org.controller') +const BaseOrgRepository = require('../../../../src/repositories/baseOrgRepository.js') +const BaseOrg = require('../../../../src/model/baseorg.js') -const { OrgControllerError } = require('../../../src/controller/org.controller/error.js') +const { OrgControllerError } = require('../../../../src/controller/org.controller/error.js') const error = new OrgControllerError() // Test Fixtures @@ -88,7 +88,7 @@ describe('Testing the GET /org/:identifier endpoint in Org Controller', () => { sinon.stub(baseOrgRepo, 'findOneByShortName').resolves(fakeSecretariatOrgDocument) isSecretariatByShortName.resolves(true) sinon.stub(baseOrgRepo, 'isSecretariat').resolves(true) - sinon.stub(baseOrgRepo, 'getOrg').resolves(null) + sinon.stub(baseOrgRepo, 'getLegacyOrg').resolves(null) await ORG_SINGLE(req, res, next) @@ -119,7 +119,7 @@ describe('Testing the GET /org/:identifier endpoint in Org Controller', () => { sinon.stub(baseOrgRepo, 'findOneByShortName').resolves(fakeSecretariatOrgDocument) isSecretariatByShortName.resolves(true) sinon.stub(baseOrgRepo, 'isSecretariat').resolves(true) - sinon.stub(baseOrgRepo, 'getOrg').resolves(null) + sinon.stub(baseOrgRepo, 'getLegacyOrg').resolves(null) await ORG_SINGLE(req, res, next) @@ -134,7 +134,7 @@ describe('Testing the GET /org/:identifier endpoint in Org Controller', () => { sinon.stub(baseOrgRepo, 'findOneByShortName').resolves(fakeSecretariatOrgDocument) isSecretariatByShortName.resolves(true) sinon.stub(baseOrgRepo, 'isSecretariat').resolves(true) - sinon.stub(baseOrgRepo, 'getOrg').resolves(orgFixtures.targetOrg) + sinon.stub(baseOrgRepo, 'getLegacyOrg').resolves(orgFixtures.targetOrg) await ORG_SINGLE(req, res, next) @@ -153,7 +153,7 @@ describe('Testing the GET /org/:identifier endpoint in Org Controller', () => { sinon.stub(baseOrgRepo, 'findOneByUUID').resolves(fakeTargetOrgDocument) sinon.stub(baseOrgRepo, 'getOrgUUID').resolves(orgFixtures.targetOrg.UUID) sinon.stub(baseOrgRepo, 'isSecretariat').resolves(false) - sinon.stub(baseOrgRepo, 'getOrg').resolves(orgFixtures.targetOrg) + sinon.stub(baseOrgRepo, 'getLegacyOrg').resolves(orgFixtures.targetOrg) await ORG_SINGLE(req, res, next) @@ -170,7 +170,7 @@ describe('Testing the GET /org/:identifier endpoint in Org Controller', () => { sinon.stub(baseOrgRepo, 'findOneByShortName').resolves(fakeTargetOrgDocument) sinon.stub(baseOrgRepo, 'findOneByUUID').resolves(fakeTargetOrgDocument) sinon.stub(baseOrgRepo, 'isSecretariat').resolves(false) - sinon.stub(baseOrgRepo, 'getOrg').resolves(orgFixtures.targetOrg) + sinon.stub(baseOrgRepo, 'getLegacyOrg').resolves(orgFixtures.targetOrg) await ORG_SINGLE(req, res, next) @@ -183,7 +183,7 @@ describe('Testing the GET /org/:identifier endpoint in Org Controller', () => { sinon.stub(baseOrgRepo, 'findOneByShortName').resolves(fakeSecretariatOrgDocument) isSecretariatByShortName.resolves(true) sinon.stub(baseOrgRepo, 'isSecretariat').resolves(true) - sinon.stub(baseOrgRepo, 'getOrg').resolves(orgFixtures.targetOrg) + sinon.stub(baseOrgRepo, 'getLegacyOrg').resolves(orgFixtures.targetOrg) await ORG_SINGLE(req, res, next) diff --git a/test/unit-tests/org/orgUpdateTest.js b/test/unit-tests/org/legacy/orgUpdateTest.js similarity index 96% rename from test/unit-tests/org/orgUpdateTest.js rename to test/unit-tests/org/legacy/orgUpdateTest.js index c706d955d..86a089b26 100644 --- a/test/unit-tests/org/orgUpdateTest.js +++ b/test/unit-tests/org/legacy/orgUpdateTest.js @@ -9,17 +9,17 @@ chai.use(require('chai-http')) // Body Parser Middleware app.use(express.json()) // Allows us to handle raw JSON data app.use(express.urlencoded({ extended: false })) // Allows us to handle url encoded data -const middleware = require('../../../src/middleware/middleware') +const middleware = require('../../../../src/middleware/middleware') app.use(middleware.createCtxAndReqUUID) -const getConstants = require('../../../src/constants').getConstants -const errors = require('../../../src/controller/org.controller/error') +const getConstants = require('../../../../src/constants').getConstants +const errors = require('../../../../src/controller/org.controller/error') const error = new errors.OrgControllerError() -const orgFixtures = require('./mockObjects.org') -const orgController = require('../../../src/controller/org.controller/org.controller') -const orgParams = require('../../../src/controller/org.controller/org.middleware') -const logger = require('../../../src/middleware/logger') +const orgFixtures = require('../mockObjects.org') +const orgController = require('../../../../src/controller/org.controller/org.controller') +const orgParams = require('../../../../src/controller/org.controller/org.middleware') +const logger = require('../../../../src/middleware/logger') class NullUserRepo { async getUserUUID () { diff --git a/test/unit-tests/org/orgCompatibilityTest.js b/test/unit-tests/org/orgCompatibilityTest.js new file mode 100644 index 000000000..d5bf9b9bd --- /dev/null +++ b/test/unit-tests/org/orgCompatibilityTest.js @@ -0,0 +1,69 @@ +const { expect } = require('chai') + +const { + toLegacyOrg, + toBaseOrgInput, + toBaseOrgUpdate +} = require('../../../src/utils/orgCompatibility') + +describe('Organization compatibility conversions', () => { + const created = new Date('2026-08-17T12:00:00.000Z') + const modified = new Date('2026-08-18T12:00:00.000Z') + + it('converts BaseOrg fields to their legacy API equivalents', () => { + const result = toLegacyOrg({ + UUID: 'org-uuid', + short_name: 'example', + long_name: 'Example Organization', + authority: ['CNA', 'ADP'], + id_quota: 100, + created, + last_updated: modified, + in_use: true + }) + + expect(result).to.deep.equal({ + name: 'Example Organization', + short_name: 'example', + UUID: 'org-uuid', + authority: { active_roles: ['CNA', 'ADP'] }, + policies: { id_quota: 100 }, + time: { created, modified } + }) + expect(result).to.not.have.property('inUse') + expect(result).to.not.have.property('in_use') + }) + + it('converts only supported legacy input fields to BaseOrg fields', () => { + const result = toBaseOrgInput({ + name: 'Example Organization', + short_name: 'example', + UUID: 'org-uuid', + authority: { active_roles: ['CNA'] }, + policies: { id_quota: 100 }, + time: { created, modified }, + inUse: true + }) + + expect(result).to.deep.equal({ + long_name: 'Example Organization', + short_name: 'example', + UUID: 'org-uuid', + authority: ['CNA'], + id_quota: 100, + created, + last_updated: modified + }) + expect(result).to.not.have.property('in_use') + }) + + it('maps a legacy short-name update to the BaseOrg field name', () => { + expect(toBaseOrgUpdate({ + new_short_name: 'renamed-example', + name: 'Renamed Example' + })).to.deep.equal({ + short_name: 'renamed-example', + long_name: 'Renamed Example' + }) + }) +}) diff --git a/test/unit-tests/org/registryOrgControllerTest.js b/test/unit-tests/org/registry/registryOrgControllerTest.js similarity index 98% rename from test/unit-tests/org/registryOrgControllerTest.js rename to test/unit-tests/org/registry/registryOrgControllerTest.js index f9430aceb..93f5cb9ee 100644 --- a/test/unit-tests/org/registryOrgControllerTest.js +++ b/test/unit-tests/org/registry/registryOrgControllerTest.js @@ -2,7 +2,7 @@ const { expect } = require('chai') const sinon = require('sinon') const mongoose = require('mongoose') -const controller = require('../../../src/controller/registry.controller/org.registry.controller') +const controller = require('../../../../src/controller/registry.controller/org.registry.controller') function mockResponse () { const res = {} diff --git a/test/unit-tests/org/registryOrgGetSingleTest.js b/test/unit-tests/org/registry/registryOrgGetSingleTest.js similarity index 99% rename from test/unit-tests/org/registryOrgGetSingleTest.js rename to test/unit-tests/org/registry/registryOrgGetSingleTest.js index 0651eaa2b..d912ed683 100644 --- a/test/unit-tests/org/registryOrgGetSingleTest.js +++ b/test/unit-tests/org/registry/registryOrgGetSingleTest.js @@ -3,7 +3,7 @@ const sinon = require('sinon') const chai = require('chai') const expect = chai.expect -const { SINGLE_ORG } = require('../../../src/controller/registry.controller/org.registry.controller') +const { SINGLE_ORG } = require('../../../../src/controller/registry.controller/org.registry.controller') describe('Testing the GET /registry/org/:identifier endpoint in Registry Org Controller', () => { let status, json, res, next, orgRepo, conversationRepo, userRepo, req, conversations diff --git a/test/unit-tests/registry-org/registryOrgPayloadTest.js b/test/unit-tests/org/registry/registryOrgPayloadTest.js similarity index 96% rename from test/unit-tests/registry-org/registryOrgPayloadTest.js rename to test/unit-tests/org/registry/registryOrgPayloadTest.js index 006b0b91c..2ee629685 100644 --- a/test/unit-tests/registry-org/registryOrgPayloadTest.js +++ b/test/unit-tests/org/registry/registryOrgPayloadTest.js @@ -3,8 +3,8 @@ const sinon = require('sinon') const mongoose = require('mongoose') const expect = chai.expect -const registryOrgController = require('../../../src/controller/registry.controller/org.registry.controller') -const logger = require('../../../src/middleware/logger') +const registryOrgController = require('../../../../src/controller/registry.controller/org.registry.controller') +const logger = require('../../../../src/middleware/logger') describe('Registry org payload logging', () => { let mockSession diff --git a/test/unit-tests/user/baseUserRepositoryFormatSemanticsTest.js b/test/unit-tests/user/baseUserRepositoryFormatSemanticsTest.js index 80c7bd7bd..295cc8e20 100644 --- a/test/unit-tests/user/baseUserRepositoryFormatSemanticsTest.js +++ b/test/unit-tests/user/baseUserRepositoryFormatSemanticsTest.js @@ -5,6 +5,9 @@ const sinon = require('sinon') const expect = chai.expect const BaseUserRepository = require('../../../src/repositories/baseUserRepository.js') +const BaseOrgRepository = require('../../../src/repositories/baseOrgRepository.js') +const BaseOrgModel = require('../../../src/model/baseorg.js') +const BaseUser = require('../../../src/model/baseuser.js') const UserRepository = require('../../../src/repositories/userRepository.js') const authContext = require('../../../src/utils/authContext.js') @@ -51,6 +54,86 @@ describe('Testing BaseUserRepository format flag semantics', () => { org_UUID: true }) }) + + it('Should source legacy organization user lists from BaseUser', async () => { + sinon.stub(BaseOrgRepository.prototype, 'findOneByShortName').resolves({ + UUID: 'org-uuid', + users: ['user-uuid'], + admins: ['user-uuid'], + toObject () { + return { + UUID: this.UUID, + users: this.users, + admins: this.admins + } + } + }) + registryAggregate = sinon.stub(repo, 'aggregatePaginate').resolves({ + itemsList: [{ + UUID: 'user-uuid', + username: 'admin@example.org', + status: 'active', + created: '2026-01-01T00:00:00.000Z', + last_updated: '2026-01-02T00:00:00.000Z' + }], + itemCount: 0 + }) + legacyAggregate = sinon.stub(UserRepository.prototype, 'aggregatePaginate').rejects(new Error('Legacy aggregate should not be called')) + + const result = await repo.getLegacyUsersByOrgShortname('example-org', { limit: 100 }) + + expect(legacyAggregate.called).to.be.false + expect(registryAggregate.calledOnce).to.be.true + expect(registryAggregate.firstCall.args[0][0]).to.deep.equal({ + $match: { + UUID: { $in: ['user-uuid'] } + } + }) + expect(result).to.deep.equal({ + users: [{ + UUID: 'user-uuid', + username: 'admin@example.org', + authority: { active_roles: ['ADMIN'] }, + active: true, + time: { + created: '2026-01-01T00:00:00.000Z', + modified: '2026-01-02T00:00:00.000Z' + }, + org_UUID: 'org-uuid' + }] + }) + }) + + it('Should source a legacy single-user response from BaseUser', async () => { + sinon.stub(BaseOrgModel, 'findOne').resolves({ + UUID: 'org-uuid', + users: ['user-uuid'], + admins: [] + }) + sinon.stub(BaseUser, 'findOne').resolves({ + UUID: 'user-uuid', + username: 'user@example.org', + status: 'inactive', + created: '2026-01-01T00:00:00.000Z', + last_updated: '2026-01-02T00:00:00.000Z' + }) + legacyAggregate = sinon.stub(UserRepository.prototype, 'findOneByUUID').rejects(new Error('Legacy user lookup should not be called')) + + const result = await repo.findLegacyUserByUsernameAndOrgShortname('user@example.org', 'example-org') + + expect(legacyAggregate.called).to.be.false + expect(result).to.deep.equal({ + UUID: 'user-uuid', + username: 'user@example.org', + authority: { active_roles: [] }, + active: false, + time: { + created: '2026-01-01T00:00:00.000Z', + modified: '2026-01-02T00:00:00.000Z' + }, + org_UUID: 'org-uuid' + }) + }) }) context('Auth Context Helper Tests', () => { diff --git a/test/unit-tests/user/userCompatibilityTest.js b/test/unit-tests/user/userCompatibilityTest.js new file mode 100644 index 000000000..395de68a8 --- /dev/null +++ b/test/unit-tests/user/userCompatibilityTest.js @@ -0,0 +1,58 @@ +/* eslint-disable no-unused-expressions */ + +const chai = require('chai') +const { expect } = chai + +const { toLegacyUser } = require('../../../src/utils/userCompatibility.js') + +describe('User compatibility conversions', () => { + it('converts a BaseUser to the legacy user response shape', () => { + const baseUser = { + UUID: 'user-uuid', + username: 'admin@example.org', + status: 'active', + name: { + first: 'Ada', + last: 'Lovelace' + }, + created: '2026-01-01T00:00:00.000Z', + last_updated: '2026-01-02T00:00:00.000Z' + } + const baseOrg = { + UUID: 'org-uuid', + admins: ['user-uuid'] + } + + expect(toLegacyUser(baseUser, baseOrg)).to.deep.equal({ + UUID: 'user-uuid', + username: 'admin@example.org', + authority: { + active_roles: ['ADMIN'] + }, + active: true, + time: { + created: '2026-01-01T00:00:00.000Z', + modified: '2026-01-02T00:00:00.000Z' + }, + name: { + first: 'Ada', + last: 'Lovelace' + }, + org_UUID: 'org-uuid' + }) + }) + + it('returns an inactive non-admin user with no active roles', () => { + const legacyUser = toLegacyUser({ + UUID: 'user-uuid', + username: 'user@example.org', + status: 'inactive' + }, { + UUID: 'org-uuid', + admins: [] + }) + + expect(legacyUser.active).to.equal(false) + expect(legacyUser.authority.active_roles).to.deep.equal([]) + }) +}) diff --git a/test/unit-tests/user/userGetAllTest.js b/test/unit-tests/user/userGetAllTest.js index 6c29b16fb..470dcb18d 100644 --- a/test/unit-tests/user/userGetAllTest.js +++ b/test/unit-tests/user/userGetAllTest.js @@ -127,7 +127,7 @@ describe('Testing the GET /org/:shortname/users endpoint in Org Controller', () } class GetAllUsersByOrgShortname { - async getAllUsersByOrgShortname () { + async getLegacyUsersByOrgShortname () { return { users: [orgFixtures.existentUserDummy] } @@ -172,7 +172,7 @@ describe('Testing the GET /org/:shortname/users endpoint in Org Controller', () } class GetAllUsersByOrgShortname { - async getAllUsersByOrgShortname () { + async getLegacyUsersByOrgShortname () { return { users: [orgFixtures.existentUserDummy] } @@ -217,7 +217,7 @@ describe('Testing the GET /org/:shortname/users endpoint in Org Controller', () } class GetAllUsersByOrgShortname { - async getAllUsersByOrgShortname () { + async getLegacyUsersByOrgShortname () { const payload = { users: orgFixtures.allOwningOrgUsers } return payload } @@ -264,7 +264,7 @@ describe('Testing the GET /org/:shortname/users endpoint in Org Controller', () } class GetAllUsersByOrgShortname { - async getAllUsersByOrgShortname () { + async getLegacyUsersByOrgShortname () { const payload = { users: [orgFixtures.allOwningOrgUsers[0], orgFixtures.allOwningOrgUsers[1], orgFixtures.allOwningOrgUsers[2]] } payload.totalCount = orgFixtures.allOwningOrgUsers.length payload.itemsPerPage = 3 @@ -321,7 +321,7 @@ describe('Testing the GET /org/:shortname/users endpoint in Org Controller', () } class GetAllUsersByOrgShortname { - async getAllUsersByOrgShortname () { + async getLegacyUsersByOrgShortname () { const payload = { users: [orgFixtures.allOwningOrgUsers[3], orgFixtures.allOwningOrgUsers[4]] } payload.totalCount = orgFixtures.allOwningOrgUsers.length payload.itemsPerPage = 3 @@ -378,7 +378,7 @@ describe('Testing the GET /org/:shortname/users endpoint in Org Controller', () } class GetAllUsersByOrgShortname { - async getAllUsersByOrgShortname () { + async getLegacyUsersByOrgShortname () { const payload = { users: [] } return payload } diff --git a/test/unit-tests/user/userGetSingleTest.js b/test/unit-tests/user/userGetSingleTest.js index 88a289dc8..e2438fe9c 100644 --- a/test/unit-tests/user/userGetSingleTest.js +++ b/test/unit-tests/user/userGetSingleTest.js @@ -46,7 +46,7 @@ describe('Testing the GET /org/:shortname/user/:username endpoint in Org Control it('Org does not exist', async () => { sinon.stub(baseOrgRepo, 'isSecretariatByShortName').resolves(true) sinon.stub(baseOrgRepo, 'getOrgUUID').resolves(null) - sinon.stub(baseUserRepo, 'findOneByUsernameAndOrgShortname').resolves(null) + sinon.stub(baseUserRepo, 'findLegacyUserByUsernameAndOrgShortname').resolves(null) const req = { ctx: { @@ -74,7 +74,7 @@ describe('Testing the GET /org/:shortname/user/:username endpoint in Org Control it('User does not exist', async () => { sinon.stub(baseOrgRepo, 'isSecretariatByShortName').resolves(true) sinon.stub(baseOrgRepo, 'getOrgUUID').resolves(userFixtures.existentOrg.UUID) - sinon.stub(baseUserRepo, 'findOneByUsernameAndOrgShortname').resolves(null) + sinon.stub(baseUserRepo, 'findLegacyUserByUsernameAndOrgShortname').resolves(null) const req = { ctx: { @@ -142,7 +142,7 @@ describe('Testing the GET /org/:shortname/user/:username endpoint in Org Control secret: 'test-secret-hash' }) } - sinon.stub(baseUserRepo, 'findOneByUsernameAndOrgShortname').resolves(mockUserDoc) + sinon.stub(baseUserRepo, 'findLegacyUserByUsernameAndOrgShortname').resolves(mockUserDoc) const req = { ctx: { @@ -184,7 +184,7 @@ describe('Testing the GET /org/:shortname/user/:username endpoint in Org Control secret: 'test-secret-hash' }) } - sinon.stub(baseUserRepo, 'findOneByUsernameAndOrgShortname').resolves(mockUserDoc) + sinon.stub(baseUserRepo, 'findLegacyUserByUsernameAndOrgShortname').resolves(mockUserDoc) const req = { ctx: { From a6cedb73386288c3cd55dc0e628aa58ffd2586c2 Mon Sep 17 00:00:00 2001 From: James Dalphond Date: Tue, 18 Aug 2026 09:26:44 -0400 Subject: [PATCH 03/20] Making test output readable --- src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index fa8ddf843..666291a04 100644 --- a/src/index.js +++ b/src/index.js @@ -25,7 +25,7 @@ if (process.env.NODE_ENV === 'development') { }) next() }) -} else { +} else if (process.env.NODE_ENV !== 'test') { // Standard Apache common log output. app.use(morgan('combined')) } From ffbb5d9057adc2b632159042de88ab9f94e7b359 Mon Sep 17 00:00:00 2001 From: James Dalphond Date: Tue, 18 Aug 2026 10:30:22 -0400 Subject: [PATCH 04/20] Moving user reads to BaseUser but projected as the legacy format --- .../user.registry.controller.js | 2 +- .../user.controller/user.controller.js | 3 +- src/repositories/baseOrgRepository.js | 2 +- src/repositories/baseUserRepository.js | 80 ++++++++++--------- test/integration-tests/user/getUsersTest.js | 1 + .../baseUserRepositoryFormatSemanticsTest.js | 60 +++++++++++--- 6 files changed, 95 insertions(+), 53 deletions(-) diff --git a/src/controller/registry.controller/user.registry.controller.js b/src/controller/registry.controller/user.registry.controller.js index 8cb5d393b..77f0752e7 100644 --- a/src/controller/registry.controller/user.registry.controller.js +++ b/src/controller/registry.controller/user.registry.controller.js @@ -44,7 +44,7 @@ async function getAllUsers (req, res, next) { options.sort = { short_name: 'asc' } options.page = req.ctx.query.page ? parseInt(req.ctx.query.page) : CONSTANTS.PAGINATOR_PAGE // if 'page' query parameter is not defined, set 'page' to the default page value - const returnValue = await repo.getAllUsers(options, REGISTRY_FORMAT) + const returnValue = await repo.getAllUsers(options) // Hydrate roles const orgRepo = req.ctx.repositories.getBaseOrgRepository() const distinctOrgUUIDs = [...new Set(returnValue.users.map(u => u.org_UUID))] diff --git a/src/controller/user.controller/user.controller.js b/src/controller/user.controller/user.controller.js index a5a8f1a4a..e41d22a0d 100644 --- a/src/controller/user.controller/user.controller.js +++ b/src/controller/user.controller/user.controller.js @@ -2,7 +2,6 @@ require('dotenv').config() const logger = require('../../middleware/logger') const getConstants = require('../../constants').getConstants -const { LEGACY_FORMAT, REGISTRY_FORMAT } = require('../format.constants') /** * Get the details of all users @@ -23,7 +22,7 @@ async function getAllUsers (req, res, next) { options.sort = { username: 'asc' } options.page = req.ctx.query.page ? parseInt(req.ctx.query.page) : CONSTANTS.PAGINATOR_PAGE // if 'page' query parameter is not defined, set 'page' to the default page value - const returnValue = await repo.getAllUsers(options, req.useRegistry ? REGISTRY_FORMAT : LEGACY_FORMAT) + const returnValue = await repo.getLegacyUsers(options) logger.info({ uuid: req.ctx.uuid, message: 'The user information was sent to the secretariat user.' }) return res.status(200).json(returnValue) diff --git a/src/repositories/baseOrgRepository.js b/src/repositories/baseOrgRepository.js index 6a38ce680..4eb937c3e 100644 --- a/src/repositories/baseOrgRepository.js +++ b/src/repositories/baseOrgRepository.js @@ -304,7 +304,7 @@ class BaseOrgRepository extends BaseRepository { return await BaseOrgModel.find( { users: { $in: userUUIDs } }, - { _id: 0, UUID: 1, short_name: 1, users: 1 }, + { _id: 0, UUID: 1, short_name: 1, users: 1, admins: 1 }, options ) } diff --git a/src/repositories/baseUserRepository.js b/src/repositories/baseUserRepository.js index c5f281dc4..31ce80110 100644 --- a/src/repositories/baseUserRepository.js +++ b/src/repositories/baseUserRepository.js @@ -27,31 +27,6 @@ const userHasAdminRole = (user) => { return roles.some(role => String(role).toUpperCase() === 'ADMIN') } -/** - * @function setAggregateUserObj - * @description Constructs the aggregation pipeline for legacy user objects. - * @param {object} query - The query object to match. - * @returns {Array} The aggregation pipeline. - */ -function setAggregateUserObj (query) { - return [ - { - $match: query - }, - { - $project: { - _id: false, - username: true, - name: true, - UUID: true, - org_UUID: true, - active: true, - 'authority.active_roles': true, - time: true - } - } - ] -} /** * @function setAggregateRegistryUserObj * @description Constructs the aggregation pipeline for registry user objects. @@ -394,20 +369,11 @@ class BaseUserRepository extends BaseRepository { * @function getAllUsers * @description Retrieves all users with pagination. * @param {object} [options={}] - Pagination and query options. - * @param {boolean} [isLegacyObject=false] - If true, returns legacy formatted users. * @returns {Promise} Paginated result containing users and metadata. */ - async getAllUsers (options = {}, isLegacyObject = false) { - const UserRepository = require('./userRepository') - const userRepo = new UserRepository() - let pg - if (isLegacyObject) { - const agt = setAggregateUserObj({}) - pg = await userRepo.aggregatePaginate(agt, options) - } else { - const agt = setAggregateRegistryUserObj({}) - pg = await this.aggregatePaginate(agt, options) - } + async getAllUsers (options = {}) { + const agt = setAggregateRegistryUserObj({}) + const pg = await this.aggregatePaginate(agt, options) const data = { users: pg.itemsList } if (pg.itemCount >= options.limit) { data.totalCount = pg.itemCount @@ -420,6 +386,46 @@ class BaseUserRepository extends BaseRepository { return data } + /** + * Retrieves all users from BaseUser and converts them to the legacy API + * representation. The owning BaseOrg supplies legacy org UUID and ADMIN + * role information that BaseUser does not store. + * + * @param {object} [options={}] - Pagination options. + * @returns {Promise} Paginated legacy user response. + */ + async getLegacyUsers (options = {}) { + const agt = setAggregateRegistryUserObj({}) + const pg = await this.aggregatePaginate(agt, options) + const userUUIDs = pg.itemsList.map(user => user.UUID) + const userUUIDSet = new Set(userUUIDs) + const baseOrgRepository = new BaseOrgRepository() + const owningOrgs = await baseOrgRepository.findOrgsByUserUUIDs(userUUIDs) + const owningOrgByUserUUID = new Map() + + owningOrgs.forEach(org => { + const orgObject = toPlainObject(org) + orgObject.users?.forEach(userUUID => { + if (userUUIDSet.has(userUUID)) { + owningOrgByUserUUID.set(userUUID, orgObject) + } + }) + }) + + const data = { + users: pg.itemsList.map(user => toLegacyUser(user, owningOrgByUserUUID.get(user.UUID))) + } + if (pg.itemCount >= options.limit) { + data.totalCount = pg.itemCount + data.itemsPerPage = pg.itemsPerPage + data.pageCount = pg.pageCount + data.currentPage = pg.currentPage + data.prevPage = pg.prevPage + data.nextPage = pg.nextPage + } + return data + } + /** * @async * @function createUser diff --git a/test/integration-tests/user/getUsersTest.js b/test/integration-tests/user/getUsersTest.js index ff140f85e..7c412cd19 100644 --- a/test/integration-tests/user/getUsersTest.js +++ b/test/integration-tests/user/getUsersTest.js @@ -30,6 +30,7 @@ describe('Testing global user list endpoints', () => { expect(res).to.have.status(200) expect(res.body).to.have.property('users') expect(res.body.users).to.be.an('array').that.is.not.empty + expect(res.body.users[0]).to.include.keys('UUID', 'username', 'authority', 'active', 'time', 'org_UUID') }) }) diff --git a/test/unit-tests/user/baseUserRepositoryFormatSemanticsTest.js b/test/unit-tests/user/baseUserRepositoryFormatSemanticsTest.js index 295cc8e20..fda7e8d28 100644 --- a/test/unit-tests/user/baseUserRepositoryFormatSemanticsTest.js +++ b/test/unit-tests/user/baseUserRepositoryFormatSemanticsTest.js @@ -38,20 +38,56 @@ describe('Testing BaseUserRepository format flag semantics', () => { }) }) - it('Should return user list responses in legacy format when isLegacyObject is true', async () => { - registryAggregate = sinon.stub(repo, 'aggregatePaginate').rejects(new Error('Registry aggregate should not be called')) - legacyAggregate = sinon.stub(UserRepository.prototype, 'aggregatePaginate').resolves({ itemsList: [{ UUID: 'user-uuid' }], itemCount: 0 }) + it('Should source legacy global user lists from BaseUser', async () => { + registryAggregate = sinon.stub(repo, 'aggregatePaginate').resolves({ + itemsList: [{ + UUID: 'admin-user-uuid', + username: 'admin@example.org', + status: 'active', + created: '2026-01-01T00:00:00.000Z', + last_updated: '2026-01-02T00:00:00.000Z' + }, { + UUID: 'regular-user-uuid', + username: 'regular@example.org', + status: 'inactive', + created: '2026-01-03T00:00:00.000Z', + last_updated: '2026-01-04T00:00:00.000Z' + }], + itemCount: 0 + }) + const findOrgsByUserUUIDs = sinon.stub(BaseOrgRepository.prototype, 'findOrgsByUserUUIDs').resolves([{ + UUID: 'org-uuid', + users: ['admin-user-uuid', 'regular-user-uuid'], + admins: ['admin-user-uuid'] + }]) + legacyAggregate = sinon.stub(UserRepository.prototype, 'aggregatePaginate').rejects(new Error('Legacy aggregate should not be called')) - const result = await repo.getAllUsers({ limit: 100 }, true) + const result = await repo.getLegacyUsers({ limit: 100 }) - expect(result).to.deep.equal({ users: [{ UUID: 'user-uuid' }] }) - expect(registryAggregate.called).to.be.false - expect(legacyAggregate.calledOnce).to.be.true - expect(legacyAggregate.firstCall.args[0][1].$project).to.include({ - _id: false, - username: true, - UUID: true, - org_UUID: true + expect(legacyAggregate.called).to.be.false + expect(findOrgsByUserUUIDs.calledOnceWith(['admin-user-uuid', 'regular-user-uuid'])).to.be.true + expect(result).to.deep.equal({ + users: [{ + UUID: 'admin-user-uuid', + username: 'admin@example.org', + authority: { active_roles: ['ADMIN'] }, + active: true, + time: { + created: '2026-01-01T00:00:00.000Z', + modified: '2026-01-02T00:00:00.000Z' + }, + org_UUID: 'org-uuid' + }, { + UUID: 'regular-user-uuid', + username: 'regular@example.org', + authority: { active_roles: [] }, + active: false, + time: { + created: '2026-01-03T00:00:00.000Z', + modified: '2026-01-04T00:00:00.000Z' + }, + org_UUID: 'org-uuid' + }] }) }) From 9f3b4b3897b9cbd7127bcaef9adfd88a5a6a040d Mon Sep 17 00:00:00 2001 From: James Dalphond Date: Tue, 18 Aug 2026 11:14:34 -0400 Subject: [PATCH 05/20] Move CVE-ID reads to BaseOrg and BaseUser --- .../cve-id.controller/cve-id.controller.js | 88 ++++++++++++------- src/repositories/baseOrgRepository.js | 20 +++++ test/unit-tests/cve-id/cveIdGetAllTest.js | 65 +++++++++----- test/unit-tests/cve-id/cveIdGetSingleTest.js | 54 +++--------- 4 files changed, 129 insertions(+), 98 deletions(-) diff --git a/src/controller/cve-id.controller/cve-id.controller.js b/src/controller/cve-id.controller/cve-id.controller.js index e33b86411..d305a7363 100644 --- a/src/controller/cve-id.controller/cve-id.controller.js +++ b/src/controller/cve-id.controller/cve-id.controller.js @@ -32,31 +32,11 @@ async function getFilteredCveId (req, res, next) { } options.page = req.ctx.query.page ? parseInt(req.ctx.query.page) : CONSTANTS.PAGINATOR_PAGE // if 'page' query parameter is not defined, set 'page' to the default page value const cveIdRepo = req.ctx.repositories.getCveIdRepository() - const orgRepo = req.ctx.repositories.getOrgRepository() - const userRepo = req.ctx.repositories.getUserRepository() - const isSecretariat = await authContext.isRequesterSecretariat(req, orgRepo) - const isBulkDownload = await authContext.isRequesterBulkDownload(req, orgRepo) - const requesterOrgUUID = await authContext.getRequesterOrgUUID(req, orgRepo) - - // Create map of orgUUID to shortnames and users to simplify aggregation later - // Only project the fields needed for the maps to avoid fetching full documents - const orgs = await orgRepo.getAllOrgs({}, { UUID: 1, short_name: 1, _id: 0 }) - const users = await userRepo.getAllUsers({}, { UUID: 1, username: 1, org_UUID: 1, _id: 0 }) - - const orgMap = {} - const userMap = {} - - orgs.forEach(org => { - orgMap[org.UUID] = { shortname: org.short_name, users: {} } - }) - - users.forEach(user => { - userMap[user.UUID] = user.username - if (!orgMap[user.org_UUID]) { - orgMap[user.org_UUID] = { shortname: `MISSING ORG ${user.org_UUID}`, users: {} } - } - orgMap[user.org_UUID].users[user.UUID] = user.username - }) + const baseOrgRepo = req.ctx.repositories.getBaseOrgRepository() + const baseUserRepo = req.ctx.repositories.getBaseUserRepository() + const isSecretariat = await authContext.isRequesterSecretariat(req, baseOrgRepo) + const isBulkDownload = await authContext.isRequesterBulkDownload(req, baseOrgRepo) + const requesterOrgUUID = await authContext.getRequesterOrgUUID(req, baseOrgRepo) Object.keys(req.ctx.query).forEach(k => { const key = k.toLowerCase() @@ -123,6 +103,39 @@ async function getFilteredCveId (req, res, next) { options.readPreference = 'primary' const agt = setMinAggregateObj(query) const pg = await cveIdRepo.aggregatePaginate(agt, options) + const orgUUIDs = new Set() + const userUUIDs = new Set() + + pg.itemsList.forEach(cveId => { + orgUUIDs.add(cveId.requested_by.cna) + orgUUIDs.add(cveId.owning_cna) + userUUIDs.add(cveId.requested_by.user) + }) + + // Fetch only the BaseOrg and BaseUser records needed for this page. + // BaseOrg.users is the source of truth for user membership. + const [orgs, users] = await Promise.all([ + orgUUIDs.size > 0 ? baseOrgRepo.findOrgsByUUIDs(Array.from(orgUUIDs)) : [], + userUUIDs.size > 0 ? baseUserRepo.findUsersByUUIDs(Array.from(userUUIDs)) : [] + ]) + const orgMap = {} + const userMap = {} + + users.forEach(user => { + userMap[user.UUID] = user.username + }) + + orgs.forEach(org => { + const orgUsers = {} + const orgUserUUIDs = Array.isArray(org.users) ? org.users : [] + orgUserUUIDs.forEach(userUUID => { + if (Object.hasOwn(userMap, userUUID)) { + orgUsers[userUUID] = userMap[userUUID] + } + }) + orgMap[org.UUID] = { shortname: org.short_name, users: orgUsers } + }) + const payload = { cve_ids: pg.itemsList.map((i) => { const cnaid = i.requested_by.cna @@ -283,10 +296,10 @@ async function getCveId (req, res, next) { const auth = req.ctx.authenticated const id = req.ctx.params.id const cveIdRepo = req.ctx.repositories.getCveIdRepository() - const orgRepo = req.ctx.repositories.getOrgRepository() + const baseOrgRepo = req.ctx.repositories.getBaseOrgRepository() const rawResult = typeof cveIdRepo.findOneByCveId === 'function' ? await cveIdRepo.findOneByCveId(id) : null - const agt = setAggregateObj({ cve_id: id }) + const agt = setBaseAggregateObj({ cve_id: id }) let result = await cveIdRepo.aggregate(agt) result = result.length > 0 ? result[0] : null @@ -303,8 +316,8 @@ async function getCveId (req, res, next) { if (auth) { loggerUuid = req.ctx.uuid orgShortName = req.ctx.org - orgUUID = await authContext.getRequesterOrgUUID(req, orgRepo) // orgShortName is not null - isSecretariat = await authContext.isRequesterSecretariat(req, orgRepo) + orgUUID = await authContext.getRequesterOrgUUID(req, baseOrgRepo) // orgShortName is not null + isSecretariat = await authContext.isRequesterSecretariat(req, baseOrgRepo) } // Secretariat and owning org are allowed to see complete results @@ -913,14 +926,14 @@ function getRandomInt (min, max) { return Math.floor(Math.random() * (max - min) + min) // The maximum is exclusive and the minimum is inclusive } -function setAggregateObj (query) { +function setAggregateObj (query, { orgCollection = 'Org', userCollection = 'User' } = {}) { return [ { $match: query }, { $lookup: { - from: 'Org', + from: orgCollection, localField: 'owning_cna', foreignField: 'UUID', as: 'ownerCna' @@ -943,7 +956,7 @@ function setAggregateObj (query) { }, { $lookup: { - from: 'User', + from: userCollection, localField: 'requested_by.user', foreignField: 'UUID', as: 'result' @@ -966,7 +979,7 @@ function setAggregateObj (query) { }, { $lookup: { - from: 'Org', + from: orgCollection, localField: 'requested_by.cna', foreignField: 'UUID', as: 'result' @@ -990,6 +1003,15 @@ function setAggregateObj (query) { ] } +// GET /cve-id/:id reads organization and user details from the registry +// collections. Keep setAggregateObj for write responses until their migration. +function setBaseAggregateObj (query) { + return setAggregateObj(query, { + orgCollection: 'BaseOrg', + userCollection: 'BaseUser' + }) +} + // Smaller aggregation used in getFilteredCveId function function setMinAggregateObj (query) { return [ diff --git a/src/repositories/baseOrgRepository.js b/src/repositories/baseOrgRepository.js index 4eb937c3e..679dddc6a 100644 --- a/src/repositories/baseOrgRepository.js +++ b/src/repositories/baseOrgRepository.js @@ -309,6 +309,26 @@ class BaseOrgRepository extends BaseRepository { ) } + /** + * Finds organizations by UUID with the fields needed to associate users + * and organization short names in read response mappings. + * + * @param {string[]} orgUUIDs - Organization UUIDs to retrieve. + * @param {object} [options={}] - Optional settings for the repository query. + * @returns {Promise} The matching organizations. + */ + async findOrgsByUUIDs (orgUUIDs, options = {}) { + if (!Array.isArray(orgUUIDs) || orgUUIDs.length === 0) { + return [] + } + + return await BaseOrgModel.find( + { UUID: { $in: orgUUIDs } }, + { _id: 0, UUID: 1, short_name: 1, users: 1 }, + options + ) + } + /** * @function hasRole * @description Checks if an organization object has the requested role. diff --git a/test/unit-tests/cve-id/cveIdGetAllTest.js b/test/unit-tests/cve-id/cveIdGetAllTest.js index 2946f871c..528973f96 100644 --- a/test/unit-tests/cve-id/cveIdGetAllTest.js +++ b/test/unit-tests/cve-id/cveIdGetAllTest.js @@ -5,9 +5,9 @@ const expect = chai.expect const _ = require('lodash') const cveIdController = require('../../../src/controller/cve-id.controller/cve-id.controller.js') -const OrgRepository = require('../../../src/repositories/orgRepository.js') +const BaseOrgRepository = require('../../../src/repositories/baseOrgRepository.js') +const BaseUserRepository = require('../../../src/repositories/baseUserRepository.js') const CveIdRepository = require('../../../src/repositories/cveIdRepository.js') -const UserRepository = require('../../../src/repositories/userRepository.js') const orgUUID = faker.datatype.uuid() const orgUUID2 = faker.datatype.uuid() @@ -17,6 +17,7 @@ const stubOrg = { short_name: 'testOrg', name: 'test_org', UUID: orgUUID, + users: [userUUID], authority: { active_roles: [ 'CNA', @@ -29,6 +30,7 @@ const stubOrg2 = { short_name: 'testOrg2', name: 'test_org2', UUID: orgUUID2, + users: [], authority: { active_roles: [ 'CNA' @@ -63,9 +65,9 @@ const builtQuery = { } } describe('Testing getFilteredCveId function', () => { - let sandbox, status, json, res, next, getOrgRepository, - orgRepo, getCveIdRepository, cveIdRepo, - getUserRepository, userRepo, req, cveIdCopy + let sandbox, status, json, res, next, getBaseOrgRepository, baseOrgRepo, + getBaseUserRepository, baseUserRepo, getCveIdRepository, cveIdRepo, + req, cveIdCopy // Stub out functions called in insertAdp and reset them for each test beforeEach(() => { @@ -90,13 +92,13 @@ describe('Testing getFilteredCveId function', () => { nextPage: null } - orgRepo = new OrgRepository() - getOrgRepository = sandbox.stub() - getOrgRepository.returns(orgRepo) + baseOrgRepo = new BaseOrgRepository() + getBaseOrgRepository = sandbox.stub() + getBaseOrgRepository.returns(baseOrgRepo) - userRepo = new UserRepository() - getUserRepository = sandbox.stub() - getUserRepository.returns(userRepo) + baseUserRepo = new BaseUserRepository() + getBaseUserRepository = sandbox.stub() + getBaseUserRepository.returns(baseUserRepo) cveIdRepo = new CveIdRepository() getCveIdRepository = sandbox.stub() @@ -105,13 +107,12 @@ describe('Testing getFilteredCveId function', () => { sandbox.stub(cveIdRepo, 'findOneByCveId').returns(cveIdCopy) sandbox.stub(cveIdRepo, 'aggregatePaginate').returns(aggPagResp) - sandbox.stub(orgRepo, 'getOrgUUID').returns(stubOrg.UUID) - sandbox.stub(orgRepo, 'isSecretariat').returns(true) - sandbox.stub(orgRepo, 'isBulkDownload').returns(false) - sandbox.stub(orgRepo, 'getAllOrgs').returns([stubOrg, stubOrg2]) + sandbox.stub(baseOrgRepo, 'getOrgUUID').returns(stubOrg.UUID) + sandbox.stub(baseOrgRepo, 'isSecretariatByShortName').returns(true) + sandbox.stub(baseOrgRepo, 'isBulkDownloadByShortname').returns(false) + sandbox.stub(baseOrgRepo, 'findOrgsByUUIDs').returns([stubOrg, stubOrg2]) - sandbox.stub(userRepo, 'getUserUUID').returns(stubUser.UUID) - sandbox.stub(userRepo, 'getAllUsers').returns([stubUser]) + sandbox.stub(baseUserRepo, 'findUsersByUUIDs').returns([stubUser]) sandbox.spy(cveIdController, 'CVEID_GET_FILTER') @@ -126,8 +127,8 @@ describe('Testing getFilteredCveId function', () => { state: 'RESERVED' }, repositories: { - getOrgRepository, - getUserRepository, + getBaseOrgRepository, + getBaseUserRepository, getCveIdRepository } } @@ -143,10 +144,30 @@ describe('Testing getFilteredCveId function', () => { expect(cveIdRepo.aggregatePaginate.args[0][0][0].$match).to.deep.equal(builtQuery) }) - it('Should request only the fields needed to build the org and user maps', async () => { + it('Should use BaseOrg for requester authorization', async () => { await cveIdController.CVEID_GET_FILTER(req, res, next) - expect(orgRepo.getAllOrgs.calledOnceWith({}, { UUID: 1, short_name: 1, _id: 0 })).to.equal(true) - expect(userRepo.getAllUsers.calledOnceWith({}, { UUID: 1, username: 1, org_UUID: 1, _id: 0 })).to.equal(true) + + expect(getBaseOrgRepository.calledOnce).to.equal(true) + expect(baseOrgRepo.isSecretariatByShortName.calledWith(stubOrg.short_name)).to.equal(true) + expect(baseOrgRepo.isBulkDownloadByShortname.calledWith(stubOrg.short_name)).to.equal(true) + expect(baseOrgRepo.getOrgUUID.calledWith(stubOrg.short_name)).to.equal(true) + }) + + it('Should look up only the page\'s related BaseOrg and BaseUser records', async () => { + await cveIdController.CVEID_GET_FILTER(req, res, next) + expect(baseOrgRepo.findOrgsByUUIDs.calledOnceWith([orgUUID, orgUUID2])).to.equal(true) + expect(baseUserRepo.findUsersByUUIDs.calledOnceWith([userUUID])).to.equal(true) + }) + + it('Should not look up BaseOrg or BaseUser records for an empty page', async () => { + cveIdRepo.aggregatePaginate.returns({ itemsList: [], itemCount: 0 }) + + await cveIdController.CVEID_GET_FILTER(req, res, next) + + expect(baseOrgRepo.findOrgsByUUIDs.called).to.equal(false) + expect(baseUserRepo.findUsersByUUIDs.called).to.equal(false) + expect(res.status.calledWith(200)).to.equal(true) + expect(res.json.calledWith({ cve_ids: [] })).to.equal(true) }) it('Should swap UUIDs for names in Cve-ids', async () => { diff --git a/test/unit-tests/cve-id/cveIdGetSingleTest.js b/test/unit-tests/cve-id/cveIdGetSingleTest.js index 3453bff7d..91913c481 100644 --- a/test/unit-tests/cve-id/cveIdGetSingleTest.js +++ b/test/unit-tests/cve-id/cveIdGetSingleTest.js @@ -18,20 +18,6 @@ const cveIdFixtures = require('./mockObjects.cve-id') const cveIdController = require('../../../src/controller/cve-id.controller/cve-id.controller') const cveIdParams = require('../../../src/controller/cve-id.controller/cve-id.middleware') -class NullUserRepo { - async getUserUUID () { - return null - } - - async findOneByUserNameAndOrgUUID () { - return null - } - - async isAdmin () { - return null - } -} - class NullOrgRepo { async findOneByShortName () { return null @@ -58,7 +44,11 @@ describe('Testing the GET /cve-id/:id endpoint in CveId Controller', () => { context('Negative Tests', () => { it('Cve does not exist', (done) => { class CveIdGetCveIdDoesntExist { - async aggregate () { + async aggregate (pipeline) { + const lookupSources = pipeline + .filter(stage => stage.$lookup) + .map(stage => stage.$lookup.from) + expect(lookupSources).to.deep.equal(['BaseOrg', 'BaseUser', 'BaseOrg']) return [] } } @@ -67,8 +57,7 @@ describe('Testing the GET /cve-id/:id endpoint in CveId Controller', () => { .get((req, res, next) => { const factory = { getCveIdRepository: () => { return new CveIdGetCveIdDoesntExist() }, - getUserRepository: () => { return new NullUserRepo() }, - getOrgRepository: () => { return new NullOrgRepo() } + getBaseOrgRepository: () => { return new NullOrgRepo() } } req.ctx.repositories = factory next() @@ -102,8 +91,7 @@ describe('Testing the GET /cve-id/:id endpoint in CveId Controller', () => { .get((req, res, next) => { const factory = { getCveIdRepository: () => { return new CveIdGetCveIdAvailable() }, - getUserRepository: () => { return new NullUserRepo() }, - getOrgRepository: () => { return new NullOrgRepo() } + getBaseOrgRepository: () => { return new NullOrgRepo() } } req.ctx.repositories = factory next() @@ -147,8 +135,7 @@ describe('Testing the GET /cve-id/:id endpoint in CveId Controller', () => { .get((req, res, next) => { const factory = { getCveIdRepository: () => { return new CveIdGetCveIdNotOwningOrg() }, - getUserRepository: () => { return new NullUserRepo() }, - getOrgRepository: () => { return new OrgGetCveIdNotOwnerOrgReserved() } + getBaseOrgRepository: () => { return new OrgGetCveIdNotOwnerOrgReserved() } } req.ctx.repositories = factory next() @@ -205,8 +192,7 @@ describe('Testing the GET /cve-id/:id endpoint in CveId Controller', () => { .get((req, res, next) => { const factory = { getCveIdRepository: () => { return new CveIdGetCveIdNotOwningOrgNotReserved() }, - getUserRepository: () => { return new NullUserRepo() }, - getOrgRepository: () => { return new OrgGetCveIdNotOwnerOrgNotReserved() } + getBaseOrgRepository: () => { return new OrgGetCveIdNotOwnerOrgNotReserved() } } req.ctx.repositories = factory next() @@ -237,12 +223,6 @@ describe('Testing the GET /cve-id/:id endpoint in CveId Controller', () => { } } - class UserGetCveIdOwningOrg { - async findOneByUUID () { - return cveIdFixtures.owningOrgUser - } - } - class OrgGetCveIdOwningOrg { async getOrgUUID () { return cveIdFixtures.owningOrg.UUID @@ -261,8 +241,7 @@ describe('Testing the GET /cve-id/:id endpoint in CveId Controller', () => { .get((req, res, next) => { const factory = { getCveIdRepository: () => { return new CveIdGetCveIdOwningOrg() }, - getUserRepository: () => { return new UserGetCveIdOwningOrg() }, - getOrgRepository: () => { return new OrgGetCveIdOwningOrg() } + getBaseOrgRepository: () => { return new OrgGetCveIdOwningOrg() } } req.ctx.repositories = factory req.ctx.authenticated = true @@ -298,16 +277,6 @@ describe('Testing the GET /cve-id/:id endpoint in CveId Controller', () => { } } - class UserGetCveIdRequestorSecretariat { - async findOneByUUID (uuid) { - if (uuid === cveIdFixtures.secretariatUser.UUID) { - return cveIdFixtures.secretariatUser - } else { - return cveIdFixtures.owningOrgUser - } - } - } - class OrgGetCveIdRequestorSecretariat { async getOrgUUID () { return cveIdFixtures.secretariatOrg.UUID @@ -330,8 +299,7 @@ describe('Testing the GET /cve-id/:id endpoint in CveId Controller', () => { .get((req, res, next) => { const factory = { getCveIdRepository: () => { return new CveIdGetCveIdOwningOrg() }, - getUserRepository: () => { return new UserGetCveIdRequestorSecretariat() }, - getOrgRepository: () => { return new OrgGetCveIdRequestorSecretariat() } + getBaseOrgRepository: () => { return new OrgGetCveIdRequestorSecretariat() } } req.ctx.repositories = factory req.ctx.authenticated = true From 8b83f323387a8209c9dba922ffb1c68da49ac1ea Mon Sep 17 00:00:00 2001 From: James Dalphond Date: Tue, 18 Aug 2026 12:48:56 -0400 Subject: [PATCH 06/20] Move legacy Org create and update writes to BaseOrg ONLY --- src/repositories/baseOrgRepository.js | 76 +----------- test/unit-tests/org/baseOrgRepositoryTest.js | 40 ++++++ test/unit-tests/org/legacy/orgCreateTest.js | 123 ++++--------------- 3 files changed, 69 insertions(+), 170 deletions(-) diff --git a/src/repositories/baseOrgRepository.js b/src/repositories/baseOrgRepository.js index 679dddc6a..8ac442927 100644 --- a/src/repositories/baseOrgRepository.js +++ b/src/repositories/baseOrgRepository.js @@ -772,10 +772,10 @@ class BaseOrgRepository extends BaseRepository { /** * @async * @function createOrg - * @description Creates a new organization in both the registry and a parallel legacy system. It handles the conversion between legacy and registry data formats, assigns a shared UUID, and saves the new organization to the respective data stores. + * @description Creates a new BaseOrg from either the legacy or registry input format. * * @param {object} incomingOrg - The raw organization data object. Can be in either legacy or registry format, specified by the `isLegacyObject` flag. - * @param {object} [options={}] - Optional settings passed to the legacy repository for database operations. + * @param {object} [options={}] - Optional settings for the database operation. * @param {boolean} [isLegacyObject=false] - If true, `incomingOrg` is treated as a legacy-formatted object. If false, it's treated as a registry-formatted object. * @param {string|null} [requestingUserUUID=null] - The user UUID representing the requester, used for audit documentation. If null, no audit document is created. * @param {boolean} [isSecretariat=false] - If true, the operation is performed by a Secretariat. @@ -785,13 +785,9 @@ class BaseOrgRepository extends BaseRepository { */ async createOrg (incomingOrg, options = {}, isLegacyObject = false, requestingUserUUID = null, isSecretariat = false) { const { deepRemoveEmpty } = require('../utils/utils') - const OrgRepository = require('./orgRepository') const CONSTANTS = getConstants() - // In the future we may be able to dynamically detect, but for now we will take a boolean - let legacyObjectRaw = null let registryObjectRaw = null let registryObject = null - const legacyOrgRepo = new OrgRepository() const ReviewObjectRepository = require('./reviewObjectRepository') const reviewObjectRepo = new ReviewObjectRepository() let requestingUsername = null @@ -806,23 +802,15 @@ class BaseOrgRepository extends BaseRepository { const sharedUUID = uuid.v4() if (isLegacyObject) { - legacyObjectRaw = incomingOrg registryObjectRaw = this.convertLegacyToRegistry(incomingOrg) } else { registryObjectRaw = incomingOrg - legacyObjectRaw = this.convertRegistryToLegacy(incomingOrg) } if (!registryObjectRaw.authority) { registryObjectRaw.authority = ['CNA'] } - if (!legacyObjectRaw.authority?.active_roles) { - legacyObjectRaw.authority = { - active_roles: ['CNA'] - } - } - // Registry stuff // Add uuid to org object registryObjectRaw.UUID = sharedUUID @@ -920,44 +908,13 @@ class BaseOrgRepository extends BaseRepository { } } - // Legacy Write, this will be removed when backwards compatibility is no longer needed. - legacyObjectRaw.UUID = sharedUUID - - //* ******* Legacy has some special cases that we have to deal with here.************** - // Holy wow. This should be replaced with something in the future. This is NOT what you think it is - legacyObjectRaw.inUse = false - if (!legacyObjectRaw?.policies?.id_quota) { - // set to default quota if none is specified - _.set(legacyObjectRaw, 'policies.id_quota', CONSTANTS.DEFAULT_ID_QUOTA) - } - if ( - legacyObjectRaw.authority.active_roles.length === 1 && ( - legacyObjectRaw.authority.active_roles[0] === 'ADP' || - legacyObjectRaw.authority.active_roles[0] === 'BULK_DOWNLOAD') - ) { - // ADPs have quota of 0 - _.set(legacyObjectRaw, 'policies.id_quota', 0) - } - - // The legacy way of doing this, the way this is written under the hood there is no other way - // This await does not return a value, even though there is a return in it. :shrugg: - let postUpdate = {} - if (isSecretariat) { - delete legacyObjectRaw.time - postUpdate = await legacyOrgRepo.updateByOrgUUID(sharedUUID, legacyObjectRaw, options) - } - // If we are not a secretariat, then we need to return the uuid of the review object. if (!isSecretariat) { return {} } if (isLegacyObject) { - // This gets us the mongoose object that has all the right data in it, the "legacyObjectRaw" is the custom JSON we are sending. NOT the post written object. - // Convert the actual model, back to a json model - - const legacyObjectRawJson = postUpdate.toObject() - return filterOrg(deepRemoveEmpty(legacyObjectRawJson), isSecretariat) + return filterOrg(deepRemoveEmpty(toLegacyOrg(registryObject)), isSecretariat) } const rawRegistryOrgObject = registryObject.toObject() @@ -967,7 +924,7 @@ class BaseOrgRepository extends BaseRepository { /** * @async * @function updateOrg - * @description Updates an organization's details in both the new registry system and a parallel legacy system. It finds the organization by its short name, applies the provided updates, and saves the changes to both data sources. + * @description Updates a BaseOrg and returns either the registry or legacy compatibility representation. * * @param {string} shortName - The unique short name of the organization to update. * @param {object} incomingParameters - An object containing the fields to update. @@ -1004,27 +961,19 @@ class BaseOrgRepository extends BaseRepository { */ async updateOrg (shortName, incomingParameters, options = {}, isLegacyObject = false, requestingUserUUID = null, isAdmin = false, isSecretariat = false) { const { deepRemoveEmpty } = require('../utils/utils') - const OrgRepository = require('./orgRepository') - // If we get here, we know the org exists - const legacyOrgRepo = new OrgRepository() - - 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) { registryOrg.short_name = incomingParameters.new_short_name - legacyOrg.short_name = incomingParameters.new_short_name } registryOrg.long_name = incomingParameters?.name ?? registryOrg.long_name - legacyOrg.name = incomingParameters?.name ?? legacyOrg.name // TODO: We should probably limit this so it only puts in things that we allow const rolesToAdd = _.flattenDeep(_.compact(_.get(incomingParameters, 'active_roles.add'))).filter(role => getConstants().ORG_ROLES.includes(role)) const rolesToRemove = _.flattenDeep(_.compact(_.get(incomingParameters, 'active_roles.remove'))).filter(role => getConstants().ORG_ROLES.includes(role)) - const initialRoles = legacyOrg.authority?.active_roles ?? [] + const initialRoles = registryOrg.authority ?? [] const finalRoles = [...new Set([...initialRoles, ...rolesToAdd])].filter(role => !rolesToRemove.includes(role)) let roleChange = false @@ -1064,8 +1013,6 @@ class BaseOrgRepository extends BaseRepository { // Replace the reference so later code works with the newly saved document registryOrg = newDoc } - _.set(legacyOrg, 'authority.active_roles', finalRoles) - const directRegistryKeys = [ 'top_level_root', 'charter_or_scope', @@ -1101,15 +1048,6 @@ class BaseOrgRepository extends BaseRepository { registryOrg.id_quota = incomingParameters.id_quota } - const legacyUpdates = {} - - // legacy Only Stuff - if (incomingParameters.id_quota !== undefined) { - _.set(legacyUpdates, 'policies.id_quota', incomingParameters.id_quota) - } - - _.merge(legacyOrg, legacyUpdates) - // ADD AUDIT ENTRY AUTOMATICALLY for the registry object before it gets saved. if (requestingUserUUID) { try { @@ -1146,11 +1084,9 @@ class BaseOrgRepository extends BaseRepository { } } - // Save changes - await legacyOrg.save(options) await registryOrg.save(options) if (isLegacyObject) { - return filterOrg(deepRemoveEmpty(legacyOrg.toObject()), isSecretariat) + return filterOrg(deepRemoveEmpty(toLegacyOrg(registryOrg)), isSecretariat) } return filterOrg(deepRemoveEmpty(registryOrg.toObject()), isSecretariat, true) diff --git a/test/unit-tests/org/baseOrgRepositoryTest.js b/test/unit-tests/org/baseOrgRepositoryTest.js index 8422d2a20..547fb4ad5 100644 --- a/test/unit-tests/org/baseOrgRepositoryTest.js +++ b/test/unit-tests/org/baseOrgRepositoryTest.js @@ -94,4 +94,44 @@ describe('Testing BaseOrgRepository', () => { policies: { id_quota: 100 } }) }) + + it('Should update BaseOrg and return a legacy compatibility response', async () => { + const baseOrgRepo = new BaseOrgRepository() + const registryOrg = { + UUID: 'org-uuid', + short_name: 'mitre', + long_name: 'MITRE', + authority: ['CNA'], + __t: 'CNAOrg', + id_quota: 100, + toObject () { + return { + UUID: this.UUID, + short_name: this.short_name, + long_name: this.long_name, + authority: [...this.authority], + id_quota: this.id_quota, + __t: this.__t + } + }, + save: sinon.stub().resolves() + } + sinon.stub(baseOrgRepo, 'findOneByShortName').resolves(registryOrg) + + const result = await baseOrgRepo.updateOrg( + 'mitre', + { name: 'MITRE Updated', id_quota: 250 }, + {}, + true + ) + + expect(registryOrg.save.calledOnce).to.equal(true) + expect(result).to.deep.equal({ + name: 'MITRE Updated', + short_name: 'mitre', + UUID: 'org-uuid', + authority: { active_roles: ['CNA'] }, + policies: { id_quota: 250 } + }) + }) }) diff --git a/test/unit-tests/org/legacy/orgCreateTest.js b/test/unit-tests/org/legacy/orgCreateTest.js index a2968f097..a13a10176 100644 --- a/test/unit-tests/org/legacy/orgCreateTest.js +++ b/test/unit-tests/org/legacy/orgCreateTest.js @@ -7,15 +7,12 @@ const expect = chai.expect const mongoose = require('mongoose') // Mock Repositories and Controller -const OrgRepository = require('../../../../src/repositories/orgRepository.js') -const UserRepository = require('../../../../src/repositories/userRepository.js') const orgController = require('../../../../src/controller/org.controller/org.controller.js') const BaseOrgRepository = require('../../../../src/repositories/baseOrgRepository.js') const BaseUserRepository = require('../../../../src/repositories/baseUserRepository.js') const SecretariatOrgModel = require('../../../../src/model/secretariatorg.js') const CNAOrgModel = require('../../../../src/model/cnaorg.js') const ADPOrgModel = require('../../../../src/model/adporg.js') -const Org = require('../../../../src/model/org.js') const AuditRepository = require('../../../../src/repositories/auditRepository.js') // Mocks for error messages and constants @@ -53,8 +50,8 @@ const orgFixtures = { } describe('Testing the ORG_CREATE_SINGLE controller', () => { - let status, json, res, next, getOrgRepository, orgRepo, getUserRepository, getBaseOrgRepository, getBaseUserRepository, - userRepo, mockSession, baseOrgRepo, baseUserRepo, fakeBaseSavedObject, saveStub, fakeLegacySavedObject, fakeMongooseDocument, fakeBaseSavedObjectCisco, fakeLegacySavedObjectCisco, auditRepo + let status, json, res, next, getBaseOrgRepository, getBaseUserRepository, + mockSession, baseOrgRepo, baseUserRepo, fakeBaseSavedObject, saveStub, fakeBaseSavedObjectCisco // Runs before each test case beforeEach(() => { @@ -78,7 +75,7 @@ describe('Testing the ORG_CREATE_SINGLE controller', () => { long_name: 'The MITRE Corporation', short_name: 'mitre', UUID: 'e388bfb5-77e3-4faa-a613-424b4af85fce', - authority: 'SECRETARIAT', + authority: ['CNA', 'SECRETARIAT'], id_quota: 1000 } @@ -92,46 +89,11 @@ describe('Testing the ORG_CREATE_SINGLE controller', () => { id_quota: 500 } - fakeLegacySavedObject = { - short_name: 'mitre', - name: 'The MITRE Corporation', - authority: { - active_roles: [ - 'CNA', - 'SECRETARIAT' - ] - }, - policies: { - id_quota: 1000 - } - } - - fakeLegacySavedObjectCisco = { - short_name: 'cisco', - name: 'Cisco', - policies: { - id_quota: 500 - }, - authority: { - active_roles: [ - 'CNA' - ] - }, - UUID: 'ea25674f-6204-40b6-8b47-c72c15f6e6d6', - inUse: false - } - saveStub = sinon.stub(SecretariatOrgModel.prototype, 'save').resolves(fakeBaseSavedObject) - fakeMongooseDocument = new Org(fakeLegacySavedObject) sinon.stub(AuditRepository.prototype, 'appendToAuditHistoryForOrg').resolves(true) // Stub repository getters - orgRepo = new OrgRepository() - getOrgRepository = sinon.stub().returns(orgRepo) - userRepo = new UserRepository() - getUserRepository = sinon.stub().returns(userRepo) - baseOrgRepo = new BaseOrgRepository() getBaseOrgRepository = sinon.stub().returns(baseOrgRepo) baseUserRepo = new BaseUserRepository() @@ -150,7 +112,7 @@ describe('Testing the ORG_CREATE_SINGLE controller', () => { const req = { ctx: { uuid: faker.datatype.uuid(), - repositories: { getOrgRepository, getBaseOrgRepository, getUserRepository, getBaseUserRepository }, + repositories: { getBaseOrgRepository, getBaseUserRepository }, body: orgFixtures.existentOrg // This fixture includes a UUID } } @@ -166,7 +128,6 @@ describe('Testing the ORG_CREATE_SINGLE controller', () => { }) it('Should fail if the organization already exists', async () => { - sinon.stub(orgRepo, 'findOneByShortName').resolves(orgFixtures.existentOrg) sinon.stub(baseOrgRepo, 'findOneByShortName').resolves(orgFixtures.existentOrg) sinon.stub(baseOrgRepo, 'orgExists').resolves(true) @@ -176,7 +137,7 @@ describe('Testing the ORG_CREATE_SINGLE controller', () => { const req = { ctx: { uuid: faker.datatype.uuid(), - repositories: { getOrgRepository, getBaseOrgRepository, getUserRepository, getBaseUserRepository }, + repositories: { getBaseOrgRepository, getBaseUserRepository }, body: testOrgPayload } } @@ -193,17 +154,10 @@ describe('Testing the ORG_CREATE_SINGLE controller', () => { }) context('Positive Tests', () => { - let updateOrgStub, updateBaseOrgStub, aggregateOrgStub, aggregateRegOrgStub - beforeEach(() => { sinon.stub(baseOrgRepo, 'findOneByShortName').resolves(null) sinon.stub(baseOrgRepo, 'orgExists').resolves(false) - aggregateOrgStub = sinon.stub(orgRepo, 'aggregate') - aggregateRegOrgStub = sinon.stub(baseOrgRepo, 'aggregate') - - sinon.stub(orgRepo, 'getOrgUUID').resolves('org-uuid-123') - sinon.stub(userRepo, 'getUserUUID').resolves('user-uuid-123') sinon.stub(baseOrgRepo, 'getOrgUUID').resolves('org-uuid-123') sinon.stub(baseOrgRepo, 'isSecretariatByShortName').resolves(true) sinon.stub(baseUserRepo, 'getUserUUID').resolves('user-uuid-123') @@ -213,20 +167,15 @@ describe('Testing the ORG_CREATE_SINGLE controller', () => { const testOrgPayload = { ...orgFixtures.existentOrg } delete testOrgPayload.UUID - aggregateOrgStub.resolves([testOrgPayload]) - aggregateRegOrgStub.resolves([testOrgPayload]) - const req = { ctx: { uuid: faker.datatype.uuid(), org: 'test_secretariat_org', user: 'test_secretariat_user', - repositories: { getOrgRepository, getUserRepository, getBaseOrgRepository, getBaseUserRepository }, + repositories: { getBaseOrgRepository, getBaseUserRepository }, body: testOrgPayload } } - sinon.stub(OrgRepository.prototype, 'updateByOrgUUID').resolves(fakeMongooseDocument) - sinon.stub(OrgRepository.prototype, 'findOneByShortName').resolves(fakeMongooseDocument) await orgController.ORG_CREATE_SINGLE(req, res, next) expect(status.args[0][0]).to.equal(200) @@ -240,20 +189,15 @@ describe('Testing the ORG_CREATE_SINGLE controller', () => { const testOrgPayload = { ...orgFixtures.existentOrg } delete testOrgPayload.UUID - aggregateOrgStub.resolves([testOrgPayload]) - aggregateRegOrgStub.resolves([testOrgPayload]) - const req = { ctx: { uuid: faker.datatype.uuid(), org: 'test_secretariat_org', user: 'test_secretariat_user', - repositories: { getOrgRepository, getUserRepository, getBaseOrgRepository, getBaseUserRepository }, + repositories: { getBaseOrgRepository, getBaseUserRepository }, body: testOrgPayload } } - sinon.stub(OrgRepository.prototype, 'updateByOrgUUID').resolves(fakeMongooseDocument) - sinon.stub(OrgRepository.prototype, 'findOneByShortName').resolves(fakeMongooseDocument) await orgController.ORG_CREATE_SINGLE(req, res, next) expect(status.args[0][0]).to.equal(200) @@ -268,24 +212,16 @@ describe('Testing the ORG_CREATE_SINGLE controller', () => { const testOrgPayload = { ...orgFixtures.nonExistentOrg } delete testOrgPayload.authority - const expectedCreatedOrg = { ...testOrgPayload, authority: { active_roles: [CONSTANTS.AUTH_ROLE_ENUM.CNA] } } - aggregateOrgStub.resolves([expectedCreatedOrg]) - aggregateRegOrgStub.resolves([expectedCreatedOrg]) - const req = { ctx: { uuid: faker.datatype.uuid(), org: 'test_secretariat_org', user: 'test_secretariat_user', - repositories: { getOrgRepository, getUserRepository, getBaseOrgRepository, getBaseUserRepository }, + repositories: { getBaseOrgRepository, getBaseUserRepository }, body: testOrgPayload } } - const test = await new Org(fakeLegacySavedObjectCisco) - - sinon.stub(OrgRepository.prototype, 'findOneByShortName').resolves(await new Org(fakeLegacySavedObjectCisco)) - sinon.stub(CNAOrgModel.prototype, 'save').resolves(fakeLegacySavedObjectCisco) - sinon.stub(OrgRepository.prototype, 'updateByOrgUUID').resolves(test) + sinon.stub(CNAOrgModel.prototype, 'save').resolves(fakeBaseSavedObjectCisco) await orgController.ORG_CREATE_SINGLE(req, res, next) expect(status.args[0][0]).to.equal(200) @@ -300,23 +236,16 @@ describe('Testing the ORG_CREATE_SINGLE controller', () => { const testOrgPayload = { ...orgFixtures.nonExistentOrg } delete testOrgPayload.policies.id_quota - const expectedCreatedOrg = { ...testOrgPayload, policies: { id_quota: CONSTANTS.DEFAULT_ID_QUOTA } } - aggregateOrgStub.resolves([expectedCreatedOrg]) - aggregateRegOrgStub.resolves([expectedCreatedOrg]) - const req = { ctx: { uuid: faker.datatype.uuid(), org: 'test_secretariat_org', user: 'test_secretariat_user', - repositories: { getOrgRepository, getUserRepository, getBaseOrgRepository, getBaseUserRepository }, + repositories: { getBaseOrgRepository, getBaseUserRepository }, body: testOrgPayload } } - const test = await new Org(expectedCreatedOrg) - sinon.stub(OrgRepository.prototype, 'findOneByShortName').resolves(test) - sinon.stub(CNAOrgModel.prototype, 'save').resolves(expectedCreatedOrg) - sinon.stub(OrgRepository.prototype, 'updateByOrgUUID').resolves(test) + sinon.stub(CNAOrgModel.prototype, 'save').resolves({ ...fakeBaseSavedObjectCisco, id_quota: CONSTANTS.DEFAULT_ID_QUOTA }) await orgController.ORG_CREATE_SINGLE(req, res, next) expect(status.args[0][0]).to.equal(200) @@ -329,23 +258,16 @@ describe('Testing the ORG_CREATE_SINGLE controller', () => { const testOrgPayload = { ...orgFixtures.nonExistentOrg } testOrgPayload.policies.id_quota = null - const expectedCreatedOrg = { ...testOrgPayload, policies: { id_quota: CONSTANTS.DEFAULT_ID_QUOTA } } - aggregateOrgStub.resolves([expectedCreatedOrg]) - aggregateRegOrgStub.resolves([expectedCreatedOrg]) - const req = { ctx: { uuid: faker.datatype.uuid(), org: 'test_secretariat_org', user: 'test_secretariat_user', - repositories: { getOrgRepository, getUserRepository, getBaseOrgRepository, getBaseUserRepository }, + repositories: { getBaseOrgRepository, getBaseUserRepository }, body: testOrgPayload } } - const test = await new Org(expectedCreatedOrg) - sinon.stub(OrgRepository.prototype, 'findOneByShortName').resolves(test) - sinon.stub(CNAOrgModel.prototype, 'save').resolves(expectedCreatedOrg) - sinon.stub(OrgRepository.prototype, 'updateByOrgUUID').resolves(test) + sinon.stub(CNAOrgModel.prototype, 'save').resolves({ ...fakeBaseSavedObjectCisco, id_quota: CONSTANTS.DEFAULT_ID_QUOTA }) await orgController.ORG_CREATE_SINGLE(req, res, next) expect(status.args[0][0]).to.equal(200) @@ -355,27 +277,28 @@ describe('Testing the ORG_CREATE_SINGLE controller', () => { it('Should return newly created org with id_quota of 0 and ADP role', async () => { const testOrgPayload = { ...orgFixtures.stubAdpOrg } - aggregateOrgStub.resolves([{ ...testOrgPayload, policies: { id_quota: 0 } }]) - aggregateRegOrgStub.resolves([{ ...testOrgPayload, policies: { id_quota: 0 } }]) const req = { ctx: { uuid: faker.datatype.uuid(), org: 'test_secretariat_org', user: 'test_secretariat_user', - repositories: { getOrgRepository, getUserRepository, getBaseOrgRepository, getBaseUserRepository }, + repositories: { getBaseOrgRepository, getBaseUserRepository }, body: testOrgPayload } } - const test = await new Org({ ...testOrgPayload, policies: { id_quota: 0 } }) - sinon.stub(OrgRepository.prototype, 'findOneByShortName').resolves(test) - sinon.stub(ADPOrgModel.prototype, 'save').resolves(testOrgPayload) - updateOrgStub = sinon.stub(OrgRepository.prototype, 'updateByOrgUUID').resolves(test) + sinon.stub(ADPOrgModel.prototype, 'save').resolves({ + long_name: testOrgPayload.name, + short_name: testOrgPayload.short_name, + UUID: '96659614-3c76-4d78-a2af-35b7c22cf6e6', + authority: ['ADP'], + id_quota: 0 + }) await orgController.ORG_CREATE_SINGLE(req, res, next) expect(status.args[0][0]).to.equal(200) - expect(updateOrgStub.args[0][1].policies.id_quota).to.equal(0) - expect(updateOrgStub.args[0][1].authority.active_roles[0]).to.equal('ADP') + expect(json.args[0][0].created.policies.id_quota).to.equal(0) + expect(json.args[0][0].created.authority.active_roles).to.deep.equal(['ADP']) }) }) }) From c1f1fd1b2eb5f54d892368027351c0eebd226ad8 Mon Sep 17 00:00:00 2001 From: James Dalphond Date: Tue, 18 Aug 2026 13:27:37 -0400 Subject: [PATCH 07/20] Move registry org writes and CNA authorization to BaseOrg. --- src/middleware/middleware.js | 2 +- src/repositories/baseOrgRepository.js | 44 +++-------- src/repositories/baseOrgRepositoryHelpers.js | 42 ++++------ .../registry-org/registryOrgCRUDTest.js | 10 +-- .../middleware/onlyCnas.fixtures.js | 51 ++++++------- .../middleware/onlyCnasMiddlewareTest.js | 45 +++++++++-- .../org/baseOrgRepositoryHelpersTest.js | 76 ++++++++++--------- test/unit-tests/org/baseOrgRepositoryTest.js | 62 +++++++++++++++ 8 files changed, 190 insertions(+), 142 deletions(-) diff --git a/src/middleware/middleware.js b/src/middleware/middleware.js index a02604c2e..b53d03b81 100644 --- a/src/middleware/middleware.js +++ b/src/middleware/middleware.js @@ -243,7 +243,7 @@ async function onlySecretariatOrAdmin (req, res, next) { // Checks that the requester belongs to an org that has the 'CNA' role async function onlyCnas (req, res, next) { const shortName = req.ctx.org - const orgRepo = req.ctx.repositories.getOrgRepository() + const orgRepo = req.ctx.repositories.getBaseOrgRepository() const CONSTANTS = getConstants() try { diff --git a/src/repositories/baseOrgRepository.js b/src/repositories/baseOrgRepository.js index 8ac442927..dd15ce920 100644 --- a/src/repositories/baseOrgRepository.js +++ b/src/repositories/baseOrgRepository.js @@ -151,7 +151,6 @@ function setAggregateRegistryOrgObj (query, isSecretariat = false) { _id: false, __t: false, __v: false, - inUse: false, in_use: false, parentOrg: false } @@ -1129,7 +1128,7 @@ class BaseOrgRepository extends BaseRepository { /** * @async * @function updateOrgFull - * @description Updates an organization in both the registry and parallel legacy system using the provided full organization body. It finds the organization by its short name, applies the provided updates, and saves the changes to both data sources. + * @description Updates a BaseOrg using the provided full organization body. A legacy compatibility response can be requested without persisting a legacy organization document. * * @param {string} shortName - The short name of the organization to update. * @param {object} incomingOrg - The body containing the full organization object to update. @@ -1143,18 +1142,15 @@ class BaseOrgRepository extends BaseRepository { */ async updateOrgFull (shortName, incomingOrg, options = {}, isLegacyObject = false, requestingUserUUID = null, isAdmin = false, isSecretariat = false) { const { deepRemoveEmpty } = require('../utils/utils') - const OrgRepository = require('./orgRepository') const ReviewObjectRepository = require('./reviewObjectRepository') const BaseUserRepository = require('./baseUserRepository') const ConversationRepository = require('./conversationRepository') const { skipReviewObjectProcessing = false, ...executeOptions } = options - const legacyOrgRepo = new OrgRepository() const reviewObjectRepo = new ReviewObjectRepository() const userRepo = new BaseUserRepository() const conversationRepo = new ConversationRepository() - const legacyOrg = await legacyOrgRepo.findOneByShortName(shortName, executeOptions) const registryOrg = await this.findOneByShortName(shortName, executeOptions) const originalRegistryOrgObject = registryOrg.toObject() const originalRoles = registryOrg.authority @@ -1164,27 +1160,18 @@ class BaseOrgRepository extends BaseRepository { : await reviewObjectRepo.getOrgReviewObjectByOrgShortname(shortName, isSecretariat, executeOptions) const { conversation, ...incomingOrgBody } = incomingOrg - let legacyObjectRaw - let registryObjectRaw + const registryObjectRaw = isLegacyObject + ? this.convertLegacyToRegistry(incomingOrgBody) + : incomingOrgBody - if (isLegacyObject) { - legacyObjectRaw = incomingOrgBody - registryObjectRaw = this.convertLegacyToRegistry(incomingOrgBody) - } else { - registryObjectRaw = incomingOrgBody - legacyObjectRaw = this.convertRegistryToLegacy(incomingOrgBody) - } - - handleShortNameUpdate(incomingOrg, registryObjectRaw, legacyObjectRaw) + handleShortNameUpdate(incomingOrg, registryObjectRaw) const requestingUser = requestingUserUUID ? await userRepo.findUserByUUID(requestingUserUUID, executeOptions) : null const requestingUsername = requestingUser ? requestingUser.username : null const jointApprovalFieldsRegistry = this.getJointApprovalFields(registryOrg, registryObjectRaw) - const jointApprovalFieldsLegacy = this.getJointApprovalFields(legacyOrg, legacyObjectRaw, true) - - let { updatedRegistryOrg, updatedLegacyOrg } = await processJointApprovalAndMerge( - registryOrg, legacyOrg, registryObjectRaw, legacyObjectRaw, reviewObject, isSecretariat, executeOptions, requestingUsername, jointApprovalFieldsRegistry, jointApprovalFieldsLegacy + let updatedRegistryOrg = await processJointApprovalAndMerge( + registryOrg, registryObjectRaw, reviewObject, isSecretariat, executeOptions, requestingUsername, jointApprovalFieldsRegistry ) const conversationArray = [] @@ -1197,14 +1184,13 @@ class BaseOrgRepository extends BaseRepository { updatedRegistryOrg = await handleAuthorityModelChange(updatedRegistryOrg, originalRoles, executeOptions) try { - await updatedLegacyOrg.save(executeOptions) await updatedRegistryOrg.save(executeOptions) } catch (error) { throw new Error(`Failed to update organization ${shortName}. Error: ${error.message}`) } if (isLegacyObject) { - const plainJavascriptLegacyOrg = updatedLegacyOrg.toObject() + const plainJavascriptLegacyOrg = toLegacyOrg(updatedRegistryOrg) plainJavascriptLegacyOrg.joint_approval_required = !(isSecretariat || _.isEmpty(jointApprovalFieldsRegistry)) return filterOrg(deepRemoveEmpty(plainJavascriptLegacyOrg), isSecretariat) } @@ -1218,7 +1204,7 @@ class BaseOrgRepository extends BaseRepository { /** * @async * @function deleteOrg - * @description Deletes an organization in both the registry and parallel legacy system. + * @description Deletes an organization from BaseOrg. * * @param {string} shortName - The short name of the organization to delete. * @param {object} [options={}] - Optional settings for the repository query. @@ -1226,10 +1212,7 @@ class BaseOrgRepository extends BaseRepository { * @returns {Promise} */ async deleteOrg (shortName, options = {}) { - const OrgRepository = require('./orgRepository') - const legacyOrgRepo = new OrgRepository() await BaseOrgModel.deleteOne({ short_name: shortName }, options) - await legacyOrgRepo.deleteOneByShortName(shortName, options) } /** @@ -1384,14 +1367,5 @@ class BaseOrgRepository extends BaseRepository { } } - /** - * @function convertRegistryToLegacy - * @description Converts a registry organization object to the legacy format. - * @param {object} registryOrg - The registry organization object. - * @returns {object} The converted legacy organization object. - */ - convertRegistryToLegacy (registryOrg) { - return toLegacyOrg(registryOrg) - } } module.exports = BaseOrgRepository diff --git a/src/repositories/baseOrgRepositoryHelpers.js b/src/repositories/baseOrgRepositoryHelpers.js index 0f9ae264e..a6fdbc84b 100644 --- a/src/repositories/baseOrgRepositoryHelpers.js +++ b/src/repositories/baseOrgRepositoryHelpers.js @@ -23,18 +23,15 @@ const skipNulls = (objValue, srcValue) => { /** * @function handleShortNameUpdate - * @description Manages the complex process of updating an organization's short name, ensuring all instances and references (legacy and registry) are synchronized with the new short name. + * @description Moves the requested short name into the BaseOrg update payload. * @param {object} incomingOrg - The payload containing the requested updates. * @param {object} registryObjectRaw - The raw data payload mapped for the registry schema. - * @param {object} legacyObjectRaw - The raw data payload mapped for the legacy schema. */ -function handleShortNameUpdate (incomingOrg, registryObjectRaw, legacyObjectRaw) { +function handleShortNameUpdate (incomingOrg, registryObjectRaw) { if (incomingOrg?.new_short_name) { const newName = incomingOrg.new_short_name registryObjectRaw.short_name = newName - legacyObjectRaw.short_name = newName delete registryObjectRaw.new_short_name - delete legacyObjectRaw.new_short_name } } @@ -106,46 +103,33 @@ async function manageReviewObject (registryOrg, registryObjectRaw, jointApproval /** * @async * @function processJointApprovalAndMerge - * @description Orchestrates the merging of new data into both legacy and registry documents. If the user is a standard user modifying restricted fields, it defers those changes by triggering a review object. + * @description Merges incoming data into a BaseOrg document. If a standard user modifies restricted fields, it defers those fields by creating or updating a review object. * @param {object} registryOrg - The current registry organization Mongoose document. - * @param {object} legacyOrg - The current legacy organization Mongoose document. * @param {object} registryObjectRaw - The raw incoming data payload for the registry schema. - * @param {object} legacyObjectRaw - The raw incoming data payload for the legacy schema. * @param {object} reviewObject - An existing review object for the organization. * @param {boolean} isSecretariat - Whether the requester has Secretariat privileges. * @param {object} options - Mongoose options for database queries. * @param {string} requestingUsername - Username of the user making the request. * @param {string[]} jointApprovalFieldsRegistry - Restricted fields that were changed in the registry object. - * @param {string[]} jointApprovalFieldsLegacy - Restricted fields that were changed in the legacy object. - * @returns {Promise} An object containing the securely updated `updatedRegistryOrg` and `updatedLegacyOrg` documents. + * @returns {Promise} The securely updated BaseOrg document. */ -async function processJointApprovalAndMerge (registryOrg, legacyOrg, registryObjectRaw, legacyObjectRaw, reviewObject, isSecretariat, options, requestingUsername, jointApprovalFieldsRegistry, jointApprovalFieldsLegacy) { - const protectedFields = ['_id', 'UUID', '__v', '__t', 'created', 'last_updated', 'createdAt', 'updatedAt', 'users', 'admins', 'inUse', 'in_use'] +async function processJointApprovalAndMerge (registryOrg, registryObjectRaw, reviewObject, isSecretariat, options, requestingUsername, jointApprovalFieldsRegistry) { + // in_use is a BaseOrg lock field. Legacy inUse is deliberately not mapped or preserved here. + const protectedFields = ['_id', 'UUID', '__v', '__t', 'created', 'last_updated', 'createdAt', 'updatedAt', 'users', 'admins', 'in_use'] + const baseOrgObjectRaw = _.omit(registryObjectRaw, ['inUse']) let registryProtectedFields = [...protectedFields] if (!isSecretariat) { registryProtectedFields = [...registryProtectedFields, ...getConstants().ORG_RESTRICTED_FIELDS] } - let updatedRegistryOrg = null - let updatedLegacyOrg = null - if (isSecretariat || _.isEmpty(jointApprovalFieldsRegistry)) { - updatedLegacyOrg = mergeAllowedFields(legacyOrg, legacyObjectRaw, protectedFields) - updatedRegistryOrg = mergeAllowedFields(registryOrg, registryObjectRaw, registryProtectedFields) - } else { - await manageReviewObject(registryOrg, registryObjectRaw, jointApprovalFieldsRegistry, reviewObject, requestingUsername, options) - - const restrictedRegistryFields = [...registryProtectedFields, ...jointApprovalFieldsRegistry] - const restrictedLegacyFields = [...protectedFields, ...jointApprovalFieldsLegacy] - - updatedRegistryOrg = mergeAllowedFields(registryOrg, registryObjectRaw, restrictedRegistryFields) - updatedLegacyOrg = mergeAllowedFields(legacyOrg, legacyObjectRaw, restrictedLegacyFields) + return mergeAllowedFields(registryOrg, baseOrgObjectRaw, registryProtectedFields) } - return { - updatedRegistryOrg, - updatedLegacyOrg - } + await manageReviewObject(registryOrg, baseOrgObjectRaw, jointApprovalFieldsRegistry, reviewObject, requestingUsername, options) + + const restrictedRegistryFields = [...registryProtectedFields, ...jointApprovalFieldsRegistry] + return mergeAllowedFields(registryOrg, baseOrgObjectRaw, restrictedRegistryFields) } /** diff --git a/test/integration-tests/registry-org/registryOrgCRUDTest.js b/test/integration-tests/registry-org/registryOrgCRUDTest.js index c8d6b21db..a982bafc9 100644 --- a/test/integration-tests/registry-org/registryOrgCRUDTest.js +++ b/test/integration-tests/registry-org/registryOrgCRUDTest.js @@ -935,7 +935,7 @@ describe('Testing /registry/org endpoints', () => { .delete(`/api/registry/org/${subOrg.short_name}`) .set(secretariatHeaders) }) - it('Preserves inUse and in_use properties across updates', async () => { + it('Preserves BaseOrg in_use across updates', async () => { // Create an organization const tempOrg = { short_name: 'temp_org_for_in_use_test', @@ -948,10 +948,8 @@ describe('Testing /registry/org endpoints', () => { .set(secretariatHeaders) .send(tempOrg) - // Set the inUse and in_use flags via mongo directly - const Org = require('../../../src/model/org') + // Set the BaseOrg lock flag via mongo directly const BaseOrg = require('../../../src/model/baseorg') - await Org.findOneAndUpdate({ short_name: tempOrg.short_name }, { $set: { inUse: true } }) await BaseOrg.findOneAndUpdate({ short_name: tempOrg.short_name }, { $set: { in_use: true } }) // Update the org using the API @@ -966,10 +964,8 @@ describe('Testing /registry/org endpoints', () => { expect(res).to.have.status(200) }) - // Verify the inUse flags are preserved - const legacyOrgCheck = await Org.findOne({ short_name: tempOrg.short_name }) + // Verify the BaseOrg lock flag is preserved const registryOrgCheck = await BaseOrg.findOne({ short_name: tempOrg.short_name }) - expect(legacyOrgCheck.inUse).to.be.true expect(registryOrgCheck.in_use).to.be.true // Cleanup diff --git a/test/unit-tests/middleware/onlyCnas.fixtures.js b/test/unit-tests/middleware/onlyCnas.fixtures.js index 0ca3e04b5..db7a330d2 100644 --- a/test/unit-tests/middleware/onlyCnas.fixtures.js +++ b/test/unit-tests/middleware/onlyCnas.fixtures.js @@ -10,13 +10,8 @@ const secretariatHeaders = { const secretariatOrg = { UUID: '11kd129f-af00-4d8c-8f7b-e19b0587223f', - authority: { - active_roles: [CONSTANTS.AUTH_ROLE_ENUM.SECRETARIAT] - }, - name: 'The Sec', - policies: { - id_quota: 5 - }, + authority: [CONSTANTS.AUTH_ROLE_ENUM.SECRETARIAT], + long_name: 'The Sec', short_name: 'secretariat' } @@ -44,13 +39,8 @@ const secretariatAndCnaHeaders = { const secretariatAndCnaOrg = { UUID: '15fd129f-af00-4d8c-8f7b-e19b0587223f', - authority: { - active_roles: [CONSTANTS.AUTH_ROLE_ENUM.CNA, CONSTANTS.AUTH_ROLE_ENUM.SECRETARIAT] - }, - name: 'The Sec and CNA', - policies: { - id_quota: 5 - }, + authority: [CONSTANTS.AUTH_ROLE_ENUM.CNA, CONSTANTS.AUTH_ROLE_ENUM.SECRETARIAT], + long_name: 'The Sec and CNA', short_name: 'secretariatAndCna' } @@ -76,15 +66,16 @@ const notCnaHeaders = { 'CVE-API-KEY': 'S96E4QT-SMT4YE3-KX03X6K-4615CED' } +const rootHeaders = { + ...notCnaHeaders, + 'CVE-API-ORG': 'ROOT', + 'CVE-API-USER': 'root_user' +} + const notCnaOrg = { UUID: '25bd129f-af00-4d8c-8f7b-e19b0587223f', - authority: { - active_roles: [''] - }, - name: 'Not a CNA', - policies: { - id_quota: 5 - }, + authority: [], + long_name: 'Not a CNA', short_name: 'not_CNA' } @@ -112,16 +103,18 @@ const cnaHeaders = { const cnaOrg = { UUID: '87yd129f-af00-4d8c-8f7b-e19b0587223f', - authority: { - active_roles: [CONSTANTS.AUTH_ROLE_ENUM.CNA] - }, - name: 'CNA', - policies: { - id_quota: 5 - }, + authority: [CONSTANTS.AUTH_ROLE_ENUM.CNA], + long_name: 'CNA', short_name: 'CNA' } +const rootOrg = { + UUID: '26bd129f-af00-4d8c-8f7b-e19b0587223f', + authority: [CONSTANTS.AUTH_ROLE_ENUM.ROOT], + long_name: 'ROOT', + short_name: 'ROOT' +} + const cnaUser = { UUID: 'f98186d5-ce3d-4fd9-aecd-8698c26897f2', org_UUID: cnaOrg.UUID, @@ -147,6 +140,8 @@ module.exports = { notCnaHeaders, notCnaUser, notCnaOrg, + rootHeaders, + rootOrg, cnaHeaders, cnaUser, cnaOrg diff --git a/test/unit-tests/middleware/onlyCnasMiddlewareTest.js b/test/unit-tests/middleware/onlyCnasMiddlewareTest.js index 8a4542f0e..4e4b6125c 100644 --- a/test/unit-tests/middleware/onlyCnasMiddlewareTest.js +++ b/test/unit-tests/middleware/onlyCnasMiddlewareTest.js @@ -26,7 +26,7 @@ describe('Test only CNA middleware', () => { app.route('/only-cnas-org-cna-passes') .post((req, res, next) => { const factory = { - getOrgRepository: () => { return new OrgOnlyCnasOrgCnaPass() } + getBaseOrgRepository: () => { return new OrgOnlyCnasOrgCnaPass() } } req.ctx.repositories = factory next() @@ -61,7 +61,7 @@ describe('Test only CNA middleware', () => { app.route('/only-cnas-org-cna-and-secretariat-passes') .post((req, res, next) => { const factory = { - getOrgRepository: () => { return new OrgOnlyCnasOrgCnaSecretariatPass() } + getBaseOrgRepository: () => { return new OrgOnlyCnasOrgCnaSecretariatPass() } } req.ctx.repositories = factory next() @@ -98,7 +98,7 @@ describe('Test only CNA middleware', () => { app.route('/only-cnas-org-secretariat-passes') .post((req, res, next) => { const factory = { - getOrgRepository: () => { return new OrgOnlyCnasOrgSecretariatPass() } + getBaseOrgRepository: () => { return new OrgOnlyCnasOrgSecretariatPass() } } req.ctx.repositories = factory next() @@ -135,7 +135,7 @@ describe('Test only CNA middleware', () => { app.route('/only-cnas-org-not-cna-rejected') .post((req, res, next) => { const factory = { - getOrgRepository: () => { return new OrgOnlyCnasOrgNotCnaReject() } + getBaseOrgRepository: () => { return new OrgOnlyCnasOrgNotCnaReject() } } req.ctx.repositories = factory next() @@ -161,6 +161,41 @@ describe('Test only CNA middleware', () => { }) }) + it('Requester is a ROOT organization', function (done) { + class BaseOrgOnlyCnasRootReject { + async findOneByShortName () { + return mwCnaFixtures.rootOrg + } + } + + app.route('/only-cnas-root-rejected') + .post((req, res, next) => { + const factory = { + getBaseOrgRepository: () => { return new BaseOrgOnlyCnasRootReject() } + } + req.ctx.repositories = factory + next() + }, middleware.onlyCnas, (req, res) => { + return res.status(200).json({ message: 'Success! You have reached the target endpoint.' }) + }) + + chai.request(app) + .post('/only-cnas-root-rejected') + .set(mwCnaFixtures.rootHeaders) + .send() + .end((err, res) => { + if (err) { + done(err) + } + + expect(res).to.have.status(403) + const errObj = error.cnaOnly() + expect(res.body.error).to.equal(errObj.error) + expect(res.body.message).to.equal(errObj.message) + done() + }) + }) + it('Requester organization shortname is not valid', function (done) { class OrgOnlyCnasOrgNull { async findOneByShortName () { @@ -171,7 +206,7 @@ describe('Test only CNA middleware', () => { app.route('/only-cnas-org-equals-null') .post((req, res, next) => { const factory = { - getOrgRepository: () => { return new OrgOnlyCnasOrgNull() } + getBaseOrgRepository: () => { return new OrgOnlyCnasOrgNull() } } req.ctx.repositories = factory next() diff --git a/test/unit-tests/org/baseOrgRepositoryHelpersTest.js b/test/unit-tests/org/baseOrgRepositoryHelpersTest.js index 293a2f725..92cb25c13 100644 --- a/test/unit-tests/org/baseOrgRepositoryHelpersTest.js +++ b/test/unit-tests/org/baseOrgRepositoryHelpersTest.js @@ -38,6 +38,19 @@ describe('Testing BaseOrgRepository helper functions', () => { }) }) + context('handleShortNameUpdate', () => { + it('Should apply new_short_name to the BaseOrg payload', () => { + const registryObjectRaw = { + short_name: 'old_short_name', + new_short_name: 'new_short_name' + } + + helpers.handleShortNameUpdate({ new_short_name: 'new_short_name' }, registryObjectRaw) + + expect(registryObjectRaw).to.deep.equal({ short_name: 'new_short_name' }) + }) + }) + context('manageReviewObject', () => { it('Should create a review object when joint approval fields change and none exists', async () => { const createReviewOrgObject = sinon.stub(ReviewObjectRepository.prototype, 'createReviewOrgObject').resolves({}) @@ -101,43 +114,26 @@ describe('Testing BaseOrgRepository helper functions', () => { }), overwrite: sinon.stub().callsFake(arg => arg) } - const legacyDoc = { - toObject: () => ({ - UUID: 'legacy-uuid', - short_name: 'old_legacy' - }), - overwrite: sinon.stub().callsFake(arg => arg) - } const result = await helpers.processJointApprovalAndMerge( registryDoc, - legacyDoc, { UUID: 'malicious-registry-uuid', short_name: 'new_registry', users: ['malicious-user'] }, - { - UUID: 'malicious-legacy-uuid', - short_name: 'new_legacy' - }, null, true, {}, 'secretariat@example.org', - [], [] ) - expect(result.updatedRegistryOrg).to.deep.equal({ + expect(result).to.deep.equal({ UUID: 'registry-uuid', short_name: 'new_registry', users: ['existing-user'] }) - expect(result.updatedLegacyOrg).to.deep.equal({ - UUID: 'legacy-uuid', - short_name: 'new_legacy' - }) }) it('Should defer joint approval fields and merge non-joint fields for non-Secretariat users', async () => { @@ -150,44 +146,50 @@ describe('Testing BaseOrgRepository helper functions', () => { }), overwrite: sinon.stub().callsFake(arg => arg) } - const legacyDoc = { - toObject: () => ({ - UUID: 'legacy-uuid', - short_name: 'old_legacy', - name: 'Old Legacy Name' - }), - overwrite: sinon.stub().callsFake(arg => arg) - } const result = await helpers.processJointApprovalAndMerge( registryDoc, - legacyDoc, { short_name: 'new_registry', long_name: 'New Registry Name' }, - { - short_name: 'new_legacy', - name: 'New Legacy Name' - }, null, false, {}, 'admin@example.org', - ['short_name'], ['short_name'] ) expect(createReviewOrgObject.calledOnce).to.equal(true) - expect(result.updatedRegistryOrg).to.deep.equal({ + expect(result).to.deep.equal({ UUID: 'registry-uuid', short_name: 'old_registry', long_name: 'New Registry Name' }) - expect(result.updatedLegacyOrg).to.deep.equal({ - UUID: 'legacy-uuid', - short_name: 'old_legacy', - name: 'New Legacy Name' + }) + + it('Should not map a legacy inUse property into BaseOrg', async () => { + const registryDoc = { + toObject: () => ({ + UUID: 'registry-uuid', + in_use: true + }), + overwrite: sinon.stub().callsFake(arg => arg) + } + + const result = await helpers.processJointApprovalAndMerge( + registryDoc, + { inUse: false }, + null, + true, + {}, + 'secretariat@example.org', + [] + ) + + expect(result).to.deep.equal({ + UUID: 'registry-uuid', + in_use: true }) }) }) diff --git a/test/unit-tests/org/baseOrgRepositoryTest.js b/test/unit-tests/org/baseOrgRepositoryTest.js index 547fb4ad5..0c66871b2 100644 --- a/test/unit-tests/org/baseOrgRepositoryTest.js +++ b/test/unit-tests/org/baseOrgRepositoryTest.js @@ -134,4 +134,66 @@ describe('Testing BaseOrgRepository', () => { policies: { id_quota: 250 } }) }) + + it('Should fully update and save only the BaseOrg record', async () => { + const baseOrgRepo = new BaseOrgRepository() + const registryOrg = { + UUID: 'org-uuid', + short_name: 'mitre', + long_name: 'MITRE', + authority: ['CNA'], + users: ['existing-user'], + admins: ['existing-admin'], + toObject () { + return { + UUID: this.UUID, + short_name: this.short_name, + long_name: this.long_name, + authority: [...this.authority], + users: [...this.users], + admins: [...this.admins] + } + }, + overwrite (data) { + Object.assign(this, data) + return this + }, + save: sinon.stub().resolves() + } + sinon.stub(baseOrgRepo, 'findOneByShortName').resolves(registryOrg) + + const result = await baseOrgRepo.updateOrgFull( + 'mitre', + { + new_short_name: 'mitre-updated', + long_name: 'MITRE Updated', + authority: ['CNA'] + }, + { skipReviewObjectProcessing: true }, + false, + null, + false, + true + ) + + expect(registryOrg.save.calledOnce).to.equal(true) + expect(result).to.include({ + UUID: 'org-uuid', + short_name: 'mitre-updated', + long_name: 'MITRE Updated' + }) + expect(result.authority).to.deep.equal(['CNA']) + expect(result.users).to.deep.equal(['existing-user']) + expect(result.admins).to.deep.equal(['existing-admin']) + }) + + it('Should delete the BaseOrg record without using the legacy Org repository', async () => { + const deleteOne = sinon.stub(BaseOrgModel, 'deleteOne').resolves({ deletedCount: 1 }) + const repo = new BaseOrgRepository() + const options = { session: 'mock-session' } + + await repo.deleteOrg('mitre', options) + + expect(deleteOne.calledOnceWith({ short_name: 'mitre' }, options)).to.equal(true) + }) }) From a144f5895e6929a6dd0465fb8393d624c8acd315 Mon Sep 17 00:00:00 2001 From: James Dalphond Date: Tue, 18 Aug 2026 13:46:52 -0400 Subject: [PATCH 08/20] Use BaseOrg for CVE-ID updates and reservations --- .../cve-id.controller/cve-id.controller.js | 44 +++++++++---------- .../unit-tests/cve-id/cveIdRangeCreateTest.js | 4 +- test/unit-tests/cve-id/cveIdUpdateTest.js | 25 +++++++---- .../reserveCveIdTest.non-sequential.js | 3 +- ...eserveCveIdTest.usersA_B.non-sequential.js | 9 ++-- .../reserveCveId/cveIdReservePriorityTest.js | 6 ++- .../cveIdReserveSequentialTest.js | 3 +- 7 files changed, 54 insertions(+), 40 deletions(-) diff --git a/src/controller/cve-id.controller/cve-id.controller.js b/src/controller/cve-id.controller/cve-id.controller.js index d305a7363..e1195d49f 100644 --- a/src/controller/cve-id.controller/cve-id.controller.js +++ b/src/controller/cve-id.controller/cve-id.controller.js @@ -351,7 +351,7 @@ async function modifyCveId (req, res, next) { let state let newOrgShortName let orgUUID - const orgRepo = req.ctx.repositories.getOrgRepository() + const baseOrgRepo = req.ctx.repositories.getBaseOrgRepository() const cveIdRepo = req.ctx.repositories.getCveIdRepository() const userRepo = req.ctx.repositories.getUserRepository() const cveRepo = req.ctx.repositories.getCveRepository() @@ -359,9 +359,9 @@ async function modifyCveId (req, res, next) { return res.status(403).json(error.orgCannotReserveForOther()) } - const requesterOrgUUID = await authContext.getRequesterOrgUUID(req, orgRepo) - const org = requesterOrgUUID && typeof orgRepo.findOneByUUID === 'function' - ? await orgRepo.findOneByUUID(requesterOrgUUID) + const requesterOrgUUID = await authContext.getRequesterOrgUUID(req, baseOrgRepo) + const org = requesterOrgUUID && typeof baseOrgRepo.findOneByUUID === 'function' + ? await baseOrgRepo.findOneByUUID(requesterOrgUUID) : null if (!org) { return res.status(403).json(error.orgCannotReserveForOther()) @@ -369,7 +369,7 @@ async function modifyCveId (req, res, next) { // Get remaining org quota const totalReserved = await cveIdRepo.countDocuments({ owning_cna: org.UUID, state: 'RESERVED' }) - const remainingQuota = (org.policies.id_quota - totalReserved) + const remainingQuota = (org.id_quota - totalReserved) // Check for existing record - await only allowed at top level so cannot // move inside of it statement below @@ -411,7 +411,7 @@ async function modifyCveId (req, res, next) { } if (newOrgShortName) { - orgUUID = await orgRepo.getOrgUUID(newOrgShortName) + orgUUID = await baseOrgRepo.getOrgUUID(newOrgShortName) if (!orgUUID) { logger.info({ uuid: req.ctx.uuid, message: id + ' could not be reassigned to ' + newOrgShortName + ' in MongoDB because it does not exist.' }) @@ -438,7 +438,7 @@ async function modifyCveId (req, res, next) { org_UUID: requesterOrgUUID, cve_id: result } - payload.user_UUID = await authContext.getRequesterUserUUID(req, userRepo, orgRepo) + payload.user_UUID = await authContext.getRequesterUserUUID(req, userRepo, baseOrgRepo) logger.info(JSON.stringify(payload)) return res.status(200).json(responseMessage) } catch (err) { @@ -452,7 +452,7 @@ async function createCveIdRange (req, res, next) { const CONSTANTS = getConstants() const year = req.ctx.params.year const cveIdRangeRepo = req.ctx.repositories.getCveIdRangeRepository() - const orgRepo = req.ctx.repositories.getOrgRepository() + const baseOrgRepo = req.ctx.repositories.getBaseOrgRepository() const userRepo = req.ctx.repositories.getUserRepository() const result = await cveIdRangeRepo.findOne({ cve_year: year }) @@ -468,9 +468,9 @@ async function createCveIdRange (req, res, next) { action: 'create_cveIdRange', change: 'CVE Id Range document for year ' + year + ' was created.', req_UUID: req.ctx.uuid, - org_UUID: await authContext.getRequesterOrgUUID(req, orgRepo) + org_UUID: await authContext.getRequesterOrgUUID(req, baseOrgRepo) } - payload.user_UUID = await authContext.getRequesterUserUUID(req, userRepo, orgRepo) + payload.user_UUID = await authContext.getRequesterUserUUID(req, userRepo, baseOrgRepo) logger.info(JSON.stringify(payload)) return res.status(200).send() @@ -518,13 +518,13 @@ async function priorityReservation (year, amount, shortName, orgShortName, reque } if (!isFull) { - const orgRepo = req.ctx.repositories.getOrgRepository() + const baseOrgRepo = req.ctx.repositories.getBaseOrgRepository() const userRepo = req.ctx.repositories.getUserRepository() const cveIdRepo = req.ctx.repositories.getCveIdRepository() const id = generateSequentialIds(year, result.ranges.priority.top_id, amount) - const owningOrgUUID = await orgRepo.getOrgUUID(shortName) - const orgUUID = await authContext.getRequesterOrgUUID(req, orgRepo) - const requesterUUID = await authContext.getRequesterUserUUID(req, userRepo, orgRepo) + const owningOrgUUID = await baseOrgRepo.getOrgUUID(shortName) + const orgUUID = await authContext.getRequesterOrgUUID(req, baseOrgRepo) + const requesterUUID = await authContext.getRequesterUserUUID(req, userRepo, baseOrgRepo) let cveIdDocuments = [] const cveIdDocumentsUUID = [] @@ -615,13 +615,13 @@ async function sequentialReservation (year, amount, shortName, orgShortName, req } if (!isFull) { - const orgRepo = req.ctx.repositories.getOrgRepository() + const baseOrgRepo = req.ctx.repositories.getBaseOrgRepository() const userRepo = req.ctx.repositories.getUserRepository() const cveIdRepo = req.ctx.repositories.getCveIdRepository() const ids = generateSequentialIds(year, result.ranges.general.top_id, amount) - const owningOrgUUID = await orgRepo.getOrgUUID(shortName) - const orgUUID = await authContext.getRequesterOrgUUID(req, orgRepo) - const requesterUUID = await authContext.getRequesterUserUUID(req, userRepo, orgRepo) + const owningOrgUUID = await baseOrgRepo.getOrgUUID(shortName) + const orgUUID = await authContext.getRequesterOrgUUID(req, baseOrgRepo) + const requesterUUID = await authContext.getRequesterUserUUID(req, userRepo, baseOrgRepo) let cveIdDocuments = [] const cveIdDocumentsUUID = [] let cveIdUUID @@ -725,15 +725,15 @@ async function nonSequentialReservation (year, amount, shortName, orgShortName, } // Case 2: Enough IDs in the 'AVAILABLE' pool - const orgRepo = req.ctx.repositories.getOrgRepository() + const baseOrgRepo = req.ctx.repositories.getBaseOrgRepository() const userRepo = req.ctx.repositories.getUserRepository() let index let counter = 0 const cveIdDocuments = [] const cveIdDocumentsUUID = [] - const owningOrgUUID = await orgRepo.getOrgUUID(shortName) - const orgUUID = await authContext.getRequesterOrgUUID(req, orgRepo) - const requesterUUID = await authContext.getRequesterUserUUID(req, userRepo, orgRepo) + const owningOrgUUID = await baseOrgRepo.getOrgUUID(shortName) + const orgUUID = await authContext.getRequesterOrgUUID(req, baseOrgRepo) + const requesterUUID = await authContext.getRequesterUserUUID(req, userRepo, baseOrgRepo) const q = availableIds - amount while ((counter < amount) && !isFull) { diff --git a/test/unit-tests/cve-id/cveIdRangeCreateTest.js b/test/unit-tests/cve-id/cveIdRangeCreateTest.js index 9782333c2..ca7d7e74b 100644 --- a/test/unit-tests/cve-id/cveIdRangeCreateTest.js +++ b/test/unit-tests/cve-id/cveIdRangeCreateTest.js @@ -75,7 +75,7 @@ describe('Testing the POST /cve-id-range/:year endpoint in CveId Controller', () const factory = { getCveIdRangeRepository: () => { return new CveIdRange2022Exists() }, getUserRepository: () => { return new NullUserRepo() }, - getOrgRepository: () => { return new NullOrgRepo() } + getBaseOrgRepository: () => { return new NullOrgRepo() } } req.ctx.repositories = factory next() @@ -116,7 +116,7 @@ describe('Testing the POST /cve-id-range/:year endpoint in CveId Controller', () const factory = { getCveIdRangeRepository: () => { return new CveIdRange2022NotExists() }, getUserRepository: () => { return new NullUserRepo() }, - getOrgRepository: () => { return new NullOrgRepo() } + getBaseOrgRepository: () => { return new NullOrgRepo() } } req.ctx.repositories = factory next() diff --git a/test/unit-tests/cve-id/cveIdUpdateTest.js b/test/unit-tests/cve-id/cveIdUpdateTest.js index a1c8dbdd4..1d0c7f79c 100644 --- a/test/unit-tests/cve-id/cveIdUpdateTest.js +++ b/test/unit-tests/cve-id/cveIdUpdateTest.js @@ -18,6 +18,13 @@ const cveIdFixtures = require('./mockObjects.cve-id') const cveIdController = require('../../../src/controller/cve-id.controller/cve-id.controller') const cveIdParams = require('../../../src/controller/cve-id.controller/cve-id.middleware') +const baseSecretariatOrg = { + UUID: cveIdFixtures.secretariatOrg.UUID, + authority: ['CNA', 'SECRETARIAT'], + id_quota: cveIdFixtures.secretariatOrg.policies.id_quota, + short_name: cveIdFixtures.secretariatOrg.short_name +} + class NullUserRepo { async getUserUUID () { return null @@ -56,7 +63,7 @@ class OrgModifyCveIdOrgAndStateModified { } async findOneByUUID () { - return cveIdFixtures.secretariatOrg + return baseSecretariatOrg } async findOneByShortName (shortName) { @@ -101,7 +108,7 @@ describe('Testing the PUT /cve-id/:id endpoint in CveId Controller', () => { } async findOneByUUID () { - return cveIdFixtures.secretariatOrg + return baseSecretariatOrg } async findOneByShortName () { @@ -114,7 +121,7 @@ describe('Testing the PUT /cve-id/:id endpoint in CveId Controller', () => { .put((req, res, next) => { const factory = { getCveIdRepository: () => { return new CveIdModifyCveIdOrgAndStateModified() }, - getOrgRepository: () => { return new OrgModifyCveIdUnauthenticated() }, + getBaseOrgRepository: () => { return new OrgModifyCveIdUnauthenticated() }, getUserRepository: () => { return new NullUserRepo() }, getCveRepository: () => { return new NullCveRepo() } } @@ -157,7 +164,7 @@ describe('Testing the PUT /cve-id/:id endpoint in CveId Controller', () => { } async findOneByUUID () { - return cveIdFixtures.secretariatOrg + return baseSecretariatOrg } async findOneByShortName (shortName) { @@ -169,7 +176,7 @@ describe('Testing the PUT /cve-id/:id endpoint in CveId Controller', () => { .put((req, res, next) => { const factory = { getCveIdRepository: () => { return new CveIdModifyCveIdDoesntExist() }, - getOrgRepository: () => { return new OrgModifyCveIdDoesntExist() }, + getBaseOrgRepository: () => { return new OrgModifyCveIdDoesntExist() }, getUserRepository: () => { return new NullUserRepo() }, getCveRepository: () => { return new NullCveRepo() } } @@ -202,7 +209,7 @@ describe('Testing the PUT /cve-id/:id endpoint in CveId Controller', () => { } async findOneByUUID () { - return cveIdFixtures.secretariatOrg + return baseSecretariatOrg } async findOneByShortName (shortName) { @@ -214,7 +221,7 @@ describe('Testing the PUT /cve-id/:id endpoint in CveId Controller', () => { .put((req, res, next) => { const factory = { getCveIdRepository: () => { return new CveIdModifyCveIdOrgAndStateModified() }, - getOrgRepository: () => { return new OrgModifyCveIdOrgDoesntExist() }, + getBaseOrgRepository: () => { return new OrgModifyCveIdOrgDoesntExist() }, getUserRepository: () => { return new NullUserRepo() }, getCveRepository: () => { return new NullCveRepo() } } @@ -282,7 +289,7 @@ describe('Testing the PUT /cve-id/:id endpoint in CveId Controller', () => { .put((req, res, next) => { const factory = { getCveIdRepository: () => { return new CveIdModifyCveIdOrgAndStateModified() }, - getOrgRepository: () => { return new OrgModifyCveIdOrgAndStateModified() }, + getBaseOrgRepository: () => { return new OrgModifyCveIdOrgAndStateModified() }, getUserRepository: () => { return new UserModifyCveIdOrgAndStateModified() }, getCveRepository: () => { return new NullCveRepo() } } @@ -343,7 +350,7 @@ describe('Testing the PUT /cve-id/:id endpoint in CveId Controller', () => { .put((req, res, next) => { const factory = { getCveIdRepository: () => { return new CveIdModifyCveIdNoQuery() }, - getOrgRepository: () => { return new OrgModifyCveIdOrgAndStateModified() }, + getBaseOrgRepository: () => { return new OrgModifyCveIdOrgAndStateModified() }, getUserRepository: () => { return new UserModifyCveIdOrgAndStateModified() }, getCveRepository: () => { return new NullCveRepo() } } diff --git a/test/unit-tests/cve-id/reserveCveId.non-sequential/reserveCveIdTest.non-sequential.js b/test/unit-tests/cve-id/reserveCveId.non-sequential/reserveCveIdTest.non-sequential.js index 18a8a47e2..98e33c19e 100644 --- a/test/unit-tests/cve-id/reserveCveId.non-sequential/reserveCveIdTest.non-sequential.js +++ b/test/unit-tests/cve-id/reserveCveId.non-sequential/reserveCveIdTest.non-sequential.js @@ -239,7 +239,8 @@ describe('Testing the non sequential reservation (Base Case) of POST /cve-id end getCveIdRepository: () => { return cveIdRepo }, getCveIdRangeRepository: () => { return cveIdRangeRepo }, getUserRepository: () => { return userRepo }, - getOrgRepository: () => { return orgRepo } + getOrgRepository: () => { return orgRepo }, + getBaseOrgRepository: () => { return orgRepo } } req.ctx.repositories = factory setOrgAUUIDContext(req) diff --git a/test/unit-tests/cve-id/reserveCveId.non-sequential/reserveCveIdTest.usersA_B.non-sequential.js b/test/unit-tests/cve-id/reserveCveId.non-sequential/reserveCveIdTest.usersA_B.non-sequential.js index f4db7ddeb..9847bef5d 100644 --- a/test/unit-tests/cve-id/reserveCveId.non-sequential/reserveCveIdTest.usersA_B.non-sequential.js +++ b/test/unit-tests/cve-id/reserveCveId.non-sequential/reserveCveIdTest.usersA_B.non-sequential.js @@ -282,7 +282,8 @@ describe('Testing the non sequential reservation (Case AB) of POST /cve-id endpo getCveIdRepository: () => { return cveIdRepo }, getCveIdRangeRepository: () => { return cveIdRangeRepo }, getUserRepository: () => { return userRepo }, - getOrgRepository: () => { return orgRepo } + getOrgRepository: () => { return orgRepo }, + getBaseOrgRepository: () => { return orgRepo } } req.ctx.repositories = factory setOrgAUUIDContext(req) @@ -382,7 +383,8 @@ describe('Testing the non sequential reservation (Case AB) of POST /cve-id endpo getCveIdRepository: () => { return new CveIdReservePoolIncremented10IdsCaseAB2() }, getCveIdRangeRepository: () => { return new CveIdRangeReserveNonSequentialSuccessCaseAB2() }, getUserRepository: () => { return new UserReserveNonSequentialSuccessCaseAB() }, - getOrgRepository: () => { return new OrgReserveNonSequentialSuccessCaseAB() } + getOrgRepository: () => { return new OrgReserveNonSequentialSuccessCaseAB() }, + getBaseOrgRepository: () => { return new OrgReserveNonSequentialSuccessCaseAB() } } req.ctx.repositories = factory setOrgBUUIDContext(req) @@ -588,7 +590,8 @@ describe('Testing the non sequential reservation (Case AB) of POST /cve-id endpo getCveIdRepository: () => { return new CveIdReservePoolIncremented10IdsCaseAB3() }, getCveIdRangeRepository: () => { return new CveIdRangeReserveNonSequentialSuccessCaseAB3() }, getUserRepository: () => { return new UserReserveNonSequentialSuccessCaseAB() }, - getOrgRepository: () => { return new OrgReserveNonSequentialSuccessCaseAB() } + getOrgRepository: () => { return new OrgReserveNonSequentialSuccessCaseAB() }, + getBaseOrgRepository: () => { return new OrgReserveNonSequentialSuccessCaseAB() } } req.ctx.repositories = factory setOrgAUUIDContext(req) diff --git a/test/unit-tests/cve-id/reserveCveId/cveIdReservePriorityTest.js b/test/unit-tests/cve-id/reserveCveId/cveIdReservePriorityTest.js index 8933d337e..2076dfac6 100644 --- a/test/unit-tests/cve-id/reserveCveId/cveIdReservePriorityTest.js +++ b/test/unit-tests/cve-id/reserveCveId/cveIdReservePriorityTest.js @@ -258,7 +258,8 @@ describe('Testing the priority reservation of POST /cve-id endpoint in CveId Con getCveIdRepository: () => { return new CveIdReserveSequentialPriorityIsFull() }, getCveIdRangeRepository: () => { return new CveIdRangeReserveSequentialPriorityIsFull() }, getUserRepository: () => { return new UserReserveSequentialPriorityIsFull() }, - getOrgRepository: () => { return new OrgReserveSequentialPriorityIsFull() } + getOrgRepository: () => { return new OrgReserveSequentialPriorityIsFull() }, + getBaseOrgRepository: () => { return new OrgReserveSequentialPriorityIsFull() } } req.ctx.repositories = factory setOwningOrgUUIDContext(req) @@ -330,7 +331,8 @@ describe('Testing the priority reservation of POST /cve-id endpoint in CveId Con getCveIdRepository: () => { return new CveIdReserveSequentialPriorityIsFull() }, getCveIdRangeRepository: () => { return new CveIdRangeReserveSequentialPriority() }, getUserRepository: () => { return new UserReserveSequentialPriorityIsFull() }, - getOrgRepository: () => { return new OrgReserveSequentialPriorityIsFull() } + getOrgRepository: () => { return new OrgReserveSequentialPriorityIsFull() }, + getBaseOrgRepository: () => { return new OrgReserveSequentialPriorityIsFull() } } req.ctx.repositories = factory setOwningOrgUUIDContext(req) diff --git a/test/unit-tests/cve-id/reserveCveId/cveIdReserveSequentialTest.js b/test/unit-tests/cve-id/reserveCveId/cveIdReserveSequentialTest.js index 4b1e94687..12c7937e4 100644 --- a/test/unit-tests/cve-id/reserveCveId/cveIdReserveSequentialTest.js +++ b/test/unit-tests/cve-id/reserveCveId/cveIdReserveSequentialTest.js @@ -243,7 +243,8 @@ describe('Testing the sequential reservation of POST /cve-id endpoint in CveId C getCveIdRepository: () => { return new CveIdReserveSequential() }, getCveIdRangeRepository: () => { return new CveIdRangeReserveSequential() }, getUserRepository: () => { return new UserReserveSequentialPriorityIsFull() }, - getOrgRepository: () => { return new OrgReserveSequentialPriorityIsFull() } + getOrgRepository: () => { return new OrgReserveSequentialPriorityIsFull() }, + getBaseOrgRepository: () => { return new OrgReserveSequentialPriorityIsFull() } } req.ctx.repositories = factory setOwningOrgUUIDContext(req) From 935264b69ab77359ad3f3f39362d807726c1a0f6 Mon Sep 17 00:00:00 2001 From: James Dalphond Date: Tue, 18 Aug 2026 14:02:45 -0400 Subject: [PATCH 09/20] Move onlyAdps, cnaMustOwnID, and CVE controller org lookups and authorization to BaseOrg --- .../cve.controller/cve.controller.js | 50 +++++++++---------- src/middleware/middleware.js | 10 ++-- .../cve/cveCnaContainerCreateTest.js | 6 +-- .../cve/cveCnaContainerUpdateTest.js | 6 +-- test/unit-tests/cve/cveCreateTest.js | 4 +- test/unit-tests/cve/cveRecordRejectionTest.js | 4 +- test/unit-tests/cve/cveUpdateTest.js | 12 ++--- test/unit-tests/cve/insertAdpTest.js | 12 ++--- test/unit-tests/cve/updateCnaTest.js | 12 ++--- .../cnaMustOwnIdTestMiddlewareTest.js | 12 ++--- test/unit-tests/middleware/onlyAdpsTest.js | 36 +++++-------- 11 files changed, 76 insertions(+), 88 deletions(-) diff --git a/src/controller/cve.controller/cve.controller.js b/src/controller/cve.controller/cve.controller.js index 098f5e084..539fb12a1 100644 --- a/src/controller/cve.controller/cve.controller.js +++ b/src/controller/cve.controller/cve.controller.js @@ -379,7 +379,7 @@ async function submitCve (req, res, next) { const state = newCve.cve.cveMetadata.state const cveRepo = req.ctx.repositories.getCveRepository() const cveIdRepo = req.ctx.repositories.getCveIdRepository() - const orgRepo = req.ctx.repositories.getOrgRepository() + const baseOrgRepo = req.ctx.repositories.getBaseOrgRepository() // the cve id provided in the body must match the cve id provided in the URL params if (id !== cveId) { @@ -419,11 +419,11 @@ async function submitCve (req, res, next) { action: 'create_cve_record', change: cveId + ' record was successfully created.', req_UUID: req.ctx.uuid, - org_UUID: await authContext.getRequesterOrgUUID(req, orgRepo), + org_UUID: await authContext.getRequesterOrgUUID(req, baseOrgRepo), cve: cveId } const userRepo = req.ctx.repositories.getUserRepository() - payload.user_UUID = await authContext.getRequesterUserUUID(req, userRepo, orgRepo) + payload.user_UUID = await authContext.getRequesterUserUUID(req, userRepo, baseOrgRepo) logger.info(JSON.stringify(payload)) return res.status(200).json(responseMessage) } catch (err) { @@ -442,7 +442,7 @@ async function updateCve (req, res, next) { const cveId = req.ctx.params.id const cveRepo = req.ctx.repositories.getCveRepository() const cveIdRepo = req.ctx.repositories.getCveIdRepository() - const orgRepo = req.ctx.repositories.getOrgRepository() + const baseOrgRepo = req.ctx.repositories.getBaseOrgRepository() const newCveMetaData = newCve.cve.cveMetadata const newCveId = newCveMetaData.cveId const newCveState = newCveMetaData.state @@ -482,12 +482,12 @@ async function updateCve (req, res, next) { action: 'update_cve_record', change: cveId + ' record was successfully updated.', req_UUID: req.ctx.uuid, - org_UUID: await authContext.getRequesterOrgUUID(req, orgRepo), + org_UUID: await authContext.getRequesterOrgUUID(req, baseOrgRepo), cve: cveId } const userRepo = req.ctx.repositories.getUserRepository() - payload.user_UUID = await authContext.getRequesterUserUUID(req, userRepo, orgRepo) + payload.user_UUID = await authContext.getRequesterUserUUID(req, userRepo, baseOrgRepo) logger.info(JSON.stringify(payload)) return res.status(200).json(responseMessage) } catch (err) { @@ -503,10 +503,10 @@ async function submitCna (req, res, next) { const id = req.ctx.params.id const cveRepo = req.ctx.repositories.getCveRepository() const cveIdRepo = req.ctx.repositories.getCveIdRepository() - const orgRepo = req.ctx.repositories.getOrgRepository() + const baseOrgRepo = req.ctx.repositories.getBaseOrgRepository() const userRepo = req.ctx.repositories.getUserRepository() - const orgUuid = await authContext.getRequesterOrgUUID(req, orgRepo) - const userUuid = await authContext.getRequesterUserUUID(req, userRepo, orgRepo) + const orgUuid = await authContext.getRequesterOrgUUID(req, baseOrgRepo) + const userUuid = await authContext.getRequesterUserUUID(req, userRepo, baseOrgRepo) // To avoid breaking legacy behavior in the "booleanIsTrue" function, we need to check to make sure that undefined is set to false let erlCheck @@ -524,7 +524,7 @@ async function submitCna (req, res, next) { // check that cveId org matches user org const cveId = result - const isSecretariat = await authContext.isRequesterSecretariat(req, orgRepo) + const isSecretariat = await authContext.isRequesterSecretariat(req, baseOrgRepo) if ((cveId.owning_cna !== orgUuid) && !isSecretariat) { return res.status(403).json(error.owningOrgDoesNotMatch()) } @@ -542,7 +542,7 @@ async function submitCna (req, res, next) { } // create full cve record here - const owningCna = await orgRepo.findOneByUUID(cveId.owning_cna) + const owningCna = await baseOrgRepo.findOneByUUID(cveId.owning_cna) const assignerShortName = owningCna.short_name const dateUpdated = (new Date()).toISOString() const additionalCveMetadataFields = { @@ -599,10 +599,10 @@ async function updateCna (req, res, next) { const id = req.ctx.params.id const cveRepo = req.ctx.repositories.getCveRepository() const cveIdRepo = req.ctx.repositories.getCveIdRepository() - const orgRepo = req.ctx.repositories.getOrgRepository() + const baseOrgRepo = req.ctx.repositories.getBaseOrgRepository() const userRepo = req.ctx.repositories.getUserRepository() - const orgUuid = await authContext.getRequesterOrgUUID(req, orgRepo) - const userUuid = await authContext.getRequesterUserUUID(req, userRepo, orgRepo) + const orgUuid = await authContext.getRequesterOrgUUID(req, baseOrgRepo) + const userUuid = await authContext.getRequesterUserUUID(req, userRepo, baseOrgRepo) // To avoid breaking legacy behavior in the "booleanIsTrue" function, we need to check to make sure that undefined is set to false let erlCheck @@ -620,7 +620,7 @@ async function updateCna (req, res, next) { // check that cveId org matches user org const cveId = result - const isSecretariat = await authContext.isRequesterSecretariat(req, orgRepo) + const isSecretariat = await authContext.isRequesterSecretariat(req, baseOrgRepo) if ((cveId.owning_cna !== orgUuid) && !isSecretariat) { return res.status(403).json(error.owningOrgDoesNotMatch()) } @@ -725,9 +725,9 @@ async function rejectCVE (req, res, next) { } // Both orgs below should exist since they passed validation - const orgRepo = req.ctx.repositories.getOrgRepository() - const providerOrgObj = await authContext.getRequesterOrg(req, orgRepo) - const owningCnaObj = await orgRepo.findOneByUUID(cveIdObj.owning_cna) + const baseOrgRepo = req.ctx.repositories.getBaseOrgRepository() + const providerOrgObj = await authContext.getRequesterOrg(req, baseOrgRepo) + const owningCnaObj = await baseOrgRepo.findOneByUUID(cveIdObj.owning_cna) const owningCnaShortName = owningCnaObj?.short_name @@ -766,7 +766,7 @@ async function rejectCVE (req, res, next) { cve: id } const userRepo = req.ctx.repositories.getUserRepository() - payload.user_UUID = await authContext.getRequesterUserUUID(req, userRepo, orgRepo) + payload.user_UUID = await authContext.getRequesterUserUUID(req, userRepo, baseOrgRepo) logger.info(JSON.stringify(payload)) return res.status(200).json(responseMessage) } catch (err) { @@ -782,8 +782,8 @@ async function rejectExistingCve (req, res, next) { const id = req.ctx.params.id const cveIdRepo = req.ctx.repositories.getCveIdRepository() const cveRepo = req.ctx.repositories.getCveRepository() - const orgRepo = req.ctx.repositories.getOrgRepository() - const providerOrgObj = await authContext.getRequesterOrg(req, orgRepo) + const baseOrgRepo = req.ctx.repositories.getBaseOrgRepository() + const providerOrgObj = await authContext.getRequesterOrg(req, baseOrgRepo) // check that cve id exists const cveIdObj = await cveIdRepo.findOneByCveId(id) @@ -841,7 +841,7 @@ async function rejectExistingCve (req, res, next) { cve: id } const userRepo = req.ctx.repositories.getUserRepository() - payload.user_UUID = await authContext.getRequesterUserUUID(req, userRepo, orgRepo) + payload.user_UUID = await authContext.getRequesterUserUUID(req, userRepo, baseOrgRepo) logger.info(JSON.stringify(payload)) return res.status(200).json(responseMessage) } catch (err) { @@ -857,10 +857,10 @@ async function insertAdp (req, res, next) { const id = req.ctx.params.id const cveRepo = req.ctx.repositories.getCveRepository() const cveIdRepo = req.ctx.repositories.getCveIdRepository() - const orgRepo = req.ctx.repositories.getOrgRepository() + const baseOrgRepo = req.ctx.repositories.getBaseOrgRepository() const userRepo = req.ctx.repositories.getUserRepository() - const orgUuid = await authContext.getRequesterOrgUUID(req, orgRepo) - const userUuid = await authContext.getRequesterUserUUID(req, userRepo, orgRepo) + const orgUuid = await authContext.getRequesterOrgUUID(req, baseOrgRepo) + const userUuid = await authContext.getRequesterUserUUID(req, userRepo, baseOrgRepo) // check that cve id exists let result = await cveIdRepo.findOneByCveId(id) diff --git a/src/middleware/middleware.js b/src/middleware/middleware.js index b53d03b81..dbd4aa7a8 100644 --- a/src/middleware/middleware.js +++ b/src/middleware/middleware.js @@ -269,11 +269,11 @@ async function onlyCnas (req, res, next) { // Checks that the requester belongs to an org that has the 'ADP' role async function onlyAdps (req, res, next) { const shortName = req.ctx.org - const orgRepo = req.ctx.repositories.getOrgRepository() + const baseOrgRepo = req.ctx.repositories.getBaseOrgRepository() const CONSTANTS = getConstants() try { - const org = await authContext.getRequesterOrg(req, orgRepo) // org exists + const org = await authContext.getRequesterOrg(req, baseOrgRepo) // org exists if (!org) { logger.info({ uuid: req.ctx.uuid, message: shortName + ' is NOT an ' + CONSTANTS.AUTH_ROLE_ENUM.ADP }) return res.status(404).json(error.adpDoesNotExist(shortName)) @@ -330,9 +330,9 @@ function validateQueryParameterNames (queryParamNames, validNames) { async function cnaMustOwnID (req, res, next) { try { const requestingOrg = req.ctx.org - const orgRepo = req.ctx.repositories.getOrgRepository() - const isSecretariat = await authContext.isRequesterSecretariat(req, orgRepo) - const requestingOrgInfo = await authContext.getRequesterOrg(req, orgRepo) + const baseOrgRepo = req.ctx.repositories.getBaseOrgRepository() + const isSecretariat = await authContext.isRequesterSecretariat(req, baseOrgRepo) + const requestingOrgInfo = await authContext.getRequesterOrg(req, baseOrgRepo) const id = req.ctx.params.id const cveIdRepo = req.ctx.repositories.getCveIdRepository() diff --git a/test/unit-tests/cve/cveCnaContainerCreateTest.js b/test/unit-tests/cve/cveCnaContainerCreateTest.js index ebc484eb1..83b562cac 100644 --- a/test/unit-tests/cve/cveCnaContainerCreateTest.js +++ b/test/unit-tests/cve/cveCnaContainerCreateTest.js @@ -33,7 +33,7 @@ class MyOrg { return null } - async isSecretariat (org) { + async isSecretariatByShortName (org) { if (org === cveFixtures.secretariatHeader['CVE-API-ORG']) { return true } @@ -117,7 +117,7 @@ app.route('/cve-cna-negative-tests/:id') const factory = { getCveIdRepository: () => { return new MyCveIdNegativeTests() }, getCveRepository: () => { return new MyCveNegativeTests() }, - getOrgRepository: () => { return new MyOrg() }, + getBaseOrgRepository: () => { return new MyOrg() }, getUserRepository: () => { return new MyUser() } } req.ctx.repositories = factory @@ -129,7 +129,7 @@ app.route('/cve-cna-positive-tests/:id') const factory = { getCveIdRepository: () => { return new MyCveIdPositiveTests() }, getCveRepository: () => { return new MyCvePositiveTests() }, - getOrgRepository: () => { return new MyOrg() }, + getBaseOrgRepository: () => { return new MyOrg() }, getUserRepository: () => { return new MyUser() } } req.ctx.repositories = factory diff --git a/test/unit-tests/cve/cveCnaContainerUpdateTest.js b/test/unit-tests/cve/cveCnaContainerUpdateTest.js index 04dd04d26..8052b4ff7 100644 --- a/test/unit-tests/cve/cveCnaContainerUpdateTest.js +++ b/test/unit-tests/cve/cveCnaContainerUpdateTest.js @@ -34,7 +34,7 @@ class MyOrg { return null } - async isSecretariat (org) { + async isSecretariatByShortName (org) { if (org === cveFixtures.secretariatHeader['CVE-API-ORG']) { return true } @@ -128,7 +128,7 @@ app.route('/cve-cna-negative-tests/:id') const factory = { getCveIdRepository: () => { return new MyCveIdNegativeTests() }, getCveRepository: () => { return new MyCveNegativeTests() }, - getOrgRepository: () => { return new MyOrg() }, + getBaseOrgRepository: () => { return new MyOrg() }, getUserRepository: () => { return new MyUser() } } req.ctx.repositories = factory @@ -140,7 +140,7 @@ app.route('/cve-cna-positive-tests/:id') const factory = { getCveIdRepository: () => { return new MyCveIdPositiveTests() }, getCveRepository: () => { return new MyCvePositiveTests() }, - getOrgRepository: () => { return new MyOrg() }, + getBaseOrgRepository: () => { return new MyOrg() }, getUserRepository: () => { return new MyUser() } } req.ctx.repositories = factory diff --git a/test/unit-tests/cve/cveCreateTest.js b/test/unit-tests/cve/cveCreateTest.js index 16057e821..678bdc423 100644 --- a/test/unit-tests/cve/cveCreateTest.js +++ b/test/unit-tests/cve/cveCreateTest.js @@ -117,7 +117,7 @@ app.route('/cve-create-record-negative-tests/:id') const factory = { getCveRepository: () => { return new MyCveNegativeTests() }, getCveIdRepository: () => { return new MyCveIdNegativeTests() }, - getOrgRepository: () => { return new MyOrg() }, + getBaseOrgRepository: () => { return new MyOrg() }, getUserRepository: () => { return new MyUser() } } req.ctx.repositories = factory @@ -129,7 +129,7 @@ app.route('/cve-create-record-positive-tests/:id') const factory = { getCveRepository: () => { return new MyCvePositiveTests() }, getCveIdRepository: () => { return new MyCveIdPositiveTests() }, - getOrgRepository: () => { return new MyOrg() }, + getBaseOrgRepository: () => { return new MyOrg() }, getUserRepository: () => { return new MyUser() } } req.ctx.repositories = factory diff --git a/test/unit-tests/cve/cveRecordRejectionTest.js b/test/unit-tests/cve/cveRecordRejectionTest.js index a38444317..68be16d80 100644 --- a/test/unit-tests/cve/cveRecordRejectionTest.js +++ b/test/unit-tests/cve/cveRecordRejectionTest.js @@ -106,7 +106,7 @@ app.route('/cve-reject-negative-tests/:id') const factory = { getCveIdRepository: () => { return new MyCveIdNegativeTests() }, getCveRepository: () => { return new MyCveNegativeTests() }, - getOrgRepository: () => { return new MyOrg() }, + getBaseOrgRepository: () => { return new MyOrg() }, getUserRepository: () => { return new MyUser() } } req.ctx.repositories = factory @@ -118,7 +118,7 @@ app.route('/cve-reject-positive-tests/:id') const factory = { getCveIdRepository: () => { return new MyCveIdPositiveTests() }, getCveRepository: () => { return new MyCvePositiveTests() }, - getOrgRepository: () => { return new MyOrg() }, + getBaseOrgRepository: () => { return new MyOrg() }, getUserRepository: () => { return new MyUser() } } req.ctx.repositories = factory diff --git a/test/unit-tests/cve/cveUpdateTest.js b/test/unit-tests/cve/cveUpdateTest.js index 06f75adc0..4386f7887 100644 --- a/test/unit-tests/cve/cveUpdateTest.js +++ b/test/unit-tests/cve/cveUpdateTest.js @@ -62,7 +62,7 @@ describe('Testing the PUT /cve/:id endpoint in Cve Controller', () => { const factory = { getCveIdRepository: () => { return new CveIdRepo() }, getCveRepository: () => { return new CveRepo() }, - getOrgRepository: () => { return new OrgRepo() }, + getBaseOrgRepository: () => { return new OrgRepo() }, getUserRepository: () => { return new UserRepo() } } req.ctx.repositories = factory @@ -126,7 +126,7 @@ describe('Testing the PUT /cve/:id endpoint in Cve Controller', () => { const factory = { getCveIdRepository: () => { return new CveIdRepo() }, getCveRepository: () => { return new CveRepo() }, - getOrgRepository: () => { return new OrgRepo() }, + getBaseOrgRepository: () => { return new OrgRepo() }, getUserRepository: () => { return new UserRepo() } } req.ctx.repositories = factory @@ -191,7 +191,7 @@ describe('Testing the PUT /cve/:id endpoint in Cve Controller', () => { const factory = { getCveIdRepository: () => { return new CveIdRepo() }, getCveRepository: () => { return new CveRepo() }, - getOrgRepository: () => { return new OrgRepo() }, + getBaseOrgRepository: () => { return new OrgRepo() }, getUserRepository: () => { return new UserRepo() } } req.ctx.repositories = factory @@ -242,7 +242,7 @@ describe('Testing the PUT /cve/:id endpoint in Cve Controller', () => { const factory = { getCveIdRepository: () => { return new CveIdRepo() }, getCveRepository: () => { return new CveRepo() }, - getOrgRepository: () => { return new OrgRepo() }, + getBaseOrgRepository: () => { return new OrgRepo() }, getUserRepository: () => { return new UserRepo() } } req.ctx.repositories = factory @@ -308,7 +308,7 @@ describe('Testing the PUT /cve/:id endpoint in Cve Controller', () => { const factory = { getCveIdRepository: () => { return new CveIdRepo() }, getCveRepository: () => { return new CveRepo() }, - getOrgRepository: () => { return new OrgRepo() }, + getBaseOrgRepository: () => { return new OrgRepo() }, getUserRepository: () => { return new UserRepo() } } req.ctx.repositories = factory @@ -367,7 +367,7 @@ describe('Testing the PUT /cve/:id endpoint in Cve Controller', () => { const factory = { getCveIdRepository: () => { return new CveIdRepo() }, getCveRepository: () => { return new CveRepo() }, - getOrgRepository: () => { return new OrgRepo() }, + getBaseOrgRepository: () => { return new OrgRepo() }, getUserRepository: () => { return new UserRepo() } } req.ctx.repositories = factory diff --git a/test/unit-tests/cve/insertAdpTest.js b/test/unit-tests/cve/insertAdpTest.js index fd654a247..0b351a4e0 100644 --- a/test/unit-tests/cve/insertAdpTest.js +++ b/test/unit-tests/cve/insertAdpTest.js @@ -11,7 +11,7 @@ const errors = require('../../../src/controller/cve.controller/error.js') const error = new errors.CveControllerError() const constants = require('../../../src/constants').getConstants() -const OrgRepository = require('../../../src/repositories/orgRepository.js') +const BaseOrgRepository = require('../../../src/repositories/baseOrgRepository.js') const CveIdRepository = require('../../../src/repositories/cveIdRepository.js') const CveRepository = require('../../../src/repositories/cveRepository.js') const UserRepository = require('../../../src/repositories/userRepository.js') @@ -48,7 +48,7 @@ const stubCveId = { } describe('Testing insertAdp function', () => { - let status, json, res, next, getOrgRepository, + let status, json, res, next, getBaseOrgRepository, orgRepo, getCveRepository, cveRepo, getCveIdRepository, cveIdRepo, getUserRepository, userRepo, adpContainerCopy, cveCopy, req @@ -60,9 +60,9 @@ describe('Testing insertAdp function', () => { res = { json, status } next = sinon.spy() status.returns(res) - orgRepo = new OrgRepository() - getOrgRepository = sinon.stub() - getOrgRepository.returns(orgRepo) + orgRepo = new BaseOrgRepository() + getBaseOrgRepository = sinon.stub() + getBaseOrgRepository.returns(orgRepo) userRepo = new UserRepository() getUserRepository = sinon.stub() @@ -94,7 +94,7 @@ describe('Testing insertAdp function', () => { id: cveIdPublished5 }, repositories: { - getOrgRepository, + getBaseOrgRepository, getUserRepository, getCveRepository, getCveIdRepository diff --git a/test/unit-tests/cve/updateCnaTest.js b/test/unit-tests/cve/updateCnaTest.js index b72cb009e..1a718cc3d 100644 --- a/test/unit-tests/cve/updateCnaTest.js +++ b/test/unit-tests/cve/updateCnaTest.js @@ -56,7 +56,7 @@ describe('updateCna function', () => { let getCveRepository let getCveIdRepository let getUserRepository - let getOrgRepository + let getBaseOrgRepository let cveCopy let cnaContainerCopy @@ -70,11 +70,11 @@ describe('updateCna function', () => { cnaContainerCopy = _.cloneDeep(cnaContainer) sinon.stub(Cve, 'validateCveRecord').returns({ isValid: true }) - orgRepo = { getOrgUUID: sinon.stub(), isSecretariat: sinon.stub() } + orgRepo = { getOrgUUID: sinon.stub(), isSecretariatByShortName: sinon.stub() } orgRepo.getOrgUUID.returns(stubCnaOrg.UUID) - orgRepo.isSecretariat.returns(false) - getOrgRepository = sinon.stub() - getOrgRepository.returns(orgRepo) + orgRepo.isSecretariatByShortName.returns(false) + getBaseOrgRepository = sinon.stub() + getBaseOrgRepository.returns(orgRepo) userRepo = { getUserUUID: sinon.stub() } userRepo.getUserUUID.returns(stubCnaUser.UUID) @@ -100,7 +100,7 @@ describe('updateCna function', () => { id: cveIdPublished5 }, repositories: { - getOrgRepository, + getBaseOrgRepository, getUserRepository, getCveRepository, getCveIdRepository diff --git a/test/unit-tests/middleware/cnaMustOwnIdTestMiddlewareTest.js b/test/unit-tests/middleware/cnaMustOwnIdTestMiddlewareTest.js index 73eeb6e08..3055e2073 100644 --- a/test/unit-tests/middleware/cnaMustOwnIdTestMiddlewareTest.js +++ b/test/unit-tests/middleware/cnaMustOwnIdTestMiddlewareTest.js @@ -23,7 +23,7 @@ describe('Test cna must own Id middleware', () => { return mwCnaFixtures.owningOrg } - async isSecretariat () { + async isSecretariatByShortName () { return false } } @@ -36,7 +36,7 @@ describe('Test cna must own Id middleware', () => { app.route('/requester-owns-cveid-and-is-a-cna') .post((req, res, next) => { const factory = { - getOrgRepository: () => { return new RequesterOrg() }, + getBaseOrgRepository: () => { return new RequesterOrg() }, getCveIdRepository: () => { return new RequesterCveId() } } req.ctx.repositories = factory @@ -70,7 +70,7 @@ describe('Test cna must own Id middleware', () => { return mwCnaFixtures.owningOrg } - async isSecretariat () { + async isSecretariatByShortName () { return false } } @@ -83,7 +83,7 @@ describe('Test cna must own Id middleware', () => { app.route('/requester-does-not-own-cve-id') .post((req, res, next) => { const factory = { - getOrgRepository: () => { return new RequesterOrg() }, + getBaseOrgRepository: () => { return new RequesterOrg() }, getCveIdRepository: () => { return new RequesterCveId() } } req.ctx.repositories = factory @@ -116,7 +116,7 @@ describe('Test cna must own Id middleware', () => { return mwCnaFixtures.owningOrg } - async isSecretariat () { + async isSecretariatByShortName () { return false } } @@ -129,7 +129,7 @@ describe('Test cna must own Id middleware', () => { app.route('/requester-did-not-provide-a-cve-id') .post((req, res, next) => { const factory = { - getOrgRepository: () => { return new RequesterOrg() }, + getBaseOrgRepository: () => { return new RequesterOrg() }, getCveIdRepository: () => { return new RequesterCveId() } } req.ctx.repositories = factory diff --git a/test/unit-tests/middleware/onlyAdpsTest.js b/test/unit-tests/middleware/onlyAdpsTest.js index 8b2cd30d4..d4b4e6e98 100644 --- a/test/unit-tests/middleware/onlyAdpsTest.js +++ b/test/unit-tests/middleware/onlyAdpsTest.js @@ -5,7 +5,7 @@ const sinon = require('sinon') const { faker } = require('@faker-js/faker') const expect = chai.expect -const OrgRepository = require('../../../src/repositories/orgRepository.js') +const BaseOrgRepository = require('../../../src/repositories/baseOrgRepository.js') const { onlyAdps } = require('../../../src/middleware/middleware.js') const errors = require('../../../src/middleware/error.js') const error = new errors.MiddlewareError() @@ -14,46 +14,34 @@ const stubAdpOrg = { short_name: 'adpOrg', name: 'test_adp', UUID: faker.datatype.uuid(), - authority: { - active_roles: [ - 'ADP' - ] - } + authority: ['ADP'] } const stubCnaOrg = { short_name: 'cnaOrg', name: 'test_cna', UUID: faker.datatype.uuid(), - authority: { - active_roles: [ - 'CNA' - ] - } + authority: ['CNA'] } const stubSecretariat = { short_name: 'secOrg', name: 'test_sec', UUID: faker.datatype.uuid(), - authority: { - active_roles: [ - 'SECRETARIAT' - ] - } + authority: ['SECRETARIAT'] } describe('Testing onlyAdps middleware', () => { - let status, json, res, next, getOrgRepository, orgRepo + let status, json, res, next, getBaseOrgRepository, orgRepo beforeEach(() => { status = sinon.stub() json = sinon.spy() res = { json, status } next = sinon.spy() status.returns(res) - orgRepo = new OrgRepository() - getOrgRepository = sinon.stub() - getOrgRepository.returns(orgRepo) + orgRepo = new BaseOrgRepository() + getBaseOrgRepository = sinon.stub() + getBaseOrgRepository.returns(orgRepo) }) context('Negative Tests', () => { it('Should return 403 for users from orgs without the ADP role ', async () => { @@ -62,7 +50,7 @@ describe('Testing onlyAdps middleware', () => { org: stubCnaOrg.short_name, uuid: stubCnaOrg.UUID, repositories: { - getOrgRepository + getBaseOrgRepository } } } @@ -81,7 +69,7 @@ describe('Testing onlyAdps middleware', () => { org: stubCnaOrg.short_name, uuid: stubCnaOrg.UUID, repositories: { - getOrgRepository + getBaseOrgRepository } } } @@ -102,7 +90,7 @@ describe('Testing onlyAdps middleware', () => { org: stubAdpOrg.short_name, uuid: stubAdpOrg.UUID, repositories: { - getOrgRepository + getBaseOrgRepository } } } @@ -119,7 +107,7 @@ describe('Testing onlyAdps middleware', () => { org: stubSecretariat.short_name, uuid: stubSecretariat.UUID, repositories: { - getOrgRepository + getBaseOrgRepository } } } From f79e4af43dded261a5f456af2547e2fe3d06feeb Mon Sep 17 00:00:00 2001 From: James Dalphond Date: Tue, 18 Aug 2026 14:35:35 -0400 Subject: [PATCH 10/20] Move CVE-ID reservation locking and quota checks to BaseOrg --- .../cve-id.controller/cve-id.controller.js | 15 +++---- .../cve-id/reserveCveIdTest.js | 37 +++++++++++++++++ test/unit-tests/cve-id/cveIdUpdateTest.js | 2 +- test/unit-tests/cve-id/mockObjects.cve-id.js | 40 ++++++------------- .../mockObjects.non-sequential.js | 30 +++++--------- .../reserveCveIdTest.non-sequential.js | 23 +++++------ ...eserveCveIdTest.usersA_B.non-sequential.js | 25 +++++------- .../cveIdReserveGeneralLogicTest.js | 28 ++++++------- .../reserveCveId/cveIdReservePriorityTest.js | 38 +++++++++--------- .../cveIdReserveSequentialTest.js | 27 ++++++------- 10 files changed, 134 insertions(+), 131 deletions(-) diff --git a/src/controller/cve-id.controller/cve-id.controller.js b/src/controller/cve-id.controller/cve-id.controller.js index e1195d49f..ed19762fb 100644 --- a/src/controller/cve-id.controller/cve-id.controller.js +++ b/src/controller/cve-id.controller/cve-id.controller.js @@ -201,7 +201,7 @@ async function reserveCveId (req, res, next) { let amount let shortName let year - const orgRepo = req.ctx.repositories.getOrgRepository() + const baseOrgRepo = req.ctx.repositories.getBaseOrgRepository() try { Object.keys(req.ctx.query).forEach(k => { @@ -218,8 +218,8 @@ async function reserveCveId (req, res, next) { } }) - const isSecretariat = await authContext.isRequesterSecretariat(req, orgRepo) - const requesterSameOrg = await authContext.isRequesterSameOrg(req, orgRepo, shortName) + const isSecretariat = await authContext.isRequesterSecretariat(req, baseOrgRepo) + const requesterSameOrg = await authContext.isRequesterSameOrg(req, baseOrgRepo, shortName) if (!requesterSameOrg && !isSecretariat) { return res.status(403).json(error.orgCannotReserveForOther()) } @@ -244,7 +244,7 @@ async function reserveCveId (req, res, next) { return res.status(400).json(error.noBatchType()) } - const result = await orgRepo.findOneByShortName(shortName) + const result = await baseOrgRepo.findOneByShortName(shortName) if (!result) { logger.info({ uuid: req.ctx.uuid, message: shortName + ' organization does not exist.' }) return res.status(403).json(error.orgDne(shortName, 'short_name', 'query')) @@ -255,7 +255,8 @@ async function reserveCveId (req, res, next) { return res.status(403).json(error.overIdQuota(payload)) } - hasLock = await orgRepo.findOneAndUpdate({ short_name: shortName, inUse: false }, { $set: { inUse: true } }, { new: true }) // set lock for org + // Treat an absent lock field as unlocked so existing BaseOrg records can acquire the lock. + hasLock = await baseOrgRepo.findOneAndUpdate({ short_name: shortName, in_use: { $ne: true } }, { $set: { in_use: true } }, { new: true }) // set lock for org if (!hasLock) { return res.status(403).json(error.reservationInProgress()) } @@ -276,7 +277,7 @@ async function reserveCveId (req, res, next) { next(err) } finally { if (shortName && hasLock) { - await orgRepo.findOneAndUpdate({ short_name: shortName, inUse: true }, { $set: { inUse: false } }, { new: true }) // release lock for org + await baseOrgRepo.findOneAndUpdate({ short_name: shortName, in_use: true }, { $set: { in_use: false } }, { new: true }) // release lock for org } } } @@ -883,7 +884,7 @@ async function reserveNonSequentialCveId (index, available, year, shortName, org async function getPayload (req, org) { const payload = { - id_quota: org.policies.id_quota + id_quota: org.id_quota } const cveIdRepo = req.ctx.repositories.getCveIdRepository() diff --git a/test/integration-tests/cve-id/reserveCveIdTest.js b/test/integration-tests/cve-id/reserveCveIdTest.js index 42e8f58d6..415fb7323 100644 --- a/test/integration-tests/cve-id/reserveCveIdTest.js +++ b/test/integration-tests/cve-id/reserveCveIdTest.js @@ -7,8 +7,10 @@ const expect = chai.expect const constants = require('../constants.js') const app = require('../../../src/index.js') +const BaseOrg = require('../../../src/model/baseorg') const requestLength = 10 +const lockedOrgShortName = 'cve_id_reservation_lock_test' describe('Testing Reserve CVE-ID Endpoints', () => { // beforeEach(() => { }) @@ -47,6 +49,41 @@ describe('Testing Reserve CVE-ID Endpoints', () => { }) }) context('Negative Tests', () => { + it('Should return 403 when the target BaseOrg reservation lock is held', async () => { + const testOrg = { + short_name: lockedOrgShortName, + long_name: 'CVE ID Reservation Lock Test Organization', + authority: ['CNA'], + id_quota: 10 + } + + await chai.request(app) + .post('/api/registry/org') + .set(constants.headers) + .send(testOrg) + .then(res => { + expect(res).to.have.status(200) + }) + + try { + await BaseOrg.findOneAndUpdate( + { short_name: lockedOrgShortName }, + { $set: { in_use: true } } + ) + + await chai.request(app) + .post(`/api/cve-id?amount=1&cve_year=2023&short_name=${lockedOrgShortName}`) + .set(constants.headers) + .then(res => { + expect(res).to.have.status(403) + }) + } finally { + await chai.request(app) + .delete(`/api/registry/org/${lockedOrgShortName}`) + .set(constants.headers) + } + }) + it('Should return 403 when a request is by a user to reserve cve-ids outside of their organization ', (done) => { chai.request(app) .post('/api/cve-id?amount=10&cve_year=2023&short_name=mitre&batch_type=sequential') diff --git a/test/unit-tests/cve-id/cveIdUpdateTest.js b/test/unit-tests/cve-id/cveIdUpdateTest.js index 1d0c7f79c..bf67a772b 100644 --- a/test/unit-tests/cve-id/cveIdUpdateTest.js +++ b/test/unit-tests/cve-id/cveIdUpdateTest.js @@ -21,7 +21,7 @@ const cveIdParams = require('../../../src/controller/cve-id.controller/cve-id.mi const baseSecretariatOrg = { UUID: cveIdFixtures.secretariatOrg.UUID, authority: ['CNA', 'SECRETARIAT'], - id_quota: cveIdFixtures.secretariatOrg.policies.id_quota, + id_quota: cveIdFixtures.secretariatOrg.id_quota, short_name: cveIdFixtures.secretariatOrg.short_name } diff --git a/test/unit-tests/cve-id/mockObjects.cve-id.js b/test/unit-tests/cve-id/mockObjects.cve-id.js index 7c81f6f23..f212b76f4 100644 --- a/test/unit-tests/cve-id/mockObjects.cve-id.js +++ b/test/unit-tests/cve-id/mockObjects.cve-id.js @@ -26,15 +26,11 @@ const orgHeader = { const secretariatOrg = { UUID: '15fd129f-af00-4d8c-8f7b-e19b0587223f', - authority: { - active_roles: [CONSTANTS.AUTH_ROLE_ENUM.CNA, CONSTANTS.AUTH_ROLE_ENUM.SECRETARIAT] - }, + authority: [CONSTANTS.AUTH_ROLE_ENUM.CNA, CONSTANTS.AUTH_ROLE_ENUM.SECRETARIAT], + id_quota: 1000, name: 'The MITRE Corporation', - policies: { - id_quota: 1000 - }, short_name: 'mitre', - inUse: false + in_use: false } const secretariatUser = { @@ -54,15 +50,11 @@ const secretariatUser = { const owningOrg = { UUID: '88c02595-c8f7-4864-a0e7-e09b3e1da691', - authority: { - active_roles: [CONSTANTS.AUTH_ROLE_ENUM.CNA] - }, + authority: [CONSTANTS.AUTH_ROLE_ENUM.CNA], + id_quota: 1000, name: 'Cisco', - policies: { - id_quota: 1000 - }, short_name: 'cisco', - inUse: false + in_use: false } const owningOrgUser = { @@ -83,15 +75,11 @@ const owningOrgUser = { const org = { UUID: 'c0a1d017-faa5-4608-92ff-5c938da17eff', - authority: { - active_roles: [CONSTANTS.AUTH_ROLE_ENUM.CNA] - }, + authority: [CONSTANTS.AUTH_ROLE_ENUM.CNA], + id_quota: 500, name: 'Siemens', - policies: { - id_quota: 500 - }, short_name: 'siemens', - inUse: false + in_use: false } const orgUser = { @@ -112,15 +100,11 @@ const orgUser = { const nonExistentOrg = { UUID: '794715e2-ded9-4a16-8dfd-9a062a63e1d5', - authority: { - active_roles: [CONSTANTS.AUTH_ROLE_ENUM.CNA] - }, + authority: [CONSTANTS.AUTH_ROLE_ENUM.CNA], + id_quota: 5, name: 'The Oval Office', - policies: { - id_quota: 5 - }, short_name: 'oval', - inUse: false + in_use: false } const cveReserved = { diff --git a/test/unit-tests/cve-id/reserveCveId.non-sequential/mockObjects.non-sequential.js b/test/unit-tests/cve-id/reserveCveId.non-sequential/mockObjects.non-sequential.js index 7fe1279fb..3d3472264 100644 --- a/test/unit-tests/cve-id/reserveCveId.non-sequential/mockObjects.non-sequential.js +++ b/test/unit-tests/cve-id/reserveCveId.non-sequential/mockObjects.non-sequential.js @@ -25,15 +25,11 @@ const userBHeader = { const secretariatOrg = { UUID: '15fd129f-af00-4d8c-8f7b-e19b0587223f', - authority: { - active_roles: [CONSTANTS.AUTH_ROLE_ENUM.CNA, CONSTANTS.AUTH_ROLE_ENUM.SECRETARIAT] - }, + authority: [CONSTANTS.AUTH_ROLE_ENUM.CNA, CONSTANTS.AUTH_ROLE_ENUM.SECRETARIAT], + id_quota: 1000, name: 'The MITRE Corporation', - policies: { - id_quota: 1000 - }, short_name: 'mitre', - inUse: false + in_use: false } const secretariatUser = { @@ -53,15 +49,11 @@ const secretariatUser = { const orgA = { UUID: '88c02595-c8f7-4864-a0e7-e09b3e1da691', - authority: { - active_roles: [CONSTANTS.AUTH_ROLE_ENUM.CNA] - }, + authority: [CONSTANTS.AUTH_ROLE_ENUM.CNA], + id_quota: 500, name: 'Cisco', - policies: { - id_quota: 500 - }, short_name: 'cisco', - inUse: false + in_use: false } const userA = { @@ -82,15 +74,11 @@ const userA = { const orgB = { UUID: 'c0a1d017-faa5-4608-92ff-5c938da17eff', - authority: { - active_roles: [CONSTANTS.AUTH_ROLE_ENUM.CNA] - }, + authority: [CONSTANTS.AUTH_ROLE_ENUM.CNA], + id_quota: 500, name: 'Siemens', - policies: { - id_quota: 500 - }, short_name: 'siemens', - inUse: false + in_use: false } const userB = { diff --git a/test/unit-tests/cve-id/reserveCveId.non-sequential/reserveCveIdTest.non-sequential.js b/test/unit-tests/cve-id/reserveCveId.non-sequential/reserveCveIdTest.non-sequential.js index 98e33c19e..31042699e 100644 --- a/test/unit-tests/cve-id/reserveCveId.non-sequential/reserveCveIdTest.non-sequential.js +++ b/test/unit-tests/cve-id/reserveCveId.non-sequential/reserveCveIdTest.non-sequential.js @@ -152,11 +152,11 @@ describe('Testing the non sequential reservation (Base Case) of POST /cve-id end } async findOneAndUpdate (query) { - if (query.short_name === cveIdNonSeqFixtures.orgA.short_name && cveIdNonSeqFixtures.orgA.inUse === false) { - cveIdNonSeqFixtures.orgA.inUse = true + if (query.short_name === cveIdNonSeqFixtures.orgA.short_name && cveIdNonSeqFixtures.orgA.in_use === false) { + cveIdNonSeqFixtures.orgA.in_use = true return cveIdNonSeqFixtures.orgA - } else if (query.short_name === cveIdNonSeqFixtures.orgA.short_name && cveIdNonSeqFixtures.orgA.inUse === true) { - cveIdNonSeqFixtures.orgA.inUse = false + } else if (query.short_name === cveIdNonSeqFixtures.orgA.short_name && cveIdNonSeqFixtures.orgA.in_use === true) { + cveIdNonSeqFixtures.orgA.in_use = false return null } } @@ -168,7 +168,7 @@ describe('Testing the non sequential reservation (Base Case) of POST /cve-id end getCveIdRepository: () => { return new CveIdReserveNonSequentialIsFull() }, getCveIdRangeRepository: () => { return new CveIdRangeReserveYearDoesntExist() }, getUserRepository: () => { return new NullUserRepo() }, - getOrgRepository: () => { return new OrgReserveNonSequentialYearDoesntExist() } + getBaseOrgRepository: () => { return new OrgReserveNonSequentialYearDoesntExist() } } req.ctx.repositories = factory setOrgAUUIDContext(req) @@ -183,7 +183,7 @@ describe('Testing the non sequential reservation (Base Case) of POST /cve-id end done(err) } - const quotaHeader = cveIdNonSeqFixtures.orgA.policies.id_quota.toString() + const quotaHeader = cveIdNonSeqFixtures.orgA.id_quota.toString() expect(res.header).to.have.property('cve-api-remaining-quota').and.to.equal(quotaHeader) expect(res).to.have.status(403) @@ -214,11 +214,11 @@ describe('Testing the non sequential reservation (Base Case) of POST /cve-id end } async findOneAndUpdate (query) { - if (query.short_name === cveIdNonSeqFixtures.orgA.short_name && cveIdNonSeqFixtures.orgA.inUse === false) { - cveIdNonSeqFixtures.orgA.inUse = true + if (query.short_name === cveIdNonSeqFixtures.orgA.short_name && cveIdNonSeqFixtures.orgA.in_use === false) { + cveIdNonSeqFixtures.orgA.in_use = true return cveIdNonSeqFixtures.orgA - } else if (query.short_name === cveIdNonSeqFixtures.orgA.short_name && cveIdNonSeqFixtures.orgA.inUse === true) { - cveIdNonSeqFixtures.orgA.inUse = false + } else if (query.short_name === cveIdNonSeqFixtures.orgA.short_name && cveIdNonSeqFixtures.orgA.in_use === true) { + cveIdNonSeqFixtures.orgA.in_use = false return null } } @@ -239,7 +239,6 @@ describe('Testing the non sequential reservation (Base Case) of POST /cve-id end getCveIdRepository: () => { return cveIdRepo }, getCveIdRangeRepository: () => { return cveIdRangeRepo }, getUserRepository: () => { return userRepo }, - getOrgRepository: () => { return orgRepo }, getBaseOrgRepository: () => { return orgRepo } } req.ctx.repositories = factory @@ -255,7 +254,7 @@ describe('Testing the non sequential reservation (Base Case) of POST /cve-id end done(err) } - const quotaHeader = (cveIdNonSeqFixtures.orgA.policies.id_quota - 10).toString() + const quotaHeader = (cveIdNonSeqFixtures.orgA.id_quota - 10).toString() expect(res.header).to.have.property('cve-api-remaining-quota').and.to.equal(quotaHeader) expect(res).to.have.status(200) diff --git a/test/unit-tests/cve-id/reserveCveId.non-sequential/reserveCveIdTest.usersA_B.non-sequential.js b/test/unit-tests/cve-id/reserveCveId.non-sequential/reserveCveIdTest.usersA_B.non-sequential.js index 9847bef5d..f87e3662f 100644 --- a/test/unit-tests/cve-id/reserveCveId.non-sequential/reserveCveIdTest.usersA_B.non-sequential.js +++ b/test/unit-tests/cve-id/reserveCveId.non-sequential/reserveCveIdTest.usersA_B.non-sequential.js @@ -232,17 +232,17 @@ class OrgReserveNonSequentialSuccessCaseAB { } async findOneAndUpdate (query) { - if (query.short_name === cveIdNonSeqFixtures.orgA.short_name && cveIdNonSeqFixtures.orgA.inUse === false) { - cveIdNonSeqFixtures.orgA.inUse = true + if (query.short_name === cveIdNonSeqFixtures.orgA.short_name && cveIdNonSeqFixtures.orgA.in_use === false) { + cveIdNonSeqFixtures.orgA.in_use = true return cveIdNonSeqFixtures.orgA - } else if (query.short_name === cveIdNonSeqFixtures.orgA.short_name && cveIdNonSeqFixtures.orgA.inUse === true) { - cveIdNonSeqFixtures.orgA.inUse = false + } else if (query.short_name === cveIdNonSeqFixtures.orgA.short_name && cveIdNonSeqFixtures.orgA.in_use === true) { + cveIdNonSeqFixtures.orgA.in_use = false return null - } else if (query.short_name === cveIdNonSeqFixtures.orgB.short_name && cveIdNonSeqFixtures.orgB.inUse === false) { - cveIdNonSeqFixtures.orgB.inUse = true + } else if (query.short_name === cveIdNonSeqFixtures.orgB.short_name && cveIdNonSeqFixtures.orgB.in_use === false) { + cveIdNonSeqFixtures.orgB.in_use = true return cveIdNonSeqFixtures.orgB - } else if (query.short_name === cveIdNonSeqFixtures.orgB.short_name && cveIdNonSeqFixtures.orgB.inUse === true) { - cveIdNonSeqFixtures.orgB.inUse = false + } else if (query.short_name === cveIdNonSeqFixtures.orgB.short_name && cveIdNonSeqFixtures.orgB.in_use === true) { + cveIdNonSeqFixtures.orgB.in_use = false return null } } @@ -282,7 +282,6 @@ describe('Testing the non sequential reservation (Case AB) of POST /cve-id endpo getCveIdRepository: () => { return cveIdRepo }, getCveIdRangeRepository: () => { return cveIdRangeRepo }, getUserRepository: () => { return userRepo }, - getOrgRepository: () => { return orgRepo }, getBaseOrgRepository: () => { return orgRepo } } req.ctx.repositories = factory @@ -298,7 +297,7 @@ describe('Testing the non sequential reservation (Case AB) of POST /cve-id endpo done(err) } - const quotaHeader = (cveIdNonSeqFixtures.orgA.policies.id_quota - 10).toString() + const quotaHeader = (cveIdNonSeqFixtures.orgA.id_quota - 10).toString() expect(res.header).to.have.property('cve-api-remaining-quota').and.to.equal(quotaHeader) expect(res).to.have.status(200) @@ -383,7 +382,6 @@ describe('Testing the non sequential reservation (Case AB) of POST /cve-id endpo getCveIdRepository: () => { return new CveIdReservePoolIncremented10IdsCaseAB2() }, getCveIdRangeRepository: () => { return new CveIdRangeReserveNonSequentialSuccessCaseAB2() }, getUserRepository: () => { return new UserReserveNonSequentialSuccessCaseAB() }, - getOrgRepository: () => { return new OrgReserveNonSequentialSuccessCaseAB() }, getBaseOrgRepository: () => { return new OrgReserveNonSequentialSuccessCaseAB() } } req.ctx.repositories = factory @@ -399,7 +397,7 @@ describe('Testing the non sequential reservation (Case AB) of POST /cve-id endpo done(err) } - const quotaHeader = (cveIdNonSeqFixtures.orgB.policies.id_quota - 10).toString() + const quotaHeader = (cveIdNonSeqFixtures.orgB.id_quota - 10).toString() expect(res.header).to.have.property('cve-api-remaining-quota').and.to.equal(quotaHeader) expect(res).to.have.status(200) @@ -590,7 +588,6 @@ describe('Testing the non sequential reservation (Case AB) of POST /cve-id endpo getCveIdRepository: () => { return new CveIdReservePoolIncremented10IdsCaseAB3() }, getCveIdRangeRepository: () => { return new CveIdRangeReserveNonSequentialSuccessCaseAB3() }, getUserRepository: () => { return new UserReserveNonSequentialSuccessCaseAB() }, - getOrgRepository: () => { return new OrgReserveNonSequentialSuccessCaseAB() }, getBaseOrgRepository: () => { return new OrgReserveNonSequentialSuccessCaseAB() } } req.ctx.repositories = factory @@ -606,7 +603,7 @@ describe('Testing the non sequential reservation (Case AB) of POST /cve-id endpo done(err) } - const quotaHeader = (cveIdNonSeqFixtures.orgA.policies.id_quota - 10).toString() + const quotaHeader = (cveIdNonSeqFixtures.orgA.id_quota - 10).toString() expect(res.header).to.have.property('cve-api-remaining-quota').and.to.equal(quotaHeader) expect(res).to.have.status(403) diff --git a/test/unit-tests/cve-id/reserveCveId/cveIdReserveGeneralLogicTest.js b/test/unit-tests/cve-id/reserveCveId/cveIdReserveGeneralLogicTest.js index bbc1d27fb..ef2a998a7 100644 --- a/test/unit-tests/cve-id/reserveCveId/cveIdReserveGeneralLogicTest.js +++ b/test/unit-tests/cve-id/reserveCveId/cveIdReserveGeneralLogicTest.js @@ -55,11 +55,11 @@ class OrgReserveAmountLargerThanNonSequential { } async findOneAndUpdate (query) { - if (query.short_name === cveIdFixtures.org.short_name && cveIdFixtures.org.inUse === false) { - cveIdFixtures.org.inUse = true + if (query.short_name === cveIdFixtures.org.short_name && cveIdFixtures.org.in_use === false) { + cveIdFixtures.org.in_use = true return cveIdFixtures.org - } else if (query.short_name === cveIdFixtures.org.short_name && cveIdFixtures.org.inUse === true) { - cveIdFixtures.org.inUse = false + } else if (query.short_name === cveIdFixtures.org.short_name && cveIdFixtures.org.in_use === true) { + cveIdFixtures.org.in_use = false return null } } @@ -79,7 +79,7 @@ describe('Testing the general logic of POST /cve-id endpoint in CveId Controller const factory = { getCveIdRepository: () => { return new NullCveIdRepo() }, getUserRepository: () => { return new NullUserRepo() }, - getOrgRepository: () => { return new OrgReserveNotOwningOrg() } + getBaseOrgRepository: () => { return new OrgReserveNotOwningOrg() } } req.ctx.repositories = factory next() @@ -108,7 +108,7 @@ describe('Testing the general logic of POST /cve-id endpoint in CveId Controller const factory = { getCveIdRepository: () => { return new NullCveIdRepo() }, getUserRepository: () => { return new NullUserRepo() }, - getOrgRepository: () => { return new OrgReserveShortNameUndefined() } + getBaseOrgRepository: () => { return new OrgReserveShortNameUndefined() } } req.ctx.repositories = factory next() @@ -137,7 +137,7 @@ describe('Testing the general logic of POST /cve-id endpoint in CveId Controller const factory = { getCveIdRepository: () => { return new NullCveIdRepo() }, getUserRepository: () => { return new NullUserRepo() }, - getOrgRepository: () => { return new OrgReserveShortNameUndefined() } + getBaseOrgRepository: () => { return new OrgReserveShortNameUndefined() } } req.ctx.repositories = factory next() @@ -166,7 +166,7 @@ describe('Testing the general logic of POST /cve-id endpoint in CveId Controller const factory = { getCveIdRepository: () => { return new NullCveIdRepo() }, getUserRepository: () => { return new NullUserRepo() }, - getOrgRepository: () => { return new OrgReserveShortNameUndefined() } + getBaseOrgRepository: () => { return new OrgReserveShortNameUndefined() } } req.ctx.repositories = factory next() @@ -195,7 +195,7 @@ describe('Testing the general logic of POST /cve-id endpoint in CveId Controller const factory = { getCveIdRepository: () => { return new NullCveIdRepo() }, getUserRepository: () => { return new NullUserRepo() }, - getOrgRepository: () => { return new OrgReserveShortNameUndefined() } + getBaseOrgRepository: () => { return new OrgReserveShortNameUndefined() } } req.ctx.repositories = factory next() @@ -224,7 +224,7 @@ describe('Testing the general logic of POST /cve-id endpoint in CveId Controller const factory = { getCveIdRepository: () => { return new NullCveIdRepo() }, getUserRepository: () => { return new NullUserRepo() }, - getOrgRepository: () => { return new OrgReserveShortNameUndefined() } + getBaseOrgRepository: () => { return new OrgReserveShortNameUndefined() } } req.ctx.repositories = factory next() @@ -263,7 +263,7 @@ describe('Testing the general logic of POST /cve-id endpoint in CveId Controller const factory = { getCveIdRepository: () => { return new NullCveIdRepo() }, getUserRepository: () => { return new NullUserRepo() }, - getOrgRepository: () => { return new OrgReserveOrgDoesntExist() } + getBaseOrgRepository: () => { return new OrgReserveOrgDoesntExist() } } req.ctx.repositories = factory next() @@ -294,7 +294,7 @@ describe('Testing the general logic of POST /cve-id endpoint in CveId Controller const factory = { getCveIdRepository: () => { return new NullCveIdRepo() }, getUserRepository: () => { return new NullUserRepo() }, - getOrgRepository: () => { return new OrgReserveAmountLargerThanNonSequential() } + getBaseOrgRepository: () => { return new OrgReserveAmountLargerThanNonSequential() } } req.ctx.repositories = factory next() @@ -333,7 +333,7 @@ describe('Testing the general logic of POST /cve-id endpoint in CveId Controller const factory = { getCveIdRepository: () => { return new NullCveIdRepo() }, getUserRepository: () => { return new NullUserRepo() }, - getOrgRepository: () => { return new OrgReserveAmountGreaterIdQuota() } + getBaseOrgRepository: () => { return new OrgReserveAmountGreaterIdQuota() } } req.ctx.repositories = factory next() @@ -363,7 +363,7 @@ describe('Testing the general logic of POST /cve-id endpoint in CveId Controller const factory = { getCveIdRepository: () => { return new NullCveIdRepo() }, getUserRepository: () => { return new NullUserRepo() }, - getOrgRepository: () => { return new OrgReserveAmountLargerThanNonSequential() } + getBaseOrgRepository: () => { return new OrgReserveAmountLargerThanNonSequential() } } req.ctx.repositories = factory next() diff --git a/test/unit-tests/cve-id/reserveCveId/cveIdReservePriorityTest.js b/test/unit-tests/cve-id/reserveCveId/cveIdReservePriorityTest.js index 2076dfac6..b9699f405 100644 --- a/test/unit-tests/cve-id/reserveCveId/cveIdReservePriorityTest.js +++ b/test/unit-tests/cve-id/reserveCveId/cveIdReservePriorityTest.js @@ -64,11 +64,11 @@ class OrgReserveSequentialPriorityIsFull { } async findOneAndUpdate (query) { - if (query.short_name === cveIdFixtures.owningOrg.short_name && cveIdFixtures.owningOrg.inUse === false) { - cveIdFixtures.owningOrg.inUse = true + if (query.short_name === cveIdFixtures.owningOrg.short_name && cveIdFixtures.owningOrg.in_use === false) { + cveIdFixtures.owningOrg.in_use = true return cveIdFixtures.owningOrg - } else if (query.short_name === cveIdFixtures.owningOrg.short_name && cveIdFixtures.owningOrg.inUse === true) { - cveIdFixtures.owningOrg.inUse = false + } else if (query.short_name === cveIdFixtures.owningOrg.short_name && cveIdFixtures.owningOrg.in_use === true) { + cveIdFixtures.owningOrg.in_use = false return null } } @@ -103,11 +103,11 @@ describe('Testing the priority reservation of POST /cve-id endpoint in CveId Con } async findOneAndUpdate (query) { - if (query.short_name === cveIdFixtures.owningOrg.short_name && cveIdFixtures.owningOrg.inUse === false) { - cveIdFixtures.owningOrg.inUse = true + if (query.short_name === cveIdFixtures.owningOrg.short_name && cveIdFixtures.owningOrg.in_use === false) { + cveIdFixtures.owningOrg.in_use = true return cveIdFixtures.owningOrg - } else if (query.short_name === cveIdFixtures.owningOrg.short_name && cveIdFixtures.owningOrg.inUse === true) { - cveIdFixtures.owningOrg.inUse = false + } else if (query.short_name === cveIdFixtures.owningOrg.short_name && cveIdFixtures.owningOrg.in_use === true) { + cveIdFixtures.owningOrg.in_use = false return null } } @@ -119,7 +119,7 @@ describe('Testing the priority reservation of POST /cve-id endpoint in CveId Con getCveIdRepository: () => { return new NullCveIdRepo() }, getCveIdRangeRepository: () => { return new CveIdRangeReserveYearDoesntExist() }, getUserRepository: () => { return new NullUserRepo() }, - getOrgRepository: () => { return new OrgReserveYear2025RangeDoesntExist() } + getBaseOrgRepository: () => { return new OrgReserveYear2025RangeDoesntExist() } } req.ctx.repositories = factory next() @@ -133,7 +133,7 @@ describe('Testing the priority reservation of POST /cve-id endpoint in CveId Con done(err) } - const quotaHeader = cveIdFixtures.owningOrg.policies.id_quota.toString() + const quotaHeader = cveIdFixtures.owningOrg.id_quota.toString() expect(res.header).to.have.property('cve-api-remaining-quota').and.to.equal(quotaHeader) expect(res).to.have.status(403) @@ -176,11 +176,11 @@ describe('Testing the priority reservation of POST /cve-id endpoint in CveId Con } async findOneAndUpdate (query) { - if (query.short_name === cveIdFixtures.owningOrg.short_name && cveIdFixtures.owningOrg.inUse === false) { - cveIdFixtures.owningOrg.inUse = true + if (query.short_name === cveIdFixtures.owningOrg.short_name && cveIdFixtures.owningOrg.in_use === false) { + cveIdFixtures.owningOrg.in_use = true return cveIdFixtures.owningOrg - } else if (query.short_name === cveIdFixtures.owningOrg.short_name && cveIdFixtures.owningOrg.inUse === true) { - cveIdFixtures.owningOrg.inUse = false + } else if (query.short_name === cveIdFixtures.owningOrg.short_name && cveIdFixtures.owningOrg.in_use === true) { + cveIdFixtures.owningOrg.in_use = false return null } } @@ -192,7 +192,7 @@ describe('Testing the priority reservation of POST /cve-id endpoint in CveId Con getCveIdRepository: () => { return new CveIdReserveSequentialPriorityIsFull() }, getCveIdRangeRepository: () => { return new CveIdRangeReserveSequentialIsFull() }, getUserRepository: () => { return new NullUserRepo() }, - getOrgRepository: () => { return new OrgReserveSequentialIsFull() } + getBaseOrgRepository: () => { return new OrgReserveSequentialIsFull() } } req.ctx.repositories = factory setOwningOrgUUIDContext(req) @@ -207,7 +207,7 @@ describe('Testing the priority reservation of POST /cve-id endpoint in CveId Con done(err) } - const quotaHeader = (cveIdFixtures.owningOrg.policies.id_quota).toString() + const quotaHeader = (cveIdFixtures.owningOrg.id_quota).toString() expect(res.header).to.have.property('cve-api-remaining-quota').and.to.equal(quotaHeader) expect(res).to.have.status(403) @@ -258,7 +258,6 @@ describe('Testing the priority reservation of POST /cve-id endpoint in CveId Con getCveIdRepository: () => { return new CveIdReserveSequentialPriorityIsFull() }, getCveIdRangeRepository: () => { return new CveIdRangeReserveSequentialPriorityIsFull() }, getUserRepository: () => { return new UserReserveSequentialPriorityIsFull() }, - getOrgRepository: () => { return new OrgReserveSequentialPriorityIsFull() }, getBaseOrgRepository: () => { return new OrgReserveSequentialPriorityIsFull() } } req.ctx.repositories = factory @@ -277,7 +276,7 @@ describe('Testing the priority reservation of POST /cve-id endpoint in CveId Con done(err) } - const quotaHeader = (cveIdFixtures.owningOrg.policies.id_quota - 1).toString() + const quotaHeader = (cveIdFixtures.owningOrg.id_quota - 1).toString() expect(res.header).to.have.property('cve-api-remaining-quota').and.to.equal(quotaHeader) expect(res).to.have.status(200) @@ -331,7 +330,6 @@ describe('Testing the priority reservation of POST /cve-id endpoint in CveId Con getCveIdRepository: () => { return new CveIdReserveSequentialPriorityIsFull() }, getCveIdRangeRepository: () => { return new CveIdRangeReserveSequentialPriority() }, getUserRepository: () => { return new UserReserveSequentialPriorityIsFull() }, - getOrgRepository: () => { return new OrgReserveSequentialPriorityIsFull() }, getBaseOrgRepository: () => { return new OrgReserveSequentialPriorityIsFull() } } req.ctx.repositories = factory @@ -350,7 +348,7 @@ describe('Testing the priority reservation of POST /cve-id endpoint in CveId Con done(err) } - const quotaHeader = (cveIdFixtures.owningOrg.policies.id_quota - 1).toString() + const quotaHeader = (cveIdFixtures.owningOrg.id_quota - 1).toString() expect(res.header).to.have.property('cve-api-remaining-quota').and.to.equal(quotaHeader) expect(res).to.have.status(200) diff --git a/test/unit-tests/cve-id/reserveCveId/cveIdReserveSequentialTest.js b/test/unit-tests/cve-id/reserveCveId/cveIdReserveSequentialTest.js index 12c7937e4..8dcf4cf82 100644 --- a/test/unit-tests/cve-id/reserveCveId/cveIdReserveSequentialTest.js +++ b/test/unit-tests/cve-id/reserveCveId/cveIdReserveSequentialTest.js @@ -48,11 +48,11 @@ class OrgReserveYear2025RangeDoesntExistSequential { } async findOneAndUpdate (query) { - if (query.short_name === cveIdFixtures.owningOrg.short_name && cveIdFixtures.owningOrg.inUse === false) { - cveIdFixtures.owningOrg.inUse = true + if (query.short_name === cveIdFixtures.owningOrg.short_name && cveIdFixtures.owningOrg.in_use === false) { + cveIdFixtures.owningOrg.in_use = true return cveIdFixtures.owningOrg - } else if (query.short_name === cveIdFixtures.owningOrg.short_name && cveIdFixtures.owningOrg.inUse === true) { - cveIdFixtures.owningOrg.inUse = false + } else if (query.short_name === cveIdFixtures.owningOrg.short_name && cveIdFixtures.owningOrg.in_use === true) { + cveIdFixtures.owningOrg.in_use = false return null } } @@ -79,7 +79,7 @@ describe('Testing the sequential reservation of POST /cve-id endpoint in CveId C getCveIdRepository: () => { return new CveIdReserveSequentialYearDoesntExist() }, getCveIdRangeRepository: () => { return new CveIdRangeReserveYearDoesntExist() }, getUserRepository: () => { return new NullUserRepo() }, - getOrgRepository: () => { return new OrgReserveYear2025RangeDoesntExistSequential() } + getBaseOrgRepository: () => { return new OrgReserveYear2025RangeDoesntExistSequential() } } req.ctx.repositories = factory next() @@ -93,7 +93,7 @@ describe('Testing the sequential reservation of POST /cve-id endpoint in CveId C done(err) } - const quotaHeader = (cveIdFixtures.owningOrg.policies.id_quota - 2).toString() + const quotaHeader = (cveIdFixtures.owningOrg.id_quota - 2).toString() expect(res.header).to.have.property('cve-api-remaining-quota').and.to.equal(quotaHeader) expect(res).to.have.status(403) @@ -138,7 +138,7 @@ describe('Testing the sequential reservation of POST /cve-id endpoint in CveId C getCveIdRepository: () => { return new CveIdReserveSequentialIsFull() }, getCveIdRangeRepository: () => { return new CveIdRangeReserveSequentialIsFull() }, getUserRepository: () => { return new NullUserRepo() }, - getOrgRepository: () => { return new OrgReserveYear2025RangeDoesntExistSequential() } + getBaseOrgRepository: () => { return new OrgReserveYear2025RangeDoesntExistSequential() } } req.ctx.repositories = factory setOwningOrgUUIDContext(req) @@ -153,7 +153,7 @@ describe('Testing the sequential reservation of POST /cve-id endpoint in CveId C done(err) } - const quotaHeader = (cveIdFixtures.owningOrg.policies.id_quota - 2).toString() + const quotaHeader = (cveIdFixtures.owningOrg.id_quota - 2).toString() expect(res.header).to.have.property('cve-api-remaining-quota').and.to.equal(quotaHeader) expect(res).to.have.status(403) @@ -223,11 +223,11 @@ describe('Testing the sequential reservation of POST /cve-id endpoint in CveId C } async findOneAndUpdate (query) { - if (query.short_name === cveIdFixtures.owningOrg.short_name && cveIdFixtures.owningOrg.inUse === false) { - cveIdFixtures.owningOrg.inUse = true + if (query.short_name === cveIdFixtures.owningOrg.short_name && cveIdFixtures.owningOrg.in_use === false) { + cveIdFixtures.owningOrg.in_use = true return cveIdFixtures.owningOrg - } else if (query.short_name === cveIdFixtures.owningOrg.short_name && cveIdFixtures.owningOrg.inUse === true) { - cveIdFixtures.owningOrg.inUse = false + } else if (query.short_name === cveIdFixtures.owningOrg.short_name && cveIdFixtures.owningOrg.in_use === true) { + cveIdFixtures.owningOrg.in_use = false return null } } @@ -243,7 +243,6 @@ describe('Testing the sequential reservation of POST /cve-id endpoint in CveId C getCveIdRepository: () => { return new CveIdReserveSequential() }, getCveIdRangeRepository: () => { return new CveIdRangeReserveSequential() }, getUserRepository: () => { return new UserReserveSequentialPriorityIsFull() }, - getOrgRepository: () => { return new OrgReserveSequentialPriorityIsFull() }, getBaseOrgRepository: () => { return new OrgReserveSequentialPriorityIsFull() } } req.ctx.repositories = factory @@ -262,7 +261,7 @@ describe('Testing the sequential reservation of POST /cve-id endpoint in CveId C done(err) } - const quotaHeader = (cveIdFixtures.owningOrg.policies.id_quota - 7).toString() // already has two reserved cve ids + const quotaHeader = (cveIdFixtures.owningOrg.id_quota - 7).toString() // already has two reserved cve ids expect(res.header).to.have.property('cve-api-remaining-quota').and.to.equal(quotaHeader) expect(res).to.have.status(200) From 131fdb26d129943abf0818b60c3c6d731ef37512 Mon Sep 17 00:00:00 2001 From: James Dalphond Date: Tue, 18 Aug 2026 14:56:12 -0400 Subject: [PATCH 11/20] Aggregate CVE-ID PUT responses from BaseOrg and BaseUser.Remove the obsolete OrgRepository factory getter and legacy test mocks. --- .../cve-id.controller/cve-id.controller.js | 19 ++---- src/repositories/repositoryFactory.js | 6 -- .../middleware/authenticatedContextTest.js | 38 ----------- test/unit-tests/cve-id/cveIdUpdateTest.js | 13 +++- .../middleware/onlyOrgWithPartnerRoleTest.js | 41 ++---------- .../onlySecretariatMiddlewareTest.js | 4 -- .../onlySecretariatOrAdminMiddlewareTest.js | 4 -- .../unit-tests/middleware/validateUserTest.js | 8 --- .../unit-tests/org/legacy/orgCreateADPTest.js | 2 +- .../org/legacy/orgGetIdQuotaTest.js | 2 - test/unit-tests/user/userResetSecretTest.js | 64 +++---------------- test/unit-tests/user/userUpdateTest.js | 14 ---- 12 files changed, 31 insertions(+), 184 deletions(-) diff --git a/src/controller/cve-id.controller/cve-id.controller.js b/src/controller/cve-id.controller/cve-id.controller.js index ed19762fb..c47c6eaea 100644 --- a/src/controller/cve-id.controller/cve-id.controller.js +++ b/src/controller/cve-id.controller/cve-id.controller.js @@ -423,7 +423,7 @@ async function modifyCveId (req, res, next) { } await cveIdRepo.findOneAndUpdate({ cve_id: id }, cveId) // update cve id - const agt = setAggregateObj({ cve_id: id }) + const agt = setBaseAggregateObj({ cve_id: id }) result = await cveIdRepo.aggregate(agt, 'primary') result = result.length > 0 ? result[0] : null @@ -927,14 +927,14 @@ function getRandomInt (min, max) { return Math.floor(Math.random() * (max - min) + min) // The maximum is exclusive and the minimum is inclusive } -function setAggregateObj (query, { orgCollection = 'Org', userCollection = 'User' } = {}) { +function setBaseAggregateObj (query) { return [ { $match: query }, { $lookup: { - from: orgCollection, + from: 'BaseOrg', localField: 'owning_cna', foreignField: 'UUID', as: 'ownerCna' @@ -957,7 +957,7 @@ function setAggregateObj (query, { orgCollection = 'Org', userCollection = 'User }, { $lookup: { - from: userCollection, + from: 'BaseUser', localField: 'requested_by.user', foreignField: 'UUID', as: 'result' @@ -980,7 +980,7 @@ function setAggregateObj (query, { orgCollection = 'Org', userCollection = 'User }, { $lookup: { - from: orgCollection, + from: 'BaseOrg', localField: 'requested_by.cna', foreignField: 'UUID', as: 'result' @@ -1004,15 +1004,6 @@ function setAggregateObj (query, { orgCollection = 'Org', userCollection = 'User ] } -// GET /cve-id/:id reads organization and user details from the registry -// collections. Keep setAggregateObj for write responses until their migration. -function setBaseAggregateObj (query) { - return setAggregateObj(query, { - orgCollection: 'BaseOrg', - userCollection: 'BaseUser' - }) -} - // Smaller aggregation used in getFilteredCveId function function setMinAggregateObj (query) { return [ diff --git a/src/repositories/repositoryFactory.js b/src/repositories/repositoryFactory.js index 7f97e1177..8e3dce9c1 100644 --- a/src/repositories/repositoryFactory.js +++ b/src/repositories/repositoryFactory.js @@ -1,4 +1,3 @@ -const OrgRepository = require('./orgRepository') const CveRepository = require('./cveRepository') const CveIdRepository = require('./cveIdRepository') const CveIdRangeRepository = require('./cveIdRangeRepository') @@ -10,11 +9,6 @@ const ReviewObjectRepository = require('./reviewObjectRepository') const GlossaryRepository = require('./glossaryRepository') class RepositoryFactory { - getOrgRepository () { - const repo = new OrgRepository() - return repo - } - getCveRepository () { const repo = new CveRepository() return repo diff --git a/test/integration-tests/middleware/authenticatedContextTest.js b/test/integration-tests/middleware/authenticatedContextTest.js index 6ad751346..ac6ad8176 100644 --- a/test/integration-tests/middleware/authenticatedContextTest.js +++ b/test/integration-tests/middleware/authenticatedContextTest.js @@ -105,13 +105,6 @@ describe('Authenticated request context middleware integration', () => { admins: [], short_name: mwFixtures.existentOrg.short_name } - const legacyAuthenticatedOrg = { - UUID: authenticatedOrg.UUID, - authority: { - active_roles: [CONSTANTS.AUTH_ROLE_ENUM.CNA] - }, - short_name: authenticatedOrg.short_name - } const authenticatedUser = { ...mwFixtures.existentUser, UUID: 'a11186d5-ce3d-4fd9-aecd-8698c26897f2', @@ -137,35 +130,6 @@ describe('Authenticated request context middleware integration', () => { } } - class LegacyOrgRepo { - async getOrgUUID () { - return '11111111-1111-4111-8111-111111111111' - } - - async findOneByUUID (orgUUID) { - expect(orgUUID).to.equal(authenticatedOrg.UUID) - return legacyAuthenticatedOrg - } - - async isSecretariat () { - return true - } - - async isBulkDownload () { - return false - } - - async getAllOrgs () { - return [legacyAuthenticatedOrg] - } - } - - class LegacyUserRepo { - async getAllUsers () { - return [] - } - } - class CveIdRepo { async aggregatePaginate (aggregate) { expect(aggregate[0].$match).to.have.property('owning_cna', authenticatedOrg.UUID) @@ -188,8 +152,6 @@ describe('Authenticated request context middleware integration', () => { req.ctx.repositories = { getBaseOrgRepository: () => new BaseOrgRepo(), getBaseUserRepository: () => new BaseUserRepo(), - getOrgRepository: () => new LegacyOrgRepo(), - getUserRepository: () => new LegacyUserRepo(), getCveIdRepository: () => new CveIdRepo() } next() diff --git a/test/unit-tests/cve-id/cveIdUpdateTest.js b/test/unit-tests/cve-id/cveIdUpdateTest.js index bf67a772b..87ecaa512 100644 --- a/test/unit-tests/cve-id/cveIdUpdateTest.js +++ b/test/unit-tests/cve-id/cveIdUpdateTest.js @@ -87,7 +87,8 @@ class CveIdModifyCveIdOrgAndStateModified { return null } - async aggregate () { + async aggregate (pipeline) { + this.aggregatePipeline = pipeline return [this.testRes1] } @@ -257,7 +258,7 @@ describe('Testing the PUT /cve-id/:id endpoint in CveId Controller', () => { // .put((req, res, next) => { // const factory = { // getCveIdRepository: () => { return new CveIdModifyCveIdOrgAndStateModified() }, - // getOrgRepository: () => { return new NullOrgRepo() }, + // getBaseOrgRepository: () => { return new NullOrgRepo() }, // getUserRepository: () => { return new NullUserRepo() }, // getCveRepository: () => { return new NullCveRepo() } // } @@ -285,10 +286,12 @@ describe('Testing the PUT /cve-id/:id endpoint in CveId Controller', () => { context('Positive Tests', () => { it('Cve is reassigned to another org and state is modified', (done) => { + const cveIdRepo = new CveIdModifyCveIdOrgAndStateModified() + app.route('/cve-id-modify-org-and-state-modified/:id') .put((req, res, next) => { const factory = { - getCveIdRepository: () => { return new CveIdModifyCveIdOrgAndStateModified() }, + getCveIdRepository: () => { return cveIdRepo }, getBaseOrgRepository: () => { return new OrgModifyCveIdOrgAndStateModified() }, getUserRepository: () => { return new UserModifyCveIdOrgAndStateModified() }, getCveRepository: () => { return new NullCveRepo() } @@ -315,6 +318,10 @@ describe('Testing the PUT /cve-id/:id endpoint in CveId Controller', () => { expect(res.body.updated).to.have.property('owning_cna').and.to.equal(cveIdFixtures.org.short_name) expect(res.body.updated).to.have.nested.property('requested_by.cna').and.to.equal(cveIdFixtures.owningOrg.short_name) expect(res.body.updated).to.have.nested.property('requested_by.user').and.to.equal(cveIdFixtures.owningOrgUser.username) + const lookupCollections = cveIdRepo.aggregatePipeline + .filter(stage => stage.$lookup) + .map(stage => stage.$lookup.from) + expect(lookupCollections).to.deep.equal(['BaseOrg', 'BaseUser', 'BaseOrg']) done() }) }) diff --git a/test/unit-tests/middleware/onlyOrgWithPartnerRoleTest.js b/test/unit-tests/middleware/onlyOrgWithPartnerRoleTest.js index 34013b54c..5559a4f2b 100644 --- a/test/unit-tests/middleware/onlyOrgWithPartnerRoleTest.js +++ b/test/unit-tests/middleware/onlyOrgWithPartnerRoleTest.js @@ -5,7 +5,6 @@ const sinon = require('sinon') const { faker } = require('@faker-js/faker') const expect = chai.expect -const OrgRepository = require('../../../src/repositories/orgRepository.js') const { onlyOrgWithPartnerRole } = require('../../../src/middleware/middleware.js') const errors = require('../../../src/middleware/error.js') const BaseOrgRepository = require('../../../src/repositories/baseOrgRepository.js') @@ -16,67 +15,45 @@ const stubAdpOrg = { short_name: 'adpOrg', name: 'test_adp', UUID: faker.datatype.uuid(), - authority: { - active_roles: [ - 'ADP' - ] - } + authority: ['ADP'] } const stubCnaOrg = { short_name: 'cnaOrg', name: 'test_cna', UUID: faker.datatype.uuid(), - authority: { - active_roles: [ - 'CNA' - ] - } + authority: ['CNA'] } const stubBulkDownloadOrg = { short_name: 'bdOrg', name: 'test_bd', UUID: faker.datatype.uuid(), - authority: { - active_roles: [ - 'BULK_DOWNLOAD' - ] - } + authority: ['BULK_DOWNLOAD'] } const stubOrgNoRole = { short_name: 'NoRole', name: 'test_org', UUID: faker.datatype.uuid(), - authority: { - active_roles: [] - } + authority: [] } const stubSecretariat = { short_name: 'secOrg', name: 'test_sec', UUID: faker.datatype.uuid(), - authority: { - active_roles: [ - 'SECRETARIAT' - ] - } + authority: ['SECRETARIAT'] } describe('Testing onlyOrgWithPartnerRole middleware', () => { - let status, json, res, next, getOrgRepository, baseUserRepo, baseOrgRepo, getBaseOrgRepository, getBaseUserRepository, orgRepo + let status, json, res, next, baseUserRepo, baseOrgRepo, getBaseOrgRepository, getBaseUserRepository beforeEach(() => { status = sinon.stub() json = sinon.spy() res = { json, status } next = sinon.spy() status.returns(res) - orgRepo = new OrgRepository() - getOrgRepository = sinon.stub() - getOrgRepository.returns(orgRepo) - baseOrgRepo = new BaseOrgRepository() getBaseOrgRepository = sinon.stub() getBaseOrgRepository.returns(baseOrgRepo) @@ -92,7 +69,6 @@ describe('Testing onlyOrgWithPartnerRole middleware', () => { org: stubBulkDownloadOrg.short_name, uuid: stubBulkDownloadOrg.UUID, repositories: { - getOrgRepository, getBaseOrgRepository, getBaseUserRepository } @@ -112,7 +88,6 @@ describe('Testing onlyOrgWithPartnerRole middleware', () => { org: stubOrgNoRole.short_name, uuid: stubOrgNoRole.UUID, repositories: { - getOrgRepository, getBaseOrgRepository } } @@ -132,7 +107,6 @@ describe('Testing onlyOrgWithPartnerRole middleware', () => { org: stubCnaOrg.short_name, uuid: stubCnaOrg.UUID, repositories: { - getOrgRepository, getBaseOrgRepository } } @@ -154,7 +128,6 @@ describe('Testing onlyOrgWithPartnerRole middleware', () => { org: stubAdpOrg.short_name, uuid: stubAdpOrg.UUID, repositories: { - getOrgRepository, getBaseOrgRepository } } @@ -172,7 +145,6 @@ describe('Testing onlyOrgWithPartnerRole middleware', () => { org: stubCnaOrg.short_name, uuid: stubCnaOrg.UUID, repositories: { - getOrgRepository, getBaseOrgRepository } } @@ -190,7 +162,6 @@ describe('Testing onlyOrgWithPartnerRole middleware', () => { org: stubSecretariat.short_name, uuid: stubSecretariat.UUID, repositories: { - getOrgRepository, getBaseOrgRepository } } diff --git a/test/unit-tests/middleware/onlySecretariatMiddlewareTest.js b/test/unit-tests/middleware/onlySecretariatMiddlewareTest.js index 9b578b7d8..d8c9c17c3 100644 --- a/test/unit-tests/middleware/onlySecretariatMiddlewareTest.js +++ b/test/unit-tests/middleware/onlySecretariatMiddlewareTest.js @@ -31,7 +31,6 @@ describe('Test only Secretariat middleware', () => { app.route('/only-secretariat-pass') .post((req, res, next) => { const factory = { - getOrgRepository: () => { return new OrgOnlySecretariatPass() }, getBaseOrgRepository: () => { return new OrgOnlySecretariatPass() } } req.ctx.repositories = factory @@ -77,7 +76,6 @@ describe('Test only Secretariat middleware', () => { app.route('/only-secretariat-reject-by-uuid') .post((req, res, next) => { const factory = { - getOrgRepository: () => { return new OrgOnlySecretariatRejectByUUID() }, getBaseOrgRepository: () => { return new OrgOnlySecretariatRejectByUUID() } } req.ctx.repositories = factory @@ -115,7 +113,6 @@ describe('Test only Secretariat middleware', () => { app.route('/only-secretariat-reject-missing-authenticated-uuid') .post((req, res, next) => { const factory = { - getOrgRepository: () => { return new OrgOnlySecretariatRejectMissingUUID() }, getBaseOrgRepository: () => { return new OrgOnlySecretariatRejectMissingUUID() } } req.ctx.repositories = factory @@ -157,7 +154,6 @@ describe('Test only Secretariat middleware', () => { app.route('/only-secretariat-reject') .post((req, res, next) => { const factory = { - getOrgRepository: () => { return new OrgOnlySecretariatReject() }, getBaseOrgRepository: () => { return new OrgOnlySecretariatReject() } } req.ctx.repositories = factory diff --git a/test/unit-tests/middleware/onlySecretariatOrAdminMiddlewareTest.js b/test/unit-tests/middleware/onlySecretariatOrAdminMiddlewareTest.js index f963ad402..925cf5c90 100644 --- a/test/unit-tests/middleware/onlySecretariatOrAdminMiddlewareTest.js +++ b/test/unit-tests/middleware/onlySecretariatOrAdminMiddlewareTest.js @@ -47,7 +47,6 @@ class UserOnlySecretariatOrAdmin { app.route('/only-secretariat-or-admin-pass') .post((req, res, next) => { const factory = { - getOrgRepository: () => { return new OrgOnlySecretariatOrAdmin() }, getBaseOrgRepository: () => { return new OrgOnlySecretariatOrAdmin() }, getUserRepository: () => { return new UserOnlySecretariatOrAdmin() }, getBaseUserRepository: () => { return new UserOnlySecretariatOrAdmin() } @@ -143,7 +142,6 @@ describe('Test only Secretariat or Org Admin user middleware', () => { app.route('/only-secretariat-or-admin-reject-by-uuid') .post((req, res, next) => { const factory = { - getOrgRepository: () => { return new OrgOnlySecretariatOrAdminRejectByUUID() }, getBaseOrgRepository: () => { return new OrgOnlySecretariatOrAdminRejectByUUID() }, getUserRepository: () => { return new UserOnlySecretariatOrAdminRejectByUUID() }, getBaseUserRepository: () => { return new UserOnlySecretariatOrAdminRejectByUUID() } @@ -197,7 +195,6 @@ describe('Test only Secretariat or Org Admin user middleware', () => { app.route('/only-secretariat-or-admin-reject-missing-authenticated-user-uuid') .post((req, res, next) => { const factory = { - getOrgRepository: () => { return new OrgOnlySecretariatOrAdminRejectMissingUserUUID() }, getBaseOrgRepository: () => { return new OrgOnlySecretariatOrAdminRejectMissingUserUUID() }, getUserRepository: () => { return new UserOnlySecretariatOrAdminRejectMissingUserUUID() }, getBaseUserRepository: () => { return new UserOnlySecretariatOrAdminRejectMissingUserUUID() } @@ -232,7 +229,6 @@ describe('Test only Secretariat or Org Admin user middleware', () => { app.route('/only-secretariat-or-admin-reject') .post((req, res, next) => { const factory = { - getOrgRepository: () => { return new OrgOnlySecretariatOrAdmin() }, getBaseOrgRepository: () => { return new OrgOnlySecretariatOrAdmin() }, getUserRepository: () => { return new UserOnlySecretariatOrAdmin() }, getBaseUserRepository: () => { return new UserOnlySecretariatOrAdmin() } diff --git a/test/unit-tests/middleware/validateUserTest.js b/test/unit-tests/middleware/validateUserTest.js index b470b633c..25c6a976b 100644 --- a/test/unit-tests/middleware/validateUserTest.js +++ b/test/unit-tests/middleware/validateUserTest.js @@ -72,7 +72,6 @@ describe('Testing the user validation middleware', () => { app.route('/validate-user-org-doesnt-exist') .post((req, res, next) => { const factory = { - getOrgRepository: () => { return new NullOrgRepo() }, getUserRepository: () => { return new NullUserRepo() }, getBaseUserRepository: () => { return new NullUserRepo() }, getBaseOrgRepository: () => { return new NullOrgRepo() } @@ -109,7 +108,6 @@ describe('Testing the user validation middleware', () => { app.route('/validate-user-user-not-found') .post((req, res, next) => { const factory = { - getOrgRepository: () => { return new NullOrgRepo() }, getUserRepository: () => { return new NullUserRepo() }, getBaseUserRepository: () => { return new NullUserRepo() }, getBaseOrgRepository: () => { return new NullOrgRepo() } @@ -146,7 +144,6 @@ describe('Testing the user validation middleware', () => { app.route('/validate-user-key-incorrect') .post((req, res, next) => { const factory = { - getOrgRepository: () => { return new OrgValidateUserSuccess() }, getUserRepository: () => { return new UserValidateUserSuccess() }, getBaseOrgRepository: () => { return new OrgValidateUserSuccess() }, getBaseUserRepository: () => { return new UserValidateUserSuccess() } @@ -189,7 +186,6 @@ describe('Testing the user validation middleware', () => { app.route('/validate-user-deactivated') .post((req, res, next) => { const factory = { - getOrgRepository: () => { return new OrgValidateUserSuccess() }, getUserRepository: () => { return new UserValidateUserDeactivated() }, getBaseOrgRepository: () => { return new OrgValidateUserSuccess() }, getBaseUserRepository: () => { return new UserValidateUserDeactivated() } @@ -229,7 +225,6 @@ describe('Testing the user validation middleware', () => { app.route('/validate-user-org-undefined') .post((req, res, next) => { const factory = { - getOrgRepository: () => { return new NullOrgRepo() }, getUserRepository: () => { return new NullUserRepo() }, getBaseUserRepository: () => { return new NullUserRepo() }, getBaseOrgRepository: () => { return new NullOrgRepo() } @@ -266,7 +261,6 @@ describe('Testing the user validation middleware', () => { app.route('/validate-user-submitter-undefined') .post((req, res, next) => { const factory = { - getOrgRepository: () => { return new NullOrgRepo() }, getUserRepository: () => { return new NullUserRepo() }, getBaseUserRepository: () => { return new NullUserRepo() }, getBaseOrgRepository: () => { return new NullOrgRepo() } @@ -303,7 +297,6 @@ describe('Testing the user validation middleware', () => { app.route('/validate-user-secret-undefined') .post((req, res, next) => { const factory = { - getOrgRepository: () => { return new NullOrgRepo() }, getUserRepository: () => { return new NullUserRepo() }, getBaseUserRepository: () => { return new NullUserRepo() }, getBaseOrgRepository: () => { return new NullOrgRepo() } @@ -342,7 +335,6 @@ describe('Testing the user validation middleware', () => { app.route('/validate-user-successful') .post((req, res, next) => { const factory = { - getOrgRepository: () => { return new OrgValidateUserSuccess() }, getUserRepository: () => { return new UserValidateUserSuccess() }, getBaseOrgRepository: () => { return new OrgValidateUserSuccess() }, getBaseUserRepository: () => { return new UserValidateUserSuccess() } diff --git a/test/unit-tests/org/legacy/orgCreateADPTest.js b/test/unit-tests/org/legacy/orgCreateADPTest.js index 470fd1068..042803fff 100644 --- a/test/unit-tests/org/legacy/orgCreateADPTest.js +++ b/test/unit-tests/org/legacy/orgCreateADPTest.js @@ -39,7 +39,7 @@ const stubAdpCnaOrg = { } describe('Testing creating orgs with the ADP role', () => { - let status, json, res, next, getOrgRepository, regOrgRepo, getUserRepository, getBaseOrgRepository, + let status, json, res, next, regOrgRepo, getBaseOrgRepository, updateOrg, updateRegOrg, userRegistryRepo, getBaseUserRepository, mockSession beforeEach(() => { diff --git a/test/unit-tests/org/legacy/orgGetIdQuotaTest.js b/test/unit-tests/org/legacy/orgGetIdQuotaTest.js index 0f1fba6ed..6456840f4 100644 --- a/test/unit-tests/org/legacy/orgGetIdQuotaTest.js +++ b/test/unit-tests/org/legacy/orgGetIdQuotaTest.js @@ -85,7 +85,6 @@ describe('Testing the GET /org/:shortname/id_quota endpoint in Org Controller', app.route('/org-id_quota-not-owning-secretariat-org/:shortname') .get((req, res, next) => { const factory = { - getOrgRepository: () => { return new OrgNotOwnerOrSecretariatIdQuota() }, getBaseOrgRepository: () => { return new OrgNotOwnerOrSecretariatIdQuota() } } req.ctx.repositories = factory @@ -248,7 +247,6 @@ describe('Testing the GET /org/:shortname/id_quota endpoint in Org Controller', app.route('/org-id_quota-owning-org/:shortname') .get((req, res, next) => { const factory = { - getOrgRepository: () => { return new OrgOwnerIdQuota() }, getBaseOrgRepository: () => { return new OrgOwnerIdQuota() }, getCveIdRepository: () => { return new CveIdOwnerIdQuota() } } diff --git a/test/unit-tests/user/userResetSecretTest.js b/test/unit-tests/user/userResetSecretTest.js index a0e500bb8..9199e7e16 100644 --- a/test/unit-tests/user/userResetSecretTest.js +++ b/test/unit-tests/user/userResetSecretTest.js @@ -6,8 +6,6 @@ const expect = chai.expect const mongoose = require('mongoose') // Mock Repositories and Controller -const OrgRepository = require('../../../src/repositories/orgRepository.js') -const UserRepository = require('../../../src/repositories/userRepository.js') const BaseOrgRepository = require('../../../src/repositories/baseOrgRepository.js') const BaseUserRepository = require('../../../src/repositories/baseUserRepository.js') @@ -19,10 +17,8 @@ const error = new OrgControllerError() const userFixtures = require('./mockObjects.user.js') describe('Testing the PUT /org/:shortname/user/:username/reset_secret endpoint', () => { - let status, json, res, next, getOrgRepository, orgRepo, getUserRepository, - userRepo, mockSession, orgUUIDStub, regOrgUUIDStub, userUUIDStub, regUserUUIDStub, - isSecretariatStub, isAdminStub, findOneUserStub, updateUserStub, - isRegSecretariatStub, isRegAdminStub, isRegAdminOrSecretariatStub, baseOrgRepo, getBaseOrgRepository, baseUserRepo, getBaseUserRepository, isSecretariatByShortName + let status, json, res, next, mockSession, regOrgUUIDStub, regUserUUIDStub, + baseOrgRepo, getBaseOrgRepository, baseUserRepo, getBaseUserRepository, isSecretariatByShortName beforeEach(() => { // Mock Express response objects @@ -41,31 +37,14 @@ describe('Testing the PUT /org/:shortname/user/:username/reset_secret endpoint', } sinon.stub(mongoose, 'startSession').resolves(mockSession) - // Stub repository getters - orgRepo = new OrgRepository() - getOrgRepository = sinon.stub().returns(orgRepo) - userRepo = new UserRepository() - getUserRepository = sinon.stub().returns(userRepo) - baseOrgRepo = new BaseOrgRepository() getBaseOrgRepository = sinon.stub().returns(baseOrgRepo) baseUserRepo = new BaseUserRepository() getBaseUserRepository = sinon.stub().returns(baseUserRepo) - // Set up stubs for all repository methods that will be called - isSecretariatStub = sinon.stub(orgRepo, 'isSecretariat') - isAdminStub = sinon.stub(userRepo, 'isAdmin') - orgUUIDStub = sinon.stub(orgRepo, 'getOrgUUID') - findOneUserStub = sinon.stub(userRepo, 'findOneByUserNameAndOrgUUID') - updateUserStub = sinon.stub(userRepo, 'updateByUserNameAndOrgUUID') - userUUIDStub = sinon.stub(userRepo, 'getUserUUID') - - // Stubs for registry repositories - isRegSecretariatStub = sinon.stub(baseOrgRepo, 'isSecretariat') + // Set up stubs for BaseOrg and BaseUser methods used by the controller. isSecretariatByShortName = sinon.stub(baseOrgRepo, 'isSecretariatByShortName') - isRegAdminStub = sinon.stub(baseUserRepo, 'isAdmin') - isRegAdminOrSecretariatStub = sinon.stub(baseUserRepo, 'isAdminOrSecretariat') regOrgUUIDStub = sinon.stub(baseOrgRepo, 'getOrgUUID') regUserUUIDStub = sinon.stub(baseUserRepo, 'getUserUUID') }) @@ -76,7 +55,6 @@ describe('Testing the PUT /org/:shortname/user/:username/reset_secret endpoint', context('Negative Tests', () => { it('Should fail if the target organization does not exist', async () => { - orgUUIDStub.resolves(null) regOrgUUIDStub.resolves(null) const req = { @@ -84,7 +62,7 @@ describe('Testing the PUT /org/:shortname/user/:username/reset_secret endpoint', uuid: faker.datatype.uuid(), org: 'secretariat_org', user: 'secretariat_user', - repositories: { getOrgRepository, getUserRepository, getBaseUserRepository, getBaseOrgRepository }, + repositories: { getBaseUserRepository, getBaseOrgRepository }, params: { shortname: userFixtures.nonExistentOrg.short_name, username: userFixtures.existentUser.username @@ -102,19 +80,15 @@ describe('Testing the PUT /org/:shortname/user/:username/reset_secret endpoint', }) it('Should fail if the target user does not exist', async () => { - orgUUIDStub.resolves(userFixtures.existentOrg.UUID) regOrgUUIDStub.resolves(userFixtures.existentOrg.UUID) - isSecretariatStub.resolves(true) - isRegSecretariatStub.resolves(true) isSecretariatByShortName.resolves(true) - findOneUserStub.resolves(null) const req = { ctx: { uuid: faker.datatype.uuid(), org: 'secretariat_org', user: 'secretariat_user', - repositories: { getOrgRepository, getUserRepository, getBaseUserRepository, getBaseOrgRepository }, + repositories: { getBaseUserRepository, getBaseOrgRepository }, params: { shortname: userFixtures.existentOrg.short_name, username: userFixtures.nonExistentUser.username @@ -132,12 +106,8 @@ describe('Testing the PUT /org/:shortname/user/:username/reset_secret endpoint', }) it('Should fail if a non-Secretariat user tries to access a different organization', async () => { - orgUUIDStub.resolves(userFixtures.existentOrg.UUID) regOrgUUIDStub.resolves(userFixtures.existentOrg.UUID) isSecretariatByShortName.resolves(false) - isRegSecretariatStub.resolves(false) - isRegAdminStub.resolves(false) - isRegAdminOrSecretariatStub.resolves(false) regUserUUIDStub.onFirstCall().resolves(userFixtures.existentUser.UUID) regUserUUIDStub.onSecondCall().resolves('FakeUUID') @@ -146,7 +116,7 @@ describe('Testing the PUT /org/:shortname/user/:username/reset_secret endpoint', uuid: faker.datatype.uuid(), org: userFixtures.owningOrg.short_name, user: 'some_user', - repositories: { getOrgRepository, getUserRepository, getBaseUserRepository, getBaseOrgRepository }, + repositories: { getBaseUserRepository, getBaseOrgRepository }, params: { shortname: userFixtures.existentOrg.short_name, username: userFixtures.existentUser.username @@ -165,15 +135,10 @@ describe('Testing the PUT /org/:shortname/user/:username/reset_secret endpoint', }) it('Should fail if a non-admin, non-secretariat user tries to reset another user\'s secret', async () => { - orgUUIDStub.resolves(userFixtures.existentOrgDummy.UUID) regOrgUUIDStub.resolves(userFixtures.existentOrgDummy.UUID) - isSecretariatStub.resolves(false) - isRegSecretariatStub.resolves(false) - isAdminStub.resolves(false) - isRegAdminStub.resolves(false) - findOneUserStub.resolves(userFixtures.userC) sinon.stub(baseOrgRepo, 'findOneByUUID').resolves(userFixtures.existentOrgDummy) sinon.stub(baseUserRepo, 'findOneByUserNameAndOrgUUID').resolves(userFixtures.userA) + sinon.stub(baseUserRepo, 'isAdmin').resolves(false) regUserUUIDStub.onFirstCall().resolves(userFixtures.userC.UUID) regUserUUIDStub.onSecondCall().resolves(userFixtures.userA.UUID) @@ -183,7 +148,7 @@ describe('Testing the PUT /org/:shortname/user/:username/reset_secret endpoint', org: userFixtures.existentOrgDummy.short_name, orgUUID: userFixtures.existentOrgDummy.UUID, user: userFixtures.userA.username, - repositories: { getOrgRepository, getUserRepository, getBaseOrgRepository, getBaseUserRepository }, + repositories: { getBaseOrgRepository, getBaseUserRepository }, params: { shortname: userFixtures.existentOrgDummy.short_name, username: userFixtures.userC.username @@ -204,18 +169,13 @@ describe('Testing the PUT /org/:shortname/user/:username/reset_secret endpoint', context('Positive Tests', () => { beforeEach(() => { // Common stubs for positive paths - orgUUIDStub.resolves(userFixtures.existentOrgDummy.UUID) regOrgUUIDStub.resolves(userFixtures.existentOrgDummy.UUID) - updateUserStub.resolves({ matchedCount: 1, modifiedCount: 1 }) - userUUIDStub.resolves(userFixtures.userA.UUID) regUserUUIDStub.resolves(userFixtures.userA.UUID) sinon.stub(baseOrgRepo, 'hasRoleByUUID').resolves(false) }) it('Should reset the secret if the requester is the user themselves', async () => { isSecretariatByShortName.resolves(false) - isRegAdminStub.resolves(false) - findOneUserStub.resolves(userFixtures.userA) sinon.stub(baseUserRepo, 'resetSecret').resolves('ANEWUUID') const req = { @@ -225,7 +185,7 @@ describe('Testing the PUT /org/:shortname/user/:username/reset_secret endpoint', orgUUID: userFixtures.existentOrgDummy.UUID, user: userFixtures.userA.username, userUUID: userFixtures.userA.UUID, - repositories: { getOrgRepository, getUserRepository, getBaseOrgRepository, getBaseUserRepository }, + repositories: { getBaseOrgRepository, getBaseUserRepository }, params: { shortname: userFixtures.existentOrgDummy.short_name, username: userFixtures.userA.username @@ -244,11 +204,8 @@ describe('Testing the PUT /org/:shortname/user/:username/reset_secret endpoint', it('Should reset the secret if the requester is a Secretariat', async () => { isSecretariatByShortName.resolves(true) - isAdminStub.resolves(false) - isRegAdminStub.resolves(false) regUserUUIDStub.onFirstCall().resolves(userFixtures.userC.UUID) regUserUUIDStub.onSecondCall().resolves(userFixtures.userA.UUID) - orgUUIDStub.withArgs(userFixtures.existentOrg.short_name).resolves(userFixtures.existentOrg.UUID) regOrgUUIDStub.withArgs(userFixtures.existentOrg.short_name).resolves(userFixtures.existentOrg.UUID) sinon.stub(baseUserRepo, 'resetSecret').resolves('ANEWUUID') @@ -274,9 +231,6 @@ describe('Testing the PUT /org/:shortname/user/:username/reset_secret endpoint', it('Should reset the secret if the requester is an admin of the target user\'s org', async () => { isSecretariatByShortName.resolves(false) - isRegSecretariatStub.resolves(false) - isAdminStub.resolves(true) - isRegAdminStub.resolves(true) regUserUUIDStub.resolves(userFixtures.userC.UUID) sinon.stub(baseUserRepo, 'isUserAdminOfOrgUUID').resolves(true) sinon.stub(baseOrgRepo, 'findOneByUUID').resolves({ ...userFixtures.existentOrgDummy, admins: [userFixtures.userD.UUID] }) diff --git a/test/unit-tests/user/userUpdateTest.js b/test/unit-tests/user/userUpdateTest.js index 1f7ce75c8..da04869e3 100644 --- a/test/unit-tests/user/userUpdateTest.js +++ b/test/unit-tests/user/userUpdateTest.js @@ -171,7 +171,6 @@ describe('Testing the PUT /org/:shortname/user/:username endpoint in Org Control app.route('/user-not-updated-org-doesnt-exist/:shortname/:username') .put((req, res, next) => { const factory = { - getOrgRepository: () => { return new OrgUserNotUpdatedOrgDoesntExist() }, getBaseOrgRepository: () => { return new OrgUserNotUpdatedOrgDoesntExist() }, getBaseUserRepository: () => { return new NullUserRepo() }, getUserRepository: () => { return new NullUserRepo() } @@ -232,7 +231,6 @@ describe('Testing the PUT /org/:shortname/user/:username endpoint in Org Control app.route('/user-not-updated-doesnt-exist/:shortname/:username') .put((req, res, next) => { const factory = { - getOrgRepository: () => { return new OrgUserNotUpdatedUserDoesntExist() }, getBaseOrgRepository: () => { return new OrgUserNotUpdatedUserDoesntExist() }, getBaseUserRepository: () => { return new UserNotUpdatedUserDoesntExist() }, getUserRepository: () => { return new UserNotUpdatedUserDoesntExist() } @@ -279,7 +277,6 @@ describe('Testing the PUT /org/:shortname/user/:username endpoint in Org Control app.route('/user-not-updated-user-doesnt-exist/:shortname/:username') .put((req, res, next) => { const factory = { - getOrgRepository: () => { return new OrgUserNotUpdatedOrgQueryDoesntExist() }, getBaseOrgRepository: () => { return new OrgUserNotUpdatedOrgQueryDoesntExist() }, getBaseUserRepository: () => { return new UserNotUpdatedOrgQueryDoesntExist() }, getUserRepository: () => { return new UserNotUpdatedOrgQueryDoesntExist() } @@ -344,7 +341,6 @@ describe('Testing the PUT /org/:shortname/user/:username endpoint in Org Control app.route('/user-not-updated-requestor-not-admin-secretariat-user/:shortname/:username') .put((req, res, next) => { const factory = { - getOrgRepository: () => { return new Org() }, getBaseOrgRepository: () => { return new Org() }, getBaseUserRepository: () => { return new User() }, getUserRepository: () => { return new User() } @@ -411,7 +407,6 @@ describe('Testing the PUT /org/:shortname/user/:username endpoint in Org Control app.route('/user-not-updated-admin-changing-org/:shortname/:username') .put((req, res, next) => { const factory = { - getOrgRepository: () => { return new Org() }, getBaseOrgRepository: () => { return new Org() }, getBaseUserRepository: () => { return new User() }, getUserRepository: () => { return new User() } @@ -481,7 +476,6 @@ describe('Testing the PUT /org/:shortname/user/:username endpoint in Org Control app.route('/user-not-updated-requestor-different-admin/:shortname/:username') .put((req, res, next) => { const factory = { - getOrgRepository: () => { return new Org() }, getBaseOrgRepository: () => { return new Org() }, getBaseUserRepository: () => { return new User() }, getUserRepository: () => { return new User() } @@ -550,7 +544,6 @@ describe('Testing the PUT /org/:shortname/user/:username endpoint in Org Control app.route('/user-not-updated-cant-update-active-field/:shortname/:username') .put((req, res, next) => { const factory = { - getOrgRepository: () => { return new Org() }, getBaseOrgRepository: () => { return new Org() }, getBaseUserRepository: () => { return new User() }, getUserRepository: () => { return new User() } @@ -583,7 +576,6 @@ describe('Testing the PUT /org/:shortname/user/:username endpoint in Org Control app.route('/user-updated-adding-role-1/:shortname/:username') .put((req, res, next) => { const factory = { - getOrgRepository: () => { return new OrgUserUpdatedAddingRole() }, getBaseOrgRepository: () => { return new OrgUserUpdatedAddingRole() }, getBaseUserRepository: () => { return new UserUpdatedAddingRole() }, getUserRepository: () => { return new UserUpdatedAddingRole() } @@ -688,7 +680,6 @@ describe('Testing the PUT /org/:shortname/user/:username endpoint in Org Control app.route('/user-updated-adding-role-2/:shortname/:username') .put((req, res, next) => { const factory = { - getOrgRepository: () => { return new OrgUserUpdatedAddingRole() }, getBaseOrgRepository: () => { return new OrgUserUpdatedAddingRole() }, getBaseUserRepository: () => { return new UserUpdatedAddingRoleAlreadyExists() }, getUserRepository: () => { return new UserUpdatedAddingRoleAlreadyExists() } @@ -804,7 +795,6 @@ describe('Testing the PUT /org/:shortname/user/:username endpoint in Org Control app.route('/user-updated-removing-role-1/:shortname/:username') .put((req, res, next) => { const factory = { - getOrgRepository: () => { return new OrgUserUpdatedAddingRole() }, getBaseOrgRepository: () => { return new OrgUserUpdatedAddingRole() }, getBaseUserRepository: () => { return new UserUpdatedRemovingRole() }, getUserRepository: () => { return new UserUpdatedRemovingRole() } @@ -901,7 +891,6 @@ describe('Testing the PUT /org/:shortname/user/:username endpoint in Org Control app.route('/user-updated-removing-role-2/:shortname/:username') .put((req, res, next) => { const factory = { - getOrgRepository: () => { return new OrgUserUpdatedAddingRole() }, getBaseOrgRepository: () => { return new OrgUserUpdatedAddingRole() }, getBaseUserRepository: () => { return new UserUpdatedRemovingRoleAlreadyRemoved() }, getUserRepository: () => { return new UserUpdatedRemovingRoleAlreadyRemoved() } @@ -1010,7 +999,6 @@ describe('Testing the PUT /org/:shortname/user/:username endpoint in Org Control app.route('/user-updated-requestor-admin-deactivate-user/:shortname/:username') .put((req, res, next) => { const factory = { - getOrgRepository: () => { return new Org() }, getBaseOrgRepository: () => { return new Org() }, getBaseUserRepository: () => { return new User() }, getUserRepository: () => { return new User() } @@ -1114,7 +1102,6 @@ describe('Testing the PUT /org/:shortname/user/:username endpoint in Org Control app.route('/user-updated-requestor-user-username-changed/:shortname/:username') .put((req, res, next) => { const factory = { - getOrgRepository: () => { return new Org() }, getBaseOrgRepository: () => { return new Org() }, getBaseUserRepository: () => { return new User() }, getUserRepository: () => { return new User() } @@ -1193,7 +1180,6 @@ describe('Testing the PUT /org/:shortname/user/:username endpoint in Org Control app.route('/user-not-updated-no-parameters/:shortname/:username') .put((req, res, next) => { const factory = { - getOrgRepository: () => { return new OrgUserNotUpdatedOrgQueryDoesntExist() }, getBaseOrgRepository: () => { return new OrgUserNotUpdatedOrgQueryDoesntExist() }, getBaseUserRepository: () => { return new UserNotUpdatedNoQuery() }, getUserRepository: () => { return new UserNotUpdatedNoQuery() } From 997df47efef46530d477b63b6271e8f9c0ebc4e7 Mon Sep 17 00:00:00 2001 From: James Dalphond Date: Wed, 19 Aug 2026 09:57:52 -0400 Subject: [PATCH 12/20] Removing stray console.logs --- src/repositories/baseOrgRepositoryHelpers.js | 1 - test/integration-tests/cve/erlCheckPUTTest.js | 1 - 2 files changed, 2 deletions(-) diff --git a/src/repositories/baseOrgRepositoryHelpers.js b/src/repositories/baseOrgRepositoryHelpers.js index a6fdbc84b..b5059b477 100644 --- a/src/repositories/baseOrgRepositoryHelpers.js +++ b/src/repositories/baseOrgRepositoryHelpers.js @@ -168,7 +168,6 @@ async function createAuditLogEntry (registryOrg, originalRegistryOrgObject, requ { ...options, upsert: true } ) } - console.log('Audit entry created for registry object') } catch (auditError) { console.error('Audit entry creation failed:', auditError) } diff --git a/test/integration-tests/cve/erlCheckPUTTest.js b/test/integration-tests/cve/erlCheckPUTTest.js index bec8bd1c6..7ba63b703 100644 --- a/test/integration-tests/cve/erlCheckPUTTest.js +++ b/test/integration-tests/cve/erlCheckPUTTest.js @@ -19,7 +19,6 @@ describe('Testing PUT ERLCheck field', () => { before(async () => { cveId = await helpers.cveIdReserveHelper(requestLength, cveYear, shortName, batchType) await helpers.cveRequestAsCnaHelper(cveId) - console.log('HEre') }) context('ERL PUT Check Tests', () => { it('PUT CVE that is ERL Checked with correct details', async () => { From 748b67174fb9270b02646601b57257151fd4555e Mon Sep 17 00:00:00 2001 From: James Dalphond Date: Wed, 19 Aug 2026 09:58:32 -0400 Subject: [PATCH 13/20] Fixing fragile cve-id in reserved state test. --- test/integration-tests/cve-id/getCveIdTest.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/integration-tests/cve-id/getCveIdTest.js b/test/integration-tests/cve-id/getCveIdTest.js index 053b9b84f..6bb979fe5 100644 --- a/test/integration-tests/cve-id/getCveIdTest.js +++ b/test/integration-tests/cve-id/getCveIdTest.js @@ -9,10 +9,9 @@ const expect = chai.expect const constants = require('../constants.js') const helpers = require('../helpers.js') const app = require('../../../src/index.js') +const CveId = require('../../../src/model/cve-id') describe('Testing Get CVE-ID endpoint', () => { - // TODO: Update this test to dynamically calculate reserved count. - const RESESRVED_COUNT = 124 const YEAR_COUNT = 10 const PUB_YEAR_COUNT = 4 const TIME_WINDOW_COUNT = 40 @@ -59,6 +58,8 @@ describe('Testing Get CVE-ID endpoint', () => { }) }) it('Get all CVE-IDs in the RESERVED state', async () => { + const reservedCount = await CveId.countDocuments({ state: 'RESERVED' }).read('primary') + await chai.request(app) .get('/api/cve-id?state=RESERVED') .set(constants.headers) @@ -66,7 +67,7 @@ describe('Testing Get CVE-ID endpoint', () => { expect(err).to.be.undefined expect(res).to.have.status(200) expect(_.every(res.body.cve_ids, { state: 'RESERVED' })).to.be.true - expect(res.body.cve_ids).to.have.length(RESESRVED_COUNT) + expect(res.body.cve_ids).to.have.length(reservedCount) }) }) it('Get all CVE-IDs in the PUBLISHED state', async () => { From 0ea14e5217e4670a3add37a8e18c08f8c61fe650 Mon Sep 17 00:00:00 2001 From: James Dalphond Date: Wed, 19 Aug 2026 09:59:41 -0400 Subject: [PATCH 14/20] Add legacy user write contract integration coverage Update legacy user-capacity setup to use BaseOrg membership rather than legacy User records. Add coverage that verifies legacy user create, update, read, and reset-secret responses remain compatible while BaseUser and BaseOrg remain the sources of truth. --- .../integration-tests/org/postOrgUsersTest.js | 35 +++--- .../user/legacyUserWriteContractTest.js | 102 ++++++++++++++++++ 2 files changed, 124 insertions(+), 13 deletions(-) create mode 100644 test/integration-tests/user/legacyUserWriteContractTest.js diff --git a/test/integration-tests/org/postOrgUsersTest.js b/test/integration-tests/org/postOrgUsersTest.js index bc2f1fda7..a0e1e14d6 100644 --- a/test/integration-tests/org/postOrgUsersTest.js +++ b/test/integration-tests/org/postOrgUsersTest.js @@ -7,7 +7,6 @@ const { faker } = require('@faker-js/faker') const constants = require('../constants.js') const app = require('../../../src/index.js') const _ = require('lodash') -const User = require('../../../src/model/user') const BaseUser = require('../../../src/model/baseuser') const BaseOrg = require('../../../src/model/baseorg') // const RegistryUser = require('../../../src/model/registry-user.js') @@ -15,7 +14,6 @@ const BaseOrg = require('../../../src/model/baseorg') const shortName = { shortname: 'win_5' } describe('Testing user post endpoint', () => { - let orgUuid context('Positive Tests', () => { it('Allows creation of user', async () => { await chai.request(app) @@ -37,7 +35,6 @@ describe('Testing user post endpoint', () => { }) .then((res, err) => { expect(err).to.be.undefined - orgUuid = res.body.created.org_UUID }) }) it('Allows creation of user with registry enabled', async () => { @@ -58,7 +55,6 @@ describe('Testing user post endpoint', () => { }) .then((res, err) => { expect(err).to.be.undefined - orgUuid = res.body.created.org_UUID }) }) it('Adds registry-created users with authority admin roles to the org admins list', async () => { @@ -89,12 +85,18 @@ describe('Testing user post endpoint', () => { }) const registryOrg = await BaseOrg.findOne({ short_name: 'win_5' }) - const legacyUser = await User.findOne({ username, UUID: userUUID }) expect(registryOrg.admins).to.include(userUUID) - expect(legacyUser.authority.active_roles).to.include('ADMIN') + await chai.request(app) + .get(`/api/org/win_5/user/${username}`) + .set({ ...constants.headers, ...shortName }) + .then((res, err) => { + expect(err).to.be.undefined + expect(res).to.have.status(200) + expect(res.body.authority.active_roles).to.include('ADMIN') + }) }) - it('Syncs literal dotted legacy user fields into the registry collection', async () => { + it('Preserves literal dotted legacy user fields through the compatibility response', async () => { const username = 'dotnotationuser999@win_5.com' let userUUID @@ -117,18 +119,24 @@ describe('Testing user post endpoint', () => { userUUID = res.body.created.UUID }) - const legacyUser = await User.findOne({ username, UUID: userUUID }) const registryUser = await BaseUser.findOne({ username, UUID: userUUID }) const registryOrg = await BaseOrg.findOne({ short_name: 'win_5' }) - expect(legacyUser).to.exist expect(registryUser).to.exist - expect(legacyUser.name.first).to.equal('AliceDot') expect(registryUser.name.first).to.equal('AliceDot') - expect(legacyUser.name.last).to.equal('LegacyDot') expect(registryUser.name.last).to.equal('LegacyDot') - expect(legacyUser.authority.active_roles).to.include('ADMIN') expect(registryOrg.admins).to.include(userUUID) + + await chai.request(app) + .get(`/api/org/win_5/user/${username}`) + .set({ ...constants.headers, ...shortName }) + .then((res, err) => { + expect(err).to.be.undefined + expect(res).to.have.status(200) + expect(res.body.name.first).to.equal('AliceDot') + expect(res.body.name.last).to.equal('LegacyDot') + expect(res.body.authority.active_roles).to.include('ADMIN') + }) }) }) context('Negative Tests', () => { @@ -335,7 +343,8 @@ describe('Testing user post endpoint', () => { }) it('Fails creation of user for trying to add the 101th user', async function () { this.timeout(70000) - let counter = await User.where({ org_UUID: orgUuid }).countDocuments().exec() + const org = await BaseOrg.findOne({ short_name: 'win_5' }) + let counter = org.users.length do { const firstName = faker.name.firstName() const lastName = faker.name.lastName() diff --git a/test/integration-tests/user/legacyUserWriteContractTest.js b/test/integration-tests/user/legacyUserWriteContractTest.js new file mode 100644 index 000000000..c91561860 --- /dev/null +++ b/test/integration-tests/user/legacyUserWriteContractTest.js @@ -0,0 +1,102 @@ +/* eslint-disable no-unused-expressions */ +const chai = require('chai') +chai.use(require('chai-http')) +const expect = chai.expect +const { v4: uuidv4 } = require('uuid') + +const constants = require('../constants.js') +const app = require('../../../src/index.js') +const BaseOrg = require('../../../src/model/baseorg') +const BaseUser = require('../../../src/model/baseuser') + +const secretariatHeaders = { ...constants.headers, 'content-type': 'application/json' } + +describe('Legacy user write response contract', () => { + it('creates, updates, and resets a user while preserving legacy and registry projections', async () => { + const orgShortName = 'range_4' + const username = `legacy-write-${uuidv4()}@example.com` + let userUUID + + await chai.request(app) + .post(`/api/org/${orgShortName}/user`) + .set(secretariatHeaders) + .send({ + username, + active: true, + name: { + first: 'Legacy', + last: 'Contract' + }, + authority: { + active_roles: ['ADMIN'] + } + }) + .then(res => { + expect(res).to.have.status(200) + expect(res.body.created).to.include({ username, active: true }) + expect(res.body.created).to.have.nested.property('authority.active_roles').that.includes('ADMIN') + expect(res.body.created).to.have.nested.property('time.created') + expect(res.body.created).to.have.nested.property('time.modified') + expect(res.body.created).to.have.property('org_UUID') + expect(res.body.created).to.have.property('secret').that.is.a('string') + expect(res.body.created).to.not.have.property('role') + userUUID = res.body.created.UUID + }) + + const createdBaseUser = await BaseUser.findOne({ UUID: userUUID }) + const orgAfterCreate = await BaseOrg.findOne({ short_name: orgShortName }) + expect(createdBaseUser).to.exist + expect(createdBaseUser.status).to.equal('active') + expect(orgAfterCreate.users).to.include(userUUID) + expect(orgAfterCreate.admins).to.include(userUUID) + + await chai.request(app) + .put(`/api/org/${orgShortName}/user/${encodeURIComponent(username)}?name.first=Updated&active=false`) + .set(secretariatHeaders) + .then(res => { + expect(res).to.have.status(200) + expect(res.body.updated).to.include({ UUID: userUUID, username, active: false, role: 'ADMIN' }) + expect(res.body.updated).to.have.nested.property('name.first', 'Updated') + expect(res.body.updated).to.have.nested.property('authority.active_roles').that.includes('ADMIN') + expect(res.body.updated).to.have.nested.property('time.created') + expect(res.body.updated).to.have.nested.property('time.modified') + expect(res.body.updated).to.have.property('org_UUID') + expect(res.body.updated).to.not.have.property('secret') + }) + + await chai.request(app) + .get(`/api/org/${orgShortName}/user/${encodeURIComponent(username)}`) + .set(secretariatHeaders) + .then(res => { + expect(res).to.have.status(200) + expect(res.body).to.include({ UUID: userUUID, username, active: false }) + expect(res.body).to.have.nested.property('name.first', 'Updated') + expect(res.body).to.have.nested.property('authority.active_roles').that.includes('ADMIN') + expect(res.body).to.not.have.property('role') + expect(res.body).to.not.have.property('secret') + }) + + await chai.request(app) + .get(`/api/registry/org/${orgShortName}/user/${encodeURIComponent(username)}`) + .set(secretariatHeaders) + .then(res => { + expect(res).to.have.status(200) + expect(res.body).to.include({ UUID: userUUID, username, status: 'inactive', role: 'ADMIN' }) + expect(res.body).to.have.nested.property('name.first', 'Updated') + expect(res.body).to.not.have.property('secret') + }) + + await chai.request(app) + .put(`/api/org/${orgShortName}/user/${encodeURIComponent(username)}/reset_secret`) + .set(secretariatHeaders) + .then(res => { + expect(res).to.have.status(200) + expect(res.body).to.have.all.keys('API-secret') + expect(res.body['API-secret']).to.be.a('string') + }) + + const updatedBaseUser = await BaseUser.findOne({ UUID: userUUID }) + expect(updatedBaseUser.status).to.equal('inactive') + expect(updatedBaseUser.name.first).to.equal('Updated') + }) +}) From 83b296683db2ab32d957c9db5b233db215e4d2d0 Mon Sep 17 00:00:00 2001 From: James Dalphond Date: Wed, 19 Aug 2026 10:50:42 -0400 Subject: [PATCH 15/20] Remove legacy UserRepository dependency from BaseUser flows Project legacy user reads and write responses directly from BaseUser and BaseOrg membership/admin data, eliminating UserRepository and RegistryUser usage from BaseUserRepository create, update, full update, reset-secret, delete, lookup, and authorization paths. Normalize legacy dotted user fields during creation and re-read the owning BaseOrg before projecting legacy responses so names and ADMIN roles match the persisted BaseUser/BaseOrg state. Avoid logging one-time user secrets in org creation audit records. Release BaseOrg reservation locks before sending post-lock CVE-ID responses, while retaining finally cleanup for exceptions, so an immediately-following reservation is not incorrectly rejected as in progress. Update unit and integration tests for BaseUser-only behavior, legacy write compatibility, and clearer reservation response assertions. --- .../cve-id.controller/cve-id.controller.js | 47 ++- .../org.controller/org.controller.js | 2 +- src/repositories/baseUserRepository.js | 348 +++++------------- .../cve-id/reserveCveIdTest.js | 5 +- .../user/legacyUserWriteContractTest.js | 3 + test/integration-tests/user/updateUserTest.js | 11 +- .../baseUserRepositoryFormatSemanticsTest.js | 11 +- test/unit-tests/user/userCreateTest.js | 13 +- 8 files changed, 151 insertions(+), 289 deletions(-) diff --git a/src/controller/cve-id.controller/cve-id.controller.js b/src/controller/cve-id.controller/cve-id.controller.js index c47c6eaea..0d1f5340f 100644 --- a/src/controller/cve-id.controller/cve-id.controller.js +++ b/src/controller/cve-id.controller/cve-id.controller.js @@ -203,6 +203,21 @@ async function reserveCveId (req, res, next) { let year const baseOrgRepo = req.ctx.repositories.getBaseOrgRepository() + // Release before sending a post-lock response. A client may immediately make a + // second reservation after receiving the first response; deferring release to + // finally could incorrectly reject it as already in progress. finally also + // invokes this helper as exception-path cleanup. + const releaseReservationLock = async () => { + if (!shortName || !hasLock) return + + await baseOrgRepo.findOneAndUpdate( + { short_name: shortName, in_use: true }, + { $set: { in_use: false } }, + { new: true } + ) + hasLock = false + } + try { Object.keys(req.ctx.query).forEach(k => { const key = k.toLowerCase() @@ -262,23 +277,23 @@ async function reserveCveId (req, res, next) { } if (batchType === undefined) { // priority - await priorityReservation(year, amount, shortName, orgShortName, requester, payload.available, res, req) + await priorityReservation(year, amount, shortName, orgShortName, requester, payload.available, res, req, releaseReservationLock) } else if (batchType === 'sequential') { - await sequentialReservation(year, amount, shortName, orgShortName, requester, payload.available, false, res, req) + await sequentialReservation(year, amount, shortName, orgShortName, requester, payload.available, false, res, req, releaseReservationLock) } else if (batchType === 'non-sequential' || batchType === 'nonsequential') { if (amount > getConstants().NONSEQUENTIAL_MAX_AMOUNT) { + await releaseReservationLock() return res.status(403).json(error.overNonSequentialMaxAmount()) } - await nonSequentialReservation(year, amount, shortName, orgShortName, requester, payload.available, res, req) + await nonSequentialReservation(year, amount, shortName, orgShortName, requester, payload.available, res, req, releaseReservationLock) } else { + await releaseReservationLock() return res.status(400).json(error.invalidBatchType()) } } catch (err) { next(err) } finally { - if (shortName && hasLock) { - await baseOrgRepo.findOneAndUpdate({ short_name: shortName, in_use: true }, { $set: { in_use: false } }, { new: true }) // release lock for org - } + await releaseReservationLock() } } @@ -480,7 +495,7 @@ async function createCveIdRange (req, res, next) { } } -async function priorityReservation (year, amount, shortName, orgShortName, requester, availableIds, res, req) { +async function priorityReservation (year, amount, shortName, orgShortName, requester, availableIds, res, req, releaseReservationLock) { const CONSTANTS = getConstants() const cveIdRangeRepo = req.ctx.repositories.getCveIdRangeRepository() const reqUUID = req.ctx.uuid @@ -498,12 +513,14 @@ async function priorityReservation (year, amount, shortName, orgShortName, reque if (!successfullyReservedYear) { logger.info({ uuid: req.ctx.uuid, message: 'CVE IDs for year ' + year + ' failed to be automatically reserved at this time.' }) res.header(CONSTANTS.QUOTA_HEADER, availableIds) + await releaseReservationLock() return res.status(403).json(error.cannotReserveForYear(year)) } result = await cveIdRangeRepo.findOne({ cve_year: year }) } else { logger.info({ uuid: req.ctx.uuid, message: 'CVE IDs for year ' + year + ' cannot be reserved at this time.' }) res.header(CONSTANTS.QUOTA_HEADER, availableIds) + await releaseReservationLock() return res.status(403).json(error.cannotReserveForYear(year)) } } @@ -515,7 +532,7 @@ async function priorityReservation (year, amount, shortName, orgShortName, reque if (!result) { isFull = true logger.info({ uuid: req.ctx.uuid, message: 'Priority id block is full for year ' + year + ', reserving id in sequential block.' }) - await sequentialReservation(year, amount, shortName, orgShortName, requester, availableIds, true, res, req) + await sequentialReservation(year, amount, shortName, orgShortName, requester, availableIds, true, res, req, releaseReservationLock) } if (!isFull) { @@ -566,11 +583,12 @@ async function priorityReservation (year, amount, shortName, orgShortName, reque logger.info(JSON.stringify(payload)) const q = availableIds - amount res.header(CONSTANTS.QUOTA_HEADER, q) + await releaseReservationLock() return res.status(200).json({ cve_ids: cveIdDocuments, meta: { remaining_quota: q } }) } } -async function sequentialReservation (year, amount, shortName, orgShortName, requester, availableIds, isPriority, res, req) { +async function sequentialReservation (year, amount, shortName, orgShortName, requester, availableIds, isPriority, res, req, releaseReservationLock) { const CONSTANTS = getConstants() const cveIdRangeRepo = req.ctx.repositories.getCveIdRangeRepository() const reqUUID = req.ctx.uuid @@ -588,12 +606,14 @@ async function sequentialReservation (year, amount, shortName, orgShortName, req if (!successfullyReservedYear) { logger.info({ uuid: req.ctx.uuid, message: 'CVE IDs for year ' + year + ' failed to be automatically reserved at this time.' }) res.header(CONSTANTS.QUOTA_HEADER, availableIds) + await releaseReservationLock() return res.status(403).json(error.cannotReserveForYear(year)) } result = await cveIdRangeRepo.findOne({ cve_year: year }) } else { logger.info({ uuid: req.ctx.uuid, message: 'CVE IDs for year ' + year + ' cannot be reserved at this time.' }) res.header(CONSTANTS.QUOTA_HEADER, availableIds) + await releaseReservationLock() return res.status(403).json(error.cannotReserveForYear(year)) } } @@ -612,6 +632,7 @@ async function sequentialReservation (year, amount, shortName, orgShortName, req } res.header(CONSTANTS.QUOTA_HEADER, availableIds) + await releaseReservationLock() return res.status(403).json(error.yearRangeFull(year)) } @@ -672,11 +693,12 @@ async function sequentialReservation (year, amount, shortName, orgShortName, req logger.info(JSON.stringify(payload)) const q = availableIds - amount res.header(CONSTANTS.QUOTA_HEADER, q) + await releaseReservationLock() return res.status(200).json({ cve_ids: cveIdDocuments, meta: { remaining_quota: q } }) } } -async function nonSequentialReservation (year, amount, shortName, orgShortName, requester, availableIds, res, req) { +async function nonSequentialReservation (year, amount, shortName, orgShortName, requester, availableIds, res, req, releaseReservationLock) { const CONSTANTS = getConstants() const cveIdRepo = req.ctx.repositories.getCveIdRepository() const cveIdRangeRepo = req.ctx.repositories.getCveIdRangeRepository() @@ -698,12 +720,14 @@ async function nonSequentialReservation (year, amount, shortName, orgShortName, if (!successfullyReservedYear) { logger.info({ uuid: req.ctx.uuid, message: 'CVE IDs for year ' + year + ' failed to be automatically reserved at this time.' }) res.header(CONSTANTS.QUOTA_HEADER, availableIds) + await releaseReservationLock() return res.status(403).json(error.cannotReserveForYear(year)) } result = await cveIdRangeRepo.findOne({ cve_year: year }) } else { logger.info({ uuid: req.ctx.uuid, message: 'CVE IDs for year ' + year + ' cannot be reserved at this time.' }) res.header(CONSTANTS.QUOTA_HEADER, availableIds) + await releaseReservationLock() return res.status(403).json(error.cannotReserveForYear(year)) } } @@ -718,6 +742,7 @@ async function nonSequentialReservation (year, amount, shortName, orgShortName, if (isFull) { logger.error(JSON.stringify({ message: 'The cve id non-sequential block is full for year ' + year + '. No more sequential ids can be reserved at this time.' })) res.header(CONSTANTS.QUOTA_HEADER, availableIds) + await releaseReservationLock() return res.status(403).json(error.yearRangeFull(year)) } @@ -760,6 +785,7 @@ async function nonSequentialReservation (year, amount, shortName, orgShortName, if (isFull) { logger.info({ uuid: req.ctx.uuid, message: 'Only ' + counter + ' cve ids were reserved because there are not enough ids in the CVE ID non-sequential block. Non-sequential CVE IDs were reserved for \'' + shortName + '\' organization on behalf of \'' + orgShortName + '\' organization.', cve_ids: cveIdDocumentsUUID.map((doc) => { return doc.cve_id }) }) res.header(CONSTANTS.QUOTA_HEADER, q) + await releaseReservationLock() return res.status(206).json(error.reservedPartialAmount(counter, cveIdDocuments), { meta: { remaining_quota: q } }) } @@ -786,6 +812,7 @@ async function nonSequentialReservation (year, amount, shortName, orgShortName, logger.info(JSON.stringify(payload)) res.header(CONSTANTS.QUOTA_HEADER, q) + await releaseReservationLock() return res.status(200).json({ cve_ids: cveIdDocuments, meta: { remaining_quota: q } }) } } diff --git a/src/controller/org.controller/org.controller.js b/src/controller/org.controller/org.controller.js index c75ff1310..268cc3366 100644 --- a/src/controller/org.controller/org.controller.js +++ b/src/controller/org.controller/org.controller.js @@ -498,7 +498,7 @@ async function createUser (req, res, next) { req_UUID: req.ctx.uuid, org_UUID: returnValue.org_UUID, user_UUID: returnValue.UUID, - user: returnValue + user: _.omit(returnValue, ['secret']) } logger.info(JSON.stringify(payload)) diff --git a/src/repositories/baseUserRepository.js b/src/repositories/baseUserRepository.js index 31ce80110..573672cee 100644 --- a/src/repositories/baseUserRepository.js +++ b/src/repositories/baseUserRepository.js @@ -4,9 +4,7 @@ const BaseOrgRepository = require('./baseOrgRepository') const uuid = require('uuid') const argon2 = require('argon2') const BaseOrgModel = require('../model/baseorg') -const RegistryUser = require('../model/registryuser') const cryptoRandomString = require('crypto-random-string') -const UserRepository = require('./userRepository') const getConstants = require('../constants').getConstants const _ = require('lodash') const { toLegacyUser } = require('../utils/userCompatibility') @@ -23,10 +21,28 @@ const toPlainObject = (value) => { } const userHasAdminRole = (user) => { - const roles = _.flattenDeep(_.compact([user?.role, user?.authority?.active_roles])) + const roles = _.flattenDeep(_.compact([ + user?.role, + user?.authority?.active_roles, + user?.['authority.active_roles'] + ])) return roles.some(role => String(role).toUpperCase() === 'ADMIN') } +const getIncomingName = (user) => { + const name = _.isPlainObject(user?.name) ? { ...user.name } : {} + const nameFields = ['first', 'middle', 'last', 'suffix'] + + nameFields.forEach(field => { + const dottedField = `name.${field}` + if (Object.hasOwn(user, dottedField)) { + name[field] = user[dottedField] + } + }) + + return Object.keys(name).length > 0 ? name : undefined +} + /** * @function setAggregateRegistryUserObj * @description Constructs the aggregation pipeline for registry user objects. @@ -149,7 +165,6 @@ class BaseUserRepository extends BaseRepository { * @returns {Promise} The user object or null if not found. */ async findOneByUserNameAndOrgUUID (username, orgUUID, options = {}, isLegacyObject = false) { - const legacyUserRepo = new UserRepository() const org = await BaseOrgModel.findOne({ UUID: orgUUID }, null, options) if (!org || !Array.isArray(org.users)) { return null @@ -158,7 +173,7 @@ class BaseUserRepository extends BaseRepository { const user = await BaseUser.findOne({ username: username, UUID: { $in: org.users } }, null, options) if (isLegacyObject && user) { - return await legacyUserRepo.findOneByUUID(user.UUID) || null + return toLegacyUser(user, org) } return user || null } @@ -187,14 +202,14 @@ class BaseUserRepository extends BaseRepository { * @returns {Promise} The user object or null if not found. */ async findUserByUUID (uuid, options = {}, isLegacyObject = false) { - const legacyUserRepo = new UserRepository() const user = await BaseUser.findOne({ UUID: uuid }, null, options) if (!user) { return null } if (isLegacyObject) { - return await legacyUserRepo.findOneByUUID(user.UUID) || null + const org = await BaseOrgModel.findOne({ users: uuid }, null, options) + return toLegacyUser(user, org) } return user || null } @@ -218,7 +233,7 @@ class BaseUserRepository extends BaseRepository { * @param {string} userUUID - The user UUID to check. * @param {string} orgUUID - The organization UUID to check. * @param {object} [options={}] - Optional settings for the repository query. - * @param {boolean} [isLegacyObject=false] - If true, retrieves the legacy user object for role fallback. + * @param {boolean} [isLegacyObject=false] - Unused parameter. * @returns {Promise} True if the user is an admin of the org, false otherwise. */ async isUserAdminOfOrgUUID (userUUID, orgUUID, options = {}, isLegacyObject = false) { @@ -231,23 +246,14 @@ class BaseUserRepository extends BaseRepository { return false } - if (Array.isArray(org.admins) && org.admins.includes(userUUID)) { - return true - } - - if (!Array.isArray(org.users) || !org.users.includes(userUUID)) { - return false - } - - const user = await this.findUserByUUID(userUUID, options, isLegacyObject) - return user?.role === 'ADMIN' || (Array.isArray(user?.authority?.active_roles) && user.authority.active_roles.includes('ADMIN')) + return Array.isArray(org.admins) && org.admins.includes(userUUID) } /** * @async * @function deleteUserByUUID - * @description Delete a user by UUID from both BaseUser and RegistryUser collections, - * and remove the user reference from any organizations. + * @description Delete a user by UUID from BaseUser and remove the user reference + * from any organizations. * * @param {string} uuid - The UUID of the user to delete. * @param {object} options - Mongoose options for the delete operations. @@ -257,9 +263,6 @@ class BaseUserRepository extends BaseRepository { // Delete from BaseUser collection const deleteResult = await BaseUser.deleteOne({ UUID: uuid }, options) - // Delete from RegistryUser collection - await RegistryUser.deleteOne({ UUID: uuid }, options) - // Remove user from any organization’s users and admins arrays const { createAuditLogEntry } = require('./baseOrgRepositoryHelpers') const orgs = await BaseOrgModel.find({ $or: [{ users: uuid }, { admins: uuid }] }) @@ -429,7 +432,7 @@ class BaseUserRepository extends BaseRepository { /** * @async * @function createUser - * @description Creates a new user in both registry and legacy systems. + * @description Creates a new BaseUser and projects the requested API format. * @param {string} orgShortName - The short name of the organization. * @param {object} incomingUser - The user object to create. * @param {object} [options={}] - Optional settings for the repository query. @@ -438,11 +441,6 @@ class BaseUserRepository extends BaseRepository { */ async createUser (orgShortName, incomingUser, options = {}, isLegacyObject = false, requestingUserUUID = null) { const { deepRemoveEmpty } = require('../utils/utils') - // TO-DO: org_UUID is not necessarily the shortname. Is this info lost during conversion? - let legacyObjectRaw = null - let registryObjectRaw = null - let registryObject = null - const legacyUserRepo = new UserRepository() const baseOrgRepository = new BaseOrgRepository() const sharedUUID = uuid.v4() @@ -456,51 +454,32 @@ class BaseUserRepository extends BaseRepository { if (!isLegacyObject && incomingUser.status === 'inactive') isConsideredInactive = true if (isLegacyObject && (incomingUser.active === false || String(incomingUser.active).toLowerCase() === 'false')) isConsideredInactive = true - // Get UUID of org, that is having the user added to it. - const existingOrg = await baseOrgRepository.findOneByShortName(orgShortName) + const baseUser = new BaseUser({ + UUID: sharedUUID, + username: incomingUser.username, + secret, + name: getIncomingName(incomingUser), + status: isConsideredInactive ? 'inactive' : 'active' + }) + const savedUser = await baseUser.save(options) + const isAdmin = userHasAdminRole(incomingUser) + + await baseOrgRepository.addUserToOrg( + orgShortName, + sharedUUID, + isAdmin, + options, + false, + requestingUserUUID + ) if (isLegacyObject) { - legacyObjectRaw = incomingUser - legacyObjectRaw.secret = secret - legacyObjectRaw.active = !isConsideredInactive - legacyObjectRaw.org_UUID = existingOrg.UUID - - const legacyObject = await legacyUserRepo.updateByUserNameAndOrgUUID(incomingUser.username, existingOrg.UUID, legacyObjectRaw, { ...options, upsert: true, new: true }) - legacyObjectRaw = toPlainObject(legacyObject) - registryObjectRaw = this.convertLegacyToRegistry(legacyObjectRaw) - registryObjectRaw.secret = secret - registryObjectRaw.status = isConsideredInactive ? 'inactive' : 'active' - } else { - registryObjectRaw = incomingUser - registryObjectRaw.secret = secret - registryObjectRaw.status = isConsideredInactive ? 'inactive' : 'active' + const owningOrg = await baseOrgRepository.findOneByShortName(orgShortName, options) + const legacyUser = toLegacyUser(savedUser, owningOrg) + legacyUser.secret = randomKey + return legacyUser } - - const registryUserToSave = new RegistryUser(registryObjectRaw) - - registryObject = await registryUserToSave.save(options) - const registryObjectPlain = toPlainObject(registryObject) - - if (!isLegacyObject) { - const legacyRole = userHasAdminRole(incomingUser) ? 'ADMIN' : incomingUser.role - legacyObjectRaw = this.convertRegistryToLegacy({ ...registryObjectPlain, role: legacyRole }) - legacyObjectRaw.secret = secret - legacyObjectRaw.active = !isConsideredInactive - legacyObjectRaw.org_UUID = existingOrg.UUID - await legacyUserRepo.updateByUserNameAndOrgUUID(incomingUser.username, existingOrg.UUID, legacyObjectRaw, { ...options, upsert: true }) - } - - await baseOrgRepository.addUserToOrg(orgShortName, incomingUser.UUID, (userHasAdminRole(incomingUser) || userHasAdminRole(legacyObjectRaw)), options, false, requestingUserUUID) - - if (isLegacyObject) { - legacyObjectRaw.secret = randomKey - legacyObjectRaw.org_UUID = existingOrg.UUID - delete legacyObjectRaw._id - delete legacyObjectRaw.__v - delete legacyObjectRaw.role - return legacyObjectRaw - } - const rawRegistryUserJson = registryObject.toObject() + const rawRegistryUserJson = toPlainObject(savedUser) rawRegistryUserJson.secret = randomKey delete rawRegistryUserJson._id delete rawRegistryUserJson.__v @@ -523,43 +502,27 @@ class BaseUserRepository extends BaseRepository { async updateUser (username, orgShortname, incomingParameters, options = {}, isLegacyObject = false, requestingUserUUID = null) { const { deepRemoveEmpty } = require('../utils/utils') const baseOrgRepository = new BaseOrgRepository() - const legacyUserRepo = new UserRepository() const { createAuditLogEntry } = require('./baseOrgRepositoryHelpers') const registryOrg = await baseOrgRepository.getOrgObject(orgShortname, false, options) const originalRegistryOrg = registryOrg.toObject() - - const legacyUser = await legacyUserRepo.findOneByUserNameAndOrgUUID(username, registryOrg.UUID, null, options) const registryUser = await this.findOneByUsernameAndOrgShortname(username, orgShortname, options, false) - if (!registryUser && !legacyUser) { + if (!registryUser) { throw new Error('User not found') } - // Safely assign usernames defensively - if (registryUser) { - registryUser.username = incomingParameters?.new_username ?? registryUser.username - } - if (legacyUser) { - legacyUser.username = incomingParameters?.new_username ?? legacyUser.username - } + registryUser.username = incomingParameters?.new_username ?? registryUser.username if (incomingParameters?.active != null) { const isConsideredActive = incomingParameters.active === true || String(incomingParameters.active).toLowerCase() === 'true' - if (registryUser) { - registryUser.status = isConsideredActive ? 'active' : 'inactive' - } - if (legacyUser) { - legacyUser.active = incomingParameters.active ?? legacyUser.active - } + registryUser.status = isConsideredActive ? 'active' : 'inactive' } ['name.last', 'name.first', 'name.middle', 'name.suffix'].forEach(field => { - if (registryUser) _.set(registryUser, field, _.get(incomingParameters, field, _.get(registryUser, field, ''))) - if (legacyUser) _.set(legacyUser, field, _.get(incomingParameters, field, _.get(legacyUser, field, ''))) + _.set(registryUser, field, _.get(incomingParameters, field, _.get(registryUser, field, ''))) }) - // Get the UUID from whichever user object actually exists - const userUUID = registryUser?.UUID ?? legacyUser?.UUID + const userUUID = registryUser.UUID const rolesToAdd = _.flattenDeep(_.compact(_.get(incomingParameters, 'active_roles.add'))) const rolesToRemove = _.flattenDeep(_.compact(_.get(incomingParameters, 'active_roles.remove'))) @@ -577,16 +540,9 @@ class BaseUserRepository extends BaseRepository { registryOrg.admins.addToSet(userUUID) } - // Handle roles calculation fallback - const initialRoles = legacyUser?.authority?.active_roles ?? [] + const initialRoles = registryOrg.admins?.includes(userUUID) ? ['ADMIN'] : [] const finalRoles = [...new Set([...initialRoles, ...rolesToAdd])].filter(role => !rolesToRemove.includes(role)) - - if (registryUser) { - registryUser.role = finalRoles[0] ?? '' - } - if (legacyUser) { - _.set(legacyUser, 'authority.active_roles', finalRoles) - } + let owningOrg = registryOrg if (incomingParameters?.org_short_name && userUUID) { if (Array.isArray(registryOrg.users)) { @@ -603,7 +559,7 @@ class BaseUserRepository extends BaseRepository { } newOrg.users.addToSet(userUUID) - const isUserAdmin = registryUser?.role?.includes('ADMIN') || finalRoles.includes('ADMIN') + const isUserAdmin = finalRoles.includes('ADMIN') if (isUserAdmin) { if (!Array.isArray(newOrg.admins)) { newOrg.admins = [] @@ -611,17 +567,11 @@ class BaseUserRepository extends BaseRepository { newOrg.admins.addToSet(userUUID) } - if (legacyUser) { - legacyUser.org_UUID = newOrg.UUID - } await newOrg.save(options) if (requestingUserUUID) { await createAuditLogEntry(newOrg, originalNewOrg, requestingUserUUID, options) } - } - - if (registryUser) { - delete registryUser.role + owningOrg = newOrg } await registryOrg.save(options) @@ -629,22 +579,15 @@ class BaseUserRepository extends BaseRepository { await createAuditLogEntry(registryOrg, originalRegistryOrg, requestingUserUUID, options) } - // Save only records that exist - if (legacyUser) await legacyUser.save(options) - if (registryUser) await registryUser.save(options) + await registryUser.save(options) if (isLegacyObject) { - if (!legacyUser) throw new Error('Legacy record missing; cannot return legacy format.') - const plainJavascriptLegacyUser = legacyUser.toObject() - plainJavascriptLegacyUser.role = finalRoles[0] ?? '' - delete plainJavascriptLegacyUser.__v - delete plainJavascriptLegacyUser._id - delete plainJavascriptLegacyUser.secret - return plainJavascriptLegacyUser + const legacyUser = toLegacyUser(registryUser, owningOrg) + legacyUser.role = finalRoles[0] ?? '' + return legacyUser } - if (!registryUser) throw new Error('Registry record missing; cannot return registry format.') - const plainJavascriptRegistryUser = registryUser.toObject() + const plainJavascriptRegistryUser = toPlainObject(registryUser) delete plainJavascriptRegistryUser.__v delete plainJavascriptRegistryUser._id delete plainJavascriptRegistryUser.__t @@ -663,50 +606,38 @@ class BaseUserRepository extends BaseRepository { * @returns {Promise} The updated user object. */ async updateUserFull (identifier, incomingUser, options = {}, isLegacyObject = false, requestingUserUUID = null) { - const legacyUserRepo = new UserRepository() - - const registryUser = await this.findUserByUUID(identifier, options) - const legacyUser = await legacyUserRepo.findOneByUUID(identifier) - - // Fail only if completely missing everywhere - if (!registryUser && !legacyUser) { - throw new Error('User not found in any repository') + const user = await this.findUserByUUID(identifier, options) + if (!user) { + throw new Error('User not found') } - const { ...incomingUserBody } = incomingUser - const legacyObjectRaw = isLegacyObject ? incomingUserBody : this.convertRegistryToLegacy(incomingUserBody) - const registryObjectRaw = isLegacyObject ? this.convertLegacyToRegistry(incomingUserBody) : incomingUserBody - - const protectedFieldsRegistry = ['_id', 'UUID', '__v', 'secret', 'created', 'last_updated'] - const protectedFieldsLegacy = ['_id', 'UUID', '__v', 'secret', 'time', 'org_UUID'] - - let updatedRegistryUser = null - let updatedLegacyUser = null - - if (registryUser) { - updatedRegistryUser = registryUser.overwrite(_.mergeWith(_.pick(registryUser.toObject(), protectedFieldsRegistry), _.omit(registryObjectRaw, protectedFieldsRegistry), skipNulls)) - if (updatedRegistryUser.status !== 'active') { - updatedRegistryUser.status = 'inactive' - } + const incomingBaseUser = isLegacyObject + ? { + username: incomingUser.username, + name: incomingUser.name, + status: incomingUser.active === false || String(incomingUser.active).toLowerCase() === 'false' ? 'inactive' : 'active' + } + : incomingUser + const protectedFields = ['_id', 'UUID', '__v', 'secret', 'created', 'last_updated'] + const updatedUser = user.overwrite( + _.mergeWith( + _.pick(user.toObject(), protectedFields), + _.omit(incomingBaseUser, protectedFields), + skipNulls + ) + ) + if (updatedUser.status !== 'active') { + updatedUser.status = 'inactive' } - if (legacyUser) { - updatedLegacyUser = legacyUser.overwrite(_.mergeWith(_.pick(legacyUser.toObject(), protectedFieldsLegacy), _.omit(legacyObjectRaw, protectedFieldsLegacy), skipNulls)) - // Align status from incoming payload or resolved registry state - const targetStatus = registryUser ? updatedRegistryUser.status : (isLegacyObject ? 'active' : registryObjectRaw.status) - updatedLegacyUser.active = (targetStatus === 'active') - } + let owningOrg = null try { if (incomingUser.org_short_name) { const baseOrgRepository = new BaseOrgRepository() const { createAuditLogEntry } = require('./baseOrgRepositoryHelpers') - // Prefer the legacy association while dual-write is active, then fall back to registry org membership. - let currentOrgUUID = legacyUser?.org_UUID || registryUser?.org_UUID - if (!currentOrgUUID) { - currentOrgUUID = await baseOrgRepository.getOrgUUIDByUserUUID(identifier, options) - } + const currentOrgUUID = await baseOrgRepository.getOrgUUIDByUserUUID(identifier, options) const currentOrg = currentOrgUUID ? await baseOrgRepository.findOneByUUID(currentOrgUUID) : null const newOrg = await baseOrgRepository.findOneByShortName(incomingUser.org_short_name) const wasAdmin = currentOrg?.admins?.includes(identifier) ?? false @@ -729,40 +660,35 @@ class BaseUserRepository extends BaseRepository { if (!Array.isArray(newOrg.users)) newOrg.users = [] newOrg.users.addToSet(identifier) - const isAdmin = wasAdmin || updatedRegistryUser?.role === 'ADMIN' || (updatedLegacyUser?.authority?.active_roles?.includes('ADMIN')) - if (isAdmin) { + if (wasAdmin) { if (!Array.isArray(newOrg.admins)) { newOrg.admins = [] } newOrg.admins.addToSet(identifier) } - if (updatedLegacyUser) { - updatedLegacyUser.org_UUID = newOrg.UUID - } - await newOrg.save(options) if (requestingUserUUID) { await createAuditLogEntry(newOrg, originalNewOrg, requestingUserUUID, options) } + owningOrg = newOrg } - // Conditionally save records - if (updatedLegacyUser) await updatedLegacyUser.save(options) - if (updatedRegistryUser) await updatedRegistryUser.save(options) + await updatedUser.save(options) } catch (error) { throw new Error('Failed to update user: ' + error.message) } if (isLegacyObject) { - if (!updatedLegacyUser) throw new Error('Legacy record missing; cannot output legacy format.') - const plain = updatedLegacyUser.toObject() - delete plain._id; delete plain.__v; delete plain.secret - return plain + if (!owningOrg) { + const baseOrgRepository = new BaseOrgRepository() + const orgUUID = await baseOrgRepository.getOrgUUIDByUserUUID(identifier, options) + owningOrg = orgUUID ? await baseOrgRepository.findOneByUUID(orgUUID, options) : null + } + return toLegacyUser(updatedUser, owningOrg) } - if (!updatedRegistryUser) throw new Error('Registry record missing; cannot output registry format.') - const plainJsRegistryUser = updatedRegistryUser.toObject() + const plainJsRegistryUser = toPlainObject(updatedUser) delete plainJsRegistryUser._id; delete plainJsRegistryUser.__v; delete plainJsRegistryUser.secret; delete plainJsRegistryUser.authority return plainJsRegistryUser } @@ -778,92 +704,20 @@ class BaseUserRepository extends BaseRepository { * @returns {Promise} The new random secret key. */ async resetSecret (username, orgShortName, options = {}, isLegacyObject = false) { - const legacyUserRepo = new UserRepository() - const baseOrgRepository = new BaseOrgRepository() - - const legOrgUUID = await baseOrgRepository.getOrgUUID(orgShortName, options, true) - const legUser = await legacyUserRepo.findOneByUserNameAndOrgUUID(username, legOrgUUID, null, options) - const regUser = await this.findOneByUsernameAndOrgShortname(username, orgShortName, options, false) - - // Fail ONLY if the user is completely missing from both collections - if (!legUser && !regUser) { - throw new Error('User not found in registry or legacy system.') + const user = await this.findOneByUsernameAndOrgShortname(username, orgShortName, options) + if (!user) { + throw new Error('User not found.') } const randomKey = cryptoRandomString({ length: getConstants().CRYPTO_RANDOM_STRING_LENGTH }) const secret = await argon2.hash(randomKey) - // Defensively update legacy if present - if (legUser) { - legUser.secret = secret - await legUser.save(options) - } - - // Defensively update registry if present - if (regUser) { - regUser.secret = secret - await regUser.save(options) - } + user.secret = secret + await user.save(options) return randomKey } - /** - * @function convertLegacyToRegistry - * @description Converts a legacy user object to the registry format. - * @param {object} legacyUser - The legacy user object. - * @returns {object} The converted registry user object. - */ - convertLegacyToRegistry (legacyUser) { - let newRole = '' - if (legacyUser?.authority?.active_roles?.includes('ADMIN')) { - newRole = 'ADMIN' - } - return { - UUID: legacyUser.UUID, - username: legacyUser.username, - secret: legacyUser.secret, - role: newRole, - name: { - first: legacyUser.name?.first, - middle: legacyUser.name?.middle, - last: legacyUser.name?.last, - suffix: legacyUser.name?.suffix - }, - status: 'active', - created: legacyUser?.time?.created, - last_updated: legacyUser?.time?.modified - } - } - - /** - * @function convertRegistryToLegacy - * @description Converts a registry user object to the legacy format. - * @param {object} registryUser - The registry user object. - * @returns {object} The converted legacy user object. - */ - convertRegistryToLegacy (registryUser) { - return { - UUID: registryUser.UUID, - username: registryUser.username, - authority: { - active_roles: registryUser.role === 'ADMIN' ? ['ADMIN'] : [] - }, - name: { - first: registryUser.name?.first, - middle: registryUser.name?.middle, - last: registryUser.name?.last, - suffix: registryUser.name?.suffix - }, - secret: registryUser.secret, - active: registryUser.status === 'active', - time: { - created: registryUser?.created, - modified: registryUser?.last_updated - } - } - } - /** * @async * @function getAllUsersByOrgShortname diff --git a/test/integration-tests/cve-id/reserveCveIdTest.js b/test/integration-tests/cve-id/reserveCveIdTest.js index 415fb7323..e7e10d77c 100644 --- a/test/integration-tests/cve-id/reserveCveIdTest.js +++ b/test/integration-tests/cve-id/reserveCveIdTest.js @@ -21,9 +21,9 @@ describe('Testing Reserve CVE-ID Endpoints', () => { .set(constants.headers) .end((err, res) => { expect(err).to.be.null + expect(res).to.have.status(200) expect(res.body).to.have.property('cve_ids') expect(res.body.cve_ids).to.have.length(requestLength) - expect(res).to.have.status(200) done() }) }) @@ -33,8 +33,9 @@ describe('Testing Reserve CVE-ID Endpoints', () => { .set(constants.headers) .end((err, res) => { expect(err).to.be.null - expect(res.body.cve_ids).to.have.length(10) expect(res).to.have.status(200) + expect(res.body).to.have.property('cve_ids') + expect(res.body.cve_ids).to.have.length(10) const onlyCveIdNumbers = res.body.cve_ids.map((element) => { return parseInt(element.cve_id.split('-')[2]) }) const cveNumbersInOrder = onlyCveIdNumbers.every((value, index) => { if (index === 0) { diff --git a/test/integration-tests/user/legacyUserWriteContractTest.js b/test/integration-tests/user/legacyUserWriteContractTest.js index c91561860..f8e4e32be 100644 --- a/test/integration-tests/user/legacyUserWriteContractTest.js +++ b/test/integration-tests/user/legacyUserWriteContractTest.js @@ -8,6 +8,7 @@ const constants = require('../constants.js') const app = require('../../../src/index.js') const BaseOrg = require('../../../src/model/baseorg') const BaseUser = require('../../../src/model/baseuser') +const LegacyUser = require('../../../src/model/user') const secretariatHeaders = { ...constants.headers, 'content-type': 'application/json' } @@ -45,10 +46,12 @@ describe('Legacy user write response contract', () => { const createdBaseUser = await BaseUser.findOne({ UUID: userUUID }) const orgAfterCreate = await BaseOrg.findOne({ short_name: orgShortName }) + const legacyUser = await LegacyUser.findOne({ UUID: userUUID }) expect(createdBaseUser).to.exist expect(createdBaseUser.status).to.equal('active') expect(orgAfterCreate.users).to.include(userUUID) expect(orgAfterCreate.admins).to.include(userUUID) + expect(legacyUser).to.be.null await chai.request(app) .put(`/api/org/${orgShortName}/user/${encodeURIComponent(username)}?name.first=Updated&active=false`) diff --git a/test/integration-tests/user/updateUserTest.js b/test/integration-tests/user/updateUserTest.js index c9be5fd69..41a4d8cd9 100644 --- a/test/integration-tests/user/updateUserTest.js +++ b/test/integration-tests/user/updateUserTest.js @@ -9,7 +9,6 @@ const { v4: uuidv4 } = require('uuid') const constants = require('../constants.js') const app = require('../../../src/index.js') const BaseOrgModel = require('../../../src/model/baseorg') -const UserModel = require('../../../src/model/user') const makeShortName = (prefix) => `${prefix}_${uuidv4().replace(/-/g, '').slice(0, 16)}` @@ -152,7 +151,7 @@ describe('Testing Edit user endpoint', () => { expect(orgBRecordAfter.admins).to.include(userUUID) }) - it('Should reassign a registry-only user without leaving old organization membership', async () => { + it('Should reassign a BaseUser without leaving old organization membership', async () => { const orgA = makeShortName('regonlya') const orgB = makeShortName('regonlyb') const username = `registryonly_${uuidv4().replace(/-/g, '').slice(0, 16)}@example.com` @@ -166,9 +165,6 @@ describe('Testing Edit user endpoint', () => { const orgARecordBefore = await BaseOrgModel.findOne({ short_name: orgA }) expect(orgARecordBefore.users).to.include(userUUID) - const deleteResult = await UserModel.deleteOne({ UUID: userUUID }) - expect(deleteResult.deletedCount).to.equal(1) - await chai.request(app) .put(`/api/registry/org/${orgA}/user/${username}`) .set(constants.headers) @@ -188,7 +184,7 @@ describe('Testing Edit user endpoint', () => { expect(orgBRecordAfter.admins || []).to.not.include(userUUID) }) - it('Should reassign a registry-only admin without leaving old organization admin membership', async () => { + it('Should reassign a BaseUser admin without leaving old organization admin membership', async () => { const orgA = makeShortName('regadmina') const orgB = makeShortName('regadminb') const username = `registryadmin_${uuidv4().replace(/-/g, '').slice(0, 16)}@example.com` @@ -203,9 +199,6 @@ describe('Testing Edit user endpoint', () => { expect(orgARecordBefore.users).to.include(userUUID) expect(orgARecordBefore.admins).to.include(userUUID) - const deleteResult = await UserModel.deleteOne({ UUID: userUUID }) - expect(deleteResult.deletedCount).to.equal(1) - await chai.request(app) .put(`/api/registry/org/${orgA}/user/${username}`) .set(constants.headers) diff --git a/test/unit-tests/user/baseUserRepositoryFormatSemanticsTest.js b/test/unit-tests/user/baseUserRepositoryFormatSemanticsTest.js index fda7e8d28..2e6eac212 100644 --- a/test/unit-tests/user/baseUserRepositoryFormatSemanticsTest.js +++ b/test/unit-tests/user/baseUserRepositoryFormatSemanticsTest.js @@ -8,11 +8,10 @@ const BaseUserRepository = require('../../../src/repositories/baseUserRepository const BaseOrgRepository = require('../../../src/repositories/baseOrgRepository.js') const BaseOrgModel = require('../../../src/model/baseorg.js') const BaseUser = require('../../../src/model/baseuser.js') -const UserRepository = require('../../../src/repositories/userRepository.js') const authContext = require('../../../src/utils/authContext.js') describe('Testing BaseUserRepository format flag semantics', () => { - let repo, registryAggregate, legacyAggregate + let repo, registryAggregate beforeEach(() => { repo = new BaseUserRepository() @@ -25,13 +24,11 @@ describe('Testing BaseUserRepository format flag semantics', () => { context('Format Flag Tests', () => { it('Should default user list responses to registry format when no legacy flag is provided', async () => { registryAggregate = sinon.stub(repo, 'aggregatePaginate').resolves({ itemsList: [{ UUID: 'user-uuid' }], itemCount: 0 }) - legacyAggregate = sinon.stub(UserRepository.prototype, 'aggregatePaginate').rejects(new Error('Legacy aggregate should not be called')) const result = await repo.getAllUsers({ limit: 100 }) expect(result).to.deep.equal({ users: [{ UUID: 'user-uuid' }] }) expect(registryAggregate.calledOnce).to.be.true - expect(legacyAggregate.called).to.be.false expect(registryAggregate.firstCall.args[0][1].$project).to.deep.equal({ _id: false, secret: false @@ -60,11 +57,9 @@ describe('Testing BaseUserRepository format flag semantics', () => { users: ['admin-user-uuid', 'regular-user-uuid'], admins: ['admin-user-uuid'] }]) - legacyAggregate = sinon.stub(UserRepository.prototype, 'aggregatePaginate').rejects(new Error('Legacy aggregate should not be called')) const result = await repo.getLegacyUsers({ limit: 100 }) - expect(legacyAggregate.called).to.be.false expect(findOrgsByUserUUIDs.calledOnceWith(['admin-user-uuid', 'regular-user-uuid'])).to.be.true expect(result).to.deep.equal({ users: [{ @@ -114,11 +109,9 @@ describe('Testing BaseUserRepository format flag semantics', () => { }], itemCount: 0 }) - legacyAggregate = sinon.stub(UserRepository.prototype, 'aggregatePaginate').rejects(new Error('Legacy aggregate should not be called')) const result = await repo.getLegacyUsersByOrgShortname('example-org', { limit: 100 }) - expect(legacyAggregate.called).to.be.false expect(registryAggregate.calledOnce).to.be.true expect(registryAggregate.firstCall.args[0][0]).to.deep.equal({ $match: { @@ -153,11 +146,9 @@ describe('Testing BaseUserRepository format flag semantics', () => { created: '2026-01-01T00:00:00.000Z', last_updated: '2026-01-02T00:00:00.000Z' }) - legacyAggregate = sinon.stub(UserRepository.prototype, 'findOneByUUID').rejects(new Error('Legacy user lookup should not be called')) const result = await repo.findLegacyUserByUsernameAndOrgShortname('user@example.org', 'example-org') - expect(legacyAggregate.called).to.be.false expect(result).to.deep.equal({ UUID: 'user-uuid', username: 'user@example.org', diff --git a/test/unit-tests/user/userCreateTest.js b/test/unit-tests/user/userCreateTest.js index 281539f94..c55b6d525 100644 --- a/test/unit-tests/user/userCreateTest.js +++ b/test/unit-tests/user/userCreateTest.js @@ -8,11 +8,9 @@ const argon2 = require('argon2') const { USER_CREATE_SINGLE } = require('../../../src/controller/org.controller/org.controller') const BaseOrgRepository = require('../../../src/repositories/baseOrgRepository.js') -const RegistryUserModel = require('../../../src/model/registryuser.js') const BaseOrg = require('../../../src/model/baseorg.js') const BaseUserRepository = require('../../../src/repositories/baseUserRepository.js') const BaseUser = require('../../../src/model/baseuser.js') -const UserRepository = require('../../../src/repositories/userRepository.js') const stubOrgUUID = faker.datatype.uuid() const stubUserUUID = faker.datatype.uuid() @@ -54,7 +52,7 @@ const fakeOrgMongooseDocument = new BaseOrg(fakeLegacySavedObject) // eslint-disable-next-line mocha/no-skipped-tests describe('Testing the POST /org/:shortname/user endpoint in Org Controller', () => { - let status, json, res, next, mockSession, baseOrgRepo, getBaseOrgRepository, baseUserRepo, getBaseUserRepository, req, userRepo, getUserRepository + let status, json, res, next, mockSession, baseOrgRepo, getBaseOrgRepository, baseUserRepo, getBaseUserRepository, req beforeEach(() => { status = sinon.stub() @@ -76,8 +74,6 @@ describe('Testing the POST /org/:shortname/user endpoint in Org Controller', () getBaseOrgRepository = sinon.stub().returns(baseOrgRepo) baseUserRepo = new BaseUserRepository() getBaseUserRepository = sinon.stub().returns(baseUserRepo) - userRepo = new UserRepository() - getUserRepository = sinon.stub().returns(userRepo) req = { ctx: { @@ -88,8 +84,7 @@ describe('Testing the POST /org/:shortname/user endpoint in Org Controller', () }, repositories: { getBaseOrgRepository, - getBaseUserRepository, - getUserRepository + getBaseUserRepository }, body: { ...stubUser @@ -109,9 +104,7 @@ describe('Testing the POST /org/:shortname/user endpoint in Org Controller', () sinon.stub(BaseOrgRepository.prototype, 'findOneByShortName').resolves(fakeOrgMongooseDocument) sinon.stub(BaseOrgRepository.prototype, 'addUserToOrg').resolves() sinon.stub(baseUserRepo, 'findUsersByOrgShortname').resolves([fakeUserMongooseDocument]) - sinon.stub(RegistryUserModel.prototype, 'save').resolves(fakeBaseUserSavedObject) - // stub the prototype since createUser in baseUserRepository creates a new internal instance of the legacy UserRepository - sinon.stub(UserRepository.prototype, 'updateByUserNameAndOrgUUID').resolves(fakeUserMongooseDocument) + sinon.stub(BaseUser.prototype, 'save').resolves(fakeBaseUserSavedObject) sinon.stub(baseUserRepo, 'getUserUUID').resolves('mock-uuid') await USER_CREATE_SINGLE(req, res, next) From c14b834252586a389bb05111d72d43176668343b Mon Sep 17 00:00:00 2001 From: James Dalphond Date: Wed, 19 Aug 2026 12:18:47 -0400 Subject: [PATCH 16/20] Drop ADMIN only when a registry user changes organization Registry full user updates preserve ADMIN membership when the user remains in the same organization. When org_short_name changes, the user is removed from the source admins list and receives destination membership only. Extend integration coverage for both same-org updates and organization moves. --- src/repositories/baseUserRepository.js | 10 ++------- test/integration-tests/user/updateUserTest.js | 22 +++++++++++++++++-- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/repositories/baseUserRepository.js b/src/repositories/baseUserRepository.js index 573672cee..d5bb4ad32 100644 --- a/src/repositories/baseUserRepository.js +++ b/src/repositories/baseUserRepository.js @@ -640,7 +640,6 @@ class BaseUserRepository extends BaseRepository { const currentOrgUUID = await baseOrgRepository.getOrgUUIDByUserUUID(identifier, options) const currentOrg = currentOrgUUID ? await baseOrgRepository.findOneByUUID(currentOrgUUID) : null const newOrg = await baseOrgRepository.findOneByShortName(incomingUser.org_short_name) - const wasAdmin = currentOrg?.admins?.includes(identifier) ?? false if (!newOrg) { throw new Error(`Organization ${incomingUser.org_short_name} not found`) @@ -659,13 +658,8 @@ class BaseUserRepository extends BaseRepository { const originalNewOrg = newOrg.toObject() if (!Array.isArray(newOrg.users)) newOrg.users = [] newOrg.users.addToSet(identifier) - - if (wasAdmin) { - if (!Array.isArray(newOrg.admins)) { - newOrg.admins = [] - } - newOrg.admins.addToSet(identifier) - } + // Registry user moves transfer membership only. Destination ADMIN + // access must be granted explicitly after the move. await newOrg.save(options) if (requestingUserUUID) { diff --git a/test/integration-tests/user/updateUserTest.js b/test/integration-tests/user/updateUserTest.js index 41a4d8cd9..d2e13fd4d 100644 --- a/test/integration-tests/user/updateUserTest.js +++ b/test/integration-tests/user/updateUserTest.js @@ -184,7 +184,7 @@ describe('Testing Edit user endpoint', () => { expect(orgBRecordAfter.admins || []).to.not.include(userUUID) }) - it('Should reassign a BaseUser admin without leaving old organization admin membership', async () => { + it('Should retain BaseUser admin membership for a same-org update and remove it after an organization move', async () => { const orgA = makeShortName('regadmina') const orgB = makeShortName('regadminb') const username = `registryadmin_${uuidv4().replace(/-/g, '').slice(0, 16)}@example.com` @@ -199,6 +199,24 @@ describe('Testing Edit user endpoint', () => { expect(orgARecordBefore.users).to.include(userUUID) expect(orgARecordBefore.admins).to.include(userUUID) + // Updating registry user fields without changing organization preserves ADMIN. + await chai.request(app) + .put(`/api/registry/org/${orgA}/user/${username}`) + .set(constants.headers) + .send({ + ...userResponse, + name: { + ...userResponse.name, + first: 'Same Org Update' + } + }) + .then((res) => { + expect(res.status).to.equal(200, JSON.stringify(res.body)) + }) + + const orgARecordAfterSameOrgUpdate = await BaseOrgModel.findOne({ short_name: orgA }) + expect(orgARecordAfterSameOrgUpdate.admins).to.include(userUUID) + await chai.request(app) .put(`/api/registry/org/${orgA}/user/${username}`) .set(constants.headers) @@ -215,7 +233,7 @@ describe('Testing Edit user endpoint', () => { expect(orgARecordAfter.users).to.not.include(userUUID) expect(orgARecordAfter.admins).to.not.include(userUUID) expect(countOccurrences(orgBRecordAfter.users, userUUID)).to.equal(1) - expect(countOccurrences(orgBRecordAfter.admins, userUUID)).to.equal(1) + expect(countOccurrences(orgBRecordAfter.admins, userUUID)).to.equal(0) }) it('Should return 200 when only name changes are done', async () => { From e9dc70e9ba13de258d42456f2a356edf2be88042 Mon Sep 17 00:00:00 2001 From: James Dalphond Date: Wed, 19 Aug 2026 13:18:43 -0400 Subject: [PATCH 17/20] Move CVE and CVE-ID audit lookups to BaseUser Use BaseUserRepository when CVE and CVE-ID controllers resolve the authenticated requester UUID for audit metadata, removing their remaining UserRepository factory dependency. Update affected CVE/CVE-ID unit-test factories to provide BaseUserRepository and update registry user-move coverage to require destination membership without destination ADMIN access. --- .../cve-id.controller/cve-id.controller.js | 10 +++++----- .../cve.controller/cve.controller.js | 14 ++++++------- test/integration-tests/user/updateUserTest.js | 7 ++++--- .../unit-tests/cve-id/cveIdRangeCreateTest.js | 4 ++-- test/unit-tests/cve-id/cveIdUpdateTest.js | 12 +++++------ .../reserveCveIdTest.non-sequential.js | 4 ++-- ...eserveCveIdTest.usersA_B.non-sequential.js | 6 +++--- .../cveIdReserveGeneralLogicTest.js | 20 +++++++++---------- .../reserveCveId/cveIdReservePriorityTest.js | 8 ++++---- .../cveIdReserveSequentialTest.js | 6 +++--- .../cve/cveCnaContainerCreateTest.js | 4 ++-- .../cve/cveCnaContainerUpdateTest.js | 4 ++-- test/unit-tests/cve/cveCreateTest.js | 4 ++-- test/unit-tests/cve/cveRecordRejectionTest.js | 4 ++-- test/unit-tests/cve/cveUpdateTest.js | 12 +++++------ test/unit-tests/cve/insertAdpTest.js | 12 +++++------ test/unit-tests/cve/updateCnaTest.js | 8 ++++---- 17 files changed, 70 insertions(+), 69 deletions(-) diff --git a/src/controller/cve-id.controller/cve-id.controller.js b/src/controller/cve-id.controller/cve-id.controller.js index 0d1f5340f..eabc2722d 100644 --- a/src/controller/cve-id.controller/cve-id.controller.js +++ b/src/controller/cve-id.controller/cve-id.controller.js @@ -369,7 +369,7 @@ async function modifyCveId (req, res, next) { let orgUUID const baseOrgRepo = req.ctx.repositories.getBaseOrgRepository() const cveIdRepo = req.ctx.repositories.getCveIdRepository() - const userRepo = req.ctx.repositories.getUserRepository() + const userRepo = req.ctx.repositories.getBaseUserRepository() const cveRepo = req.ctx.repositories.getCveRepository() if (!req.ctx.authenticated) { return res.status(403).json(error.orgCannotReserveForOther()) @@ -469,7 +469,7 @@ async function createCveIdRange (req, res, next) { const year = req.ctx.params.year const cveIdRangeRepo = req.ctx.repositories.getCveIdRangeRepository() const baseOrgRepo = req.ctx.repositories.getBaseOrgRepository() - const userRepo = req.ctx.repositories.getUserRepository() + const userRepo = req.ctx.repositories.getBaseUserRepository() const result = await cveIdRangeRepo.findOne({ cve_year: year }) if (result) { @@ -537,7 +537,7 @@ async function priorityReservation (year, amount, shortName, orgShortName, reque if (!isFull) { const baseOrgRepo = req.ctx.repositories.getBaseOrgRepository() - const userRepo = req.ctx.repositories.getUserRepository() + const userRepo = req.ctx.repositories.getBaseUserRepository() const cveIdRepo = req.ctx.repositories.getCveIdRepository() const id = generateSequentialIds(year, result.ranges.priority.top_id, amount) const owningOrgUUID = await baseOrgRepo.getOrgUUID(shortName) @@ -638,7 +638,7 @@ async function sequentialReservation (year, amount, shortName, orgShortName, req if (!isFull) { const baseOrgRepo = req.ctx.repositories.getBaseOrgRepository() - const userRepo = req.ctx.repositories.getUserRepository() + const userRepo = req.ctx.repositories.getBaseUserRepository() const cveIdRepo = req.ctx.repositories.getCveIdRepository() const ids = generateSequentialIds(year, result.ranges.general.top_id, amount) const owningOrgUUID = await baseOrgRepo.getOrgUUID(shortName) @@ -752,7 +752,7 @@ async function nonSequentialReservation (year, amount, shortName, orgShortName, // Case 2: Enough IDs in the 'AVAILABLE' pool const baseOrgRepo = req.ctx.repositories.getBaseOrgRepository() - const userRepo = req.ctx.repositories.getUserRepository() + const userRepo = req.ctx.repositories.getBaseUserRepository() let index let counter = 0 const cveIdDocuments = [] diff --git a/src/controller/cve.controller/cve.controller.js b/src/controller/cve.controller/cve.controller.js index 539fb12a1..18f669dd1 100644 --- a/src/controller/cve.controller/cve.controller.js +++ b/src/controller/cve.controller/cve.controller.js @@ -422,7 +422,7 @@ async function submitCve (req, res, next) { org_UUID: await authContext.getRequesterOrgUUID(req, baseOrgRepo), cve: cveId } - const userRepo = req.ctx.repositories.getUserRepository() + const userRepo = req.ctx.repositories.getBaseUserRepository() payload.user_UUID = await authContext.getRequesterUserUUID(req, userRepo, baseOrgRepo) logger.info(JSON.stringify(payload)) return res.status(200).json(responseMessage) @@ -486,7 +486,7 @@ async function updateCve (req, res, next) { cve: cveId } - const userRepo = req.ctx.repositories.getUserRepository() + const userRepo = req.ctx.repositories.getBaseUserRepository() payload.user_UUID = await authContext.getRequesterUserUUID(req, userRepo, baseOrgRepo) logger.info(JSON.stringify(payload)) return res.status(200).json(responseMessage) @@ -504,7 +504,7 @@ async function submitCna (req, res, next) { const cveRepo = req.ctx.repositories.getCveRepository() const cveIdRepo = req.ctx.repositories.getCveIdRepository() const baseOrgRepo = req.ctx.repositories.getBaseOrgRepository() - const userRepo = req.ctx.repositories.getUserRepository() + const userRepo = req.ctx.repositories.getBaseUserRepository() const orgUuid = await authContext.getRequesterOrgUUID(req, baseOrgRepo) const userUuid = await authContext.getRequesterUserUUID(req, userRepo, baseOrgRepo) @@ -600,7 +600,7 @@ async function updateCna (req, res, next) { const cveRepo = req.ctx.repositories.getCveRepository() const cveIdRepo = req.ctx.repositories.getCveIdRepository() const baseOrgRepo = req.ctx.repositories.getBaseOrgRepository() - const userRepo = req.ctx.repositories.getUserRepository() + const userRepo = req.ctx.repositories.getBaseUserRepository() const orgUuid = await authContext.getRequesterOrgUUID(req, baseOrgRepo) const userUuid = await authContext.getRequesterUserUUID(req, userRepo, baseOrgRepo) @@ -765,7 +765,7 @@ async function rejectCVE (req, res, next) { org_UUID: providerOrgObj.UUID, cve: id } - const userRepo = req.ctx.repositories.getUserRepository() + const userRepo = req.ctx.repositories.getBaseUserRepository() payload.user_UUID = await authContext.getRequesterUserUUID(req, userRepo, baseOrgRepo) logger.info(JSON.stringify(payload)) return res.status(200).json(responseMessage) @@ -840,7 +840,7 @@ async function rejectExistingCve (req, res, next) { org_UUID: providerOrgObj.UUID, cve: id } - const userRepo = req.ctx.repositories.getUserRepository() + const userRepo = req.ctx.repositories.getBaseUserRepository() payload.user_UUID = await authContext.getRequesterUserUUID(req, userRepo, baseOrgRepo) logger.info(JSON.stringify(payload)) return res.status(200).json(responseMessage) @@ -858,7 +858,7 @@ async function insertAdp (req, res, next) { const cveRepo = req.ctx.repositories.getCveRepository() const cveIdRepo = req.ctx.repositories.getCveIdRepository() const baseOrgRepo = req.ctx.repositories.getBaseOrgRepository() - const userRepo = req.ctx.repositories.getUserRepository() + const userRepo = req.ctx.repositories.getBaseUserRepository() const orgUuid = await authContext.getRequesterOrgUUID(req, baseOrgRepo) const userUuid = await authContext.getRequesterUserUUID(req, userRepo, baseOrgRepo) diff --git a/test/integration-tests/user/updateUserTest.js b/test/integration-tests/user/updateUserTest.js index d2e13fd4d..f0fb96e78 100644 --- a/test/integration-tests/user/updateUserTest.js +++ b/test/integration-tests/user/updateUserTest.js @@ -70,7 +70,7 @@ const countOccurrences = (values = [], value) => values.filter(item => item === describe('Testing Edit user endpoint', () => { context('Positive Tests', () => { - it('Should correctly remove an admin from the original organization admins array when migrated to a new organization', async () => { + it('Should remove admin membership when an admin is migrated to a new organization', async () => { // Create org A const orgA = 'mig_org_a' await chai.request(app) @@ -146,9 +146,10 @@ describe('Testing Edit user endpoint', () => { const orgARecordAfter = await BaseOrgModel.findOne({ short_name: orgA }) expect(orgARecordAfter.admins).to.not.include(userUUID) - // Verify user is in orgB's admins array + // Verify user has membership but no ADMIN role in orgB. const orgBRecordAfter = await BaseOrgModel.findOne({ short_name: orgB }) - expect(orgBRecordAfter.admins).to.include(userUUID) + expect(orgBRecordAfter.users).to.include(userUUID) + expect(orgBRecordAfter.admins || []).to.not.include(userUUID) }) it('Should reassign a BaseUser without leaving old organization membership', async () => { diff --git a/test/unit-tests/cve-id/cveIdRangeCreateTest.js b/test/unit-tests/cve-id/cveIdRangeCreateTest.js index ca7d7e74b..14d31054a 100644 --- a/test/unit-tests/cve-id/cveIdRangeCreateTest.js +++ b/test/unit-tests/cve-id/cveIdRangeCreateTest.js @@ -74,7 +74,7 @@ describe('Testing the POST /cve-id-range/:year endpoint in CveId Controller', () .post((req, res, next) => { const factory = { getCveIdRangeRepository: () => { return new CveIdRange2022Exists() }, - getUserRepository: () => { return new NullUserRepo() }, + getBaseUserRepository: () => { return new NullUserRepo() }, getBaseOrgRepository: () => { return new NullOrgRepo() } } req.ctx.repositories = factory @@ -115,7 +115,7 @@ describe('Testing the POST /cve-id-range/:year endpoint in CveId Controller', () .post((req, res, next) => { const factory = { getCveIdRangeRepository: () => { return new CveIdRange2022NotExists() }, - getUserRepository: () => { return new NullUserRepo() }, + getBaseUserRepository: () => { return new NullUserRepo() }, getBaseOrgRepository: () => { return new NullOrgRepo() } } req.ctx.repositories = factory diff --git a/test/unit-tests/cve-id/cveIdUpdateTest.js b/test/unit-tests/cve-id/cveIdUpdateTest.js index 87ecaa512..cddde52da 100644 --- a/test/unit-tests/cve-id/cveIdUpdateTest.js +++ b/test/unit-tests/cve-id/cveIdUpdateTest.js @@ -123,7 +123,7 @@ describe('Testing the PUT /cve-id/:id endpoint in CveId Controller', () => { const factory = { getCveIdRepository: () => { return new CveIdModifyCveIdOrgAndStateModified() }, getBaseOrgRepository: () => { return new OrgModifyCveIdUnauthenticated() }, - getUserRepository: () => { return new NullUserRepo() }, + getBaseUserRepository: () => { return new NullUserRepo() }, getCveRepository: () => { return new NullCveRepo() } } req.ctx.repositories = factory @@ -178,7 +178,7 @@ describe('Testing the PUT /cve-id/:id endpoint in CveId Controller', () => { const factory = { getCveIdRepository: () => { return new CveIdModifyCveIdDoesntExist() }, getBaseOrgRepository: () => { return new OrgModifyCveIdDoesntExist() }, - getUserRepository: () => { return new NullUserRepo() }, + getBaseUserRepository: () => { return new NullUserRepo() }, getCveRepository: () => { return new NullCveRepo() } } authenticateAsSecretariat(req) @@ -223,7 +223,7 @@ describe('Testing the PUT /cve-id/:id endpoint in CveId Controller', () => { const factory = { getCveIdRepository: () => { return new CveIdModifyCveIdOrgAndStateModified() }, getBaseOrgRepository: () => { return new OrgModifyCveIdOrgDoesntExist() }, - getUserRepository: () => { return new NullUserRepo() }, + getBaseUserRepository: () => { return new NullUserRepo() }, getCveRepository: () => { return new NullCveRepo() } } authenticateAsSecretariat(req) @@ -259,7 +259,7 @@ describe('Testing the PUT /cve-id/:id endpoint in CveId Controller', () => { // const factory = { // getCveIdRepository: () => { return new CveIdModifyCveIdOrgAndStateModified() }, // getBaseOrgRepository: () => { return new NullOrgRepo() }, - // getUserRepository: () => { return new NullUserRepo() }, + // getBaseUserRepository: () => { return new NullUserRepo() }, // getCveRepository: () => { return new NullCveRepo() } // } // req.ctx.repositories = factory @@ -293,7 +293,7 @@ describe('Testing the PUT /cve-id/:id endpoint in CveId Controller', () => { const factory = { getCveIdRepository: () => { return cveIdRepo }, getBaseOrgRepository: () => { return new OrgModifyCveIdOrgAndStateModified() }, - getUserRepository: () => { return new UserModifyCveIdOrgAndStateModified() }, + getBaseUserRepository: () => { return new UserModifyCveIdOrgAndStateModified() }, getCveRepository: () => { return new NullCveRepo() } } authenticateAsSecretariat(req) @@ -358,7 +358,7 @@ describe('Testing the PUT /cve-id/:id endpoint in CveId Controller', () => { const factory = { getCveIdRepository: () => { return new CveIdModifyCveIdNoQuery() }, getBaseOrgRepository: () => { return new OrgModifyCveIdOrgAndStateModified() }, - getUserRepository: () => { return new UserModifyCveIdOrgAndStateModified() }, + getBaseUserRepository: () => { return new UserModifyCveIdOrgAndStateModified() }, getCveRepository: () => { return new NullCveRepo() } } authenticateAsSecretariat(req) diff --git a/test/unit-tests/cve-id/reserveCveId.non-sequential/reserveCveIdTest.non-sequential.js b/test/unit-tests/cve-id/reserveCveId.non-sequential/reserveCveIdTest.non-sequential.js index 31042699e..e05ff8898 100644 --- a/test/unit-tests/cve-id/reserveCveId.non-sequential/reserveCveIdTest.non-sequential.js +++ b/test/unit-tests/cve-id/reserveCveId.non-sequential/reserveCveIdTest.non-sequential.js @@ -167,7 +167,7 @@ describe('Testing the non sequential reservation (Base Case) of POST /cve-id end const factory = { getCveIdRepository: () => { return new CveIdReserveNonSequentialIsFull() }, getCveIdRangeRepository: () => { return new CveIdRangeReserveYearDoesntExist() }, - getUserRepository: () => { return new NullUserRepo() }, + getBaseUserRepository: () => { return new NullUserRepo() }, getBaseOrgRepository: () => { return new OrgReserveNonSequentialYearDoesntExist() } } req.ctx.repositories = factory @@ -238,7 +238,7 @@ describe('Testing the non sequential reservation (Base Case) of POST /cve-id end const factory = { getCveIdRepository: () => { return cveIdRepo }, getCveIdRangeRepository: () => { return cveIdRangeRepo }, - getUserRepository: () => { return userRepo }, + getBaseUserRepository: () => { return userRepo }, getBaseOrgRepository: () => { return orgRepo } } req.ctx.repositories = factory diff --git a/test/unit-tests/cve-id/reserveCveId.non-sequential/reserveCveIdTest.usersA_B.non-sequential.js b/test/unit-tests/cve-id/reserveCveId.non-sequential/reserveCveIdTest.usersA_B.non-sequential.js index f87e3662f..9104fb9ca 100644 --- a/test/unit-tests/cve-id/reserveCveId.non-sequential/reserveCveIdTest.usersA_B.non-sequential.js +++ b/test/unit-tests/cve-id/reserveCveId.non-sequential/reserveCveIdTest.usersA_B.non-sequential.js @@ -281,7 +281,7 @@ describe('Testing the non sequential reservation (Case AB) of POST /cve-id endpo const factory = { getCveIdRepository: () => { return cveIdRepo }, getCveIdRangeRepository: () => { return cveIdRangeRepo }, - getUserRepository: () => { return userRepo }, + getBaseUserRepository: () => { return userRepo }, getBaseOrgRepository: () => { return orgRepo } } req.ctx.repositories = factory @@ -381,7 +381,7 @@ describe('Testing the non sequential reservation (Case AB) of POST /cve-id endpo const factory = { getCveIdRepository: () => { return new CveIdReservePoolIncremented10IdsCaseAB2() }, getCveIdRangeRepository: () => { return new CveIdRangeReserveNonSequentialSuccessCaseAB2() }, - getUserRepository: () => { return new UserReserveNonSequentialSuccessCaseAB() }, + getBaseUserRepository: () => { return new UserReserveNonSequentialSuccessCaseAB() }, getBaseOrgRepository: () => { return new OrgReserveNonSequentialSuccessCaseAB() } } req.ctx.repositories = factory @@ -587,7 +587,7 @@ describe('Testing the non sequential reservation (Case AB) of POST /cve-id endpo const factory = { getCveIdRepository: () => { return new CveIdReservePoolIncremented10IdsCaseAB3() }, getCveIdRangeRepository: () => { return new CveIdRangeReserveNonSequentialSuccessCaseAB3() }, - getUserRepository: () => { return new UserReserveNonSequentialSuccessCaseAB() }, + getBaseUserRepository: () => { return new UserReserveNonSequentialSuccessCaseAB() }, getBaseOrgRepository: () => { return new OrgReserveNonSequentialSuccessCaseAB() } } req.ctx.repositories = factory diff --git a/test/unit-tests/cve-id/reserveCveId/cveIdReserveGeneralLogicTest.js b/test/unit-tests/cve-id/reserveCveId/cveIdReserveGeneralLogicTest.js index ef2a998a7..cf1bf2ddc 100644 --- a/test/unit-tests/cve-id/reserveCveId/cveIdReserveGeneralLogicTest.js +++ b/test/unit-tests/cve-id/reserveCveId/cveIdReserveGeneralLogicTest.js @@ -78,7 +78,7 @@ describe('Testing the general logic of POST /cve-id endpoint in CveId Controller .post((req, res, next) => { const factory = { getCveIdRepository: () => { return new NullCveIdRepo() }, - getUserRepository: () => { return new NullUserRepo() }, + getBaseUserRepository: () => { return new NullUserRepo() }, getBaseOrgRepository: () => { return new OrgReserveNotOwningOrg() } } req.ctx.repositories = factory @@ -107,7 +107,7 @@ describe('Testing the general logic of POST /cve-id endpoint in CveId Controller .post((req, res, next) => { const factory = { getCveIdRepository: () => { return new NullCveIdRepo() }, - getUserRepository: () => { return new NullUserRepo() }, + getBaseUserRepository: () => { return new NullUserRepo() }, getBaseOrgRepository: () => { return new OrgReserveShortNameUndefined() } } req.ctx.repositories = factory @@ -136,7 +136,7 @@ describe('Testing the general logic of POST /cve-id endpoint in CveId Controller .post((req, res, next) => { const factory = { getCveIdRepository: () => { return new NullCveIdRepo() }, - getUserRepository: () => { return new NullUserRepo() }, + getBaseUserRepository: () => { return new NullUserRepo() }, getBaseOrgRepository: () => { return new OrgReserveShortNameUndefined() } } req.ctx.repositories = factory @@ -165,7 +165,7 @@ describe('Testing the general logic of POST /cve-id endpoint in CveId Controller .post((req, res, next) => { const factory = { getCveIdRepository: () => { return new NullCveIdRepo() }, - getUserRepository: () => { return new NullUserRepo() }, + getBaseUserRepository: () => { return new NullUserRepo() }, getBaseOrgRepository: () => { return new OrgReserveShortNameUndefined() } } req.ctx.repositories = factory @@ -194,7 +194,7 @@ describe('Testing the general logic of POST /cve-id endpoint in CveId Controller .post((req, res, next) => { const factory = { getCveIdRepository: () => { return new NullCveIdRepo() }, - getUserRepository: () => { return new NullUserRepo() }, + getBaseUserRepository: () => { return new NullUserRepo() }, getBaseOrgRepository: () => { return new OrgReserveShortNameUndefined() } } req.ctx.repositories = factory @@ -223,7 +223,7 @@ describe('Testing the general logic of POST /cve-id endpoint in CveId Controller .post((req, res, next) => { const factory = { getCveIdRepository: () => { return new NullCveIdRepo() }, - getUserRepository: () => { return new NullUserRepo() }, + getBaseUserRepository: () => { return new NullUserRepo() }, getBaseOrgRepository: () => { return new OrgReserveShortNameUndefined() } } req.ctx.repositories = factory @@ -262,7 +262,7 @@ describe('Testing the general logic of POST /cve-id endpoint in CveId Controller .post((req, res, next) => { const factory = { getCveIdRepository: () => { return new NullCveIdRepo() }, - getUserRepository: () => { return new NullUserRepo() }, + getBaseUserRepository: () => { return new NullUserRepo() }, getBaseOrgRepository: () => { return new OrgReserveOrgDoesntExist() } } req.ctx.repositories = factory @@ -293,7 +293,7 @@ describe('Testing the general logic of POST /cve-id endpoint in CveId Controller .post((req, res, next) => { const factory = { getCveIdRepository: () => { return new NullCveIdRepo() }, - getUserRepository: () => { return new NullUserRepo() }, + getBaseUserRepository: () => { return new NullUserRepo() }, getBaseOrgRepository: () => { return new OrgReserveAmountLargerThanNonSequential() } } req.ctx.repositories = factory @@ -332,7 +332,7 @@ describe('Testing the general logic of POST /cve-id endpoint in CveId Controller .post((req, res, next) => { const factory = { getCveIdRepository: () => { return new NullCveIdRepo() }, - getUserRepository: () => { return new NullUserRepo() }, + getBaseUserRepository: () => { return new NullUserRepo() }, getBaseOrgRepository: () => { return new OrgReserveAmountGreaterIdQuota() } } req.ctx.repositories = factory @@ -362,7 +362,7 @@ describe('Testing the general logic of POST /cve-id endpoint in CveId Controller .post((req, res, next) => { const factory = { getCveIdRepository: () => { return new NullCveIdRepo() }, - getUserRepository: () => { return new NullUserRepo() }, + getBaseUserRepository: () => { return new NullUserRepo() }, getBaseOrgRepository: () => { return new OrgReserveAmountLargerThanNonSequential() } } req.ctx.repositories = factory diff --git a/test/unit-tests/cve-id/reserveCveId/cveIdReservePriorityTest.js b/test/unit-tests/cve-id/reserveCveId/cveIdReservePriorityTest.js index b9699f405..f157a45da 100644 --- a/test/unit-tests/cve-id/reserveCveId/cveIdReservePriorityTest.js +++ b/test/unit-tests/cve-id/reserveCveId/cveIdReservePriorityTest.js @@ -118,7 +118,7 @@ describe('Testing the priority reservation of POST /cve-id endpoint in CveId Con const factory = { getCveIdRepository: () => { return new NullCveIdRepo() }, getCveIdRangeRepository: () => { return new CveIdRangeReserveYearDoesntExist() }, - getUserRepository: () => { return new NullUserRepo() }, + getBaseUserRepository: () => { return new NullUserRepo() }, getBaseOrgRepository: () => { return new OrgReserveYear2025RangeDoesntExist() } } req.ctx.repositories = factory @@ -191,7 +191,7 @@ describe('Testing the priority reservation of POST /cve-id endpoint in CveId Con const factory = { getCveIdRepository: () => { return new CveIdReserveSequentialPriorityIsFull() }, getCveIdRangeRepository: () => { return new CveIdRangeReserveSequentialIsFull() }, - getUserRepository: () => { return new NullUserRepo() }, + getBaseUserRepository: () => { return new NullUserRepo() }, getBaseOrgRepository: () => { return new OrgReserveSequentialIsFull() } } req.ctx.repositories = factory @@ -257,7 +257,7 @@ describe('Testing the priority reservation of POST /cve-id endpoint in CveId Con const factory = { getCveIdRepository: () => { return new CveIdReserveSequentialPriorityIsFull() }, getCveIdRangeRepository: () => { return new CveIdRangeReserveSequentialPriorityIsFull() }, - getUserRepository: () => { return new UserReserveSequentialPriorityIsFull() }, + getBaseUserRepository: () => { return new UserReserveSequentialPriorityIsFull() }, getBaseOrgRepository: () => { return new OrgReserveSequentialPriorityIsFull() } } req.ctx.repositories = factory @@ -329,7 +329,7 @@ describe('Testing the priority reservation of POST /cve-id endpoint in CveId Con const factory = { getCveIdRepository: () => { return new CveIdReserveSequentialPriorityIsFull() }, getCveIdRangeRepository: () => { return new CveIdRangeReserveSequentialPriority() }, - getUserRepository: () => { return new UserReserveSequentialPriorityIsFull() }, + getBaseUserRepository: () => { return new UserReserveSequentialPriorityIsFull() }, getBaseOrgRepository: () => { return new OrgReserveSequentialPriorityIsFull() } } req.ctx.repositories = factory diff --git a/test/unit-tests/cve-id/reserveCveId/cveIdReserveSequentialTest.js b/test/unit-tests/cve-id/reserveCveId/cveIdReserveSequentialTest.js index 8dcf4cf82..101c901ba 100644 --- a/test/unit-tests/cve-id/reserveCveId/cveIdReserveSequentialTest.js +++ b/test/unit-tests/cve-id/reserveCveId/cveIdReserveSequentialTest.js @@ -78,7 +78,7 @@ describe('Testing the sequential reservation of POST /cve-id endpoint in CveId C const factory = { getCveIdRepository: () => { return new CveIdReserveSequentialYearDoesntExist() }, getCveIdRangeRepository: () => { return new CveIdRangeReserveYearDoesntExist() }, - getUserRepository: () => { return new NullUserRepo() }, + getBaseUserRepository: () => { return new NullUserRepo() }, getBaseOrgRepository: () => { return new OrgReserveYear2025RangeDoesntExistSequential() } } req.ctx.repositories = factory @@ -137,7 +137,7 @@ describe('Testing the sequential reservation of POST /cve-id endpoint in CveId C const factory = { getCveIdRepository: () => { return new CveIdReserveSequentialIsFull() }, getCveIdRangeRepository: () => { return new CveIdRangeReserveSequentialIsFull() }, - getUserRepository: () => { return new NullUserRepo() }, + getBaseUserRepository: () => { return new NullUserRepo() }, getBaseOrgRepository: () => { return new OrgReserveYear2025RangeDoesntExistSequential() } } req.ctx.repositories = factory @@ -242,7 +242,7 @@ describe('Testing the sequential reservation of POST /cve-id endpoint in CveId C const factory = { getCveIdRepository: () => { return new CveIdReserveSequential() }, getCveIdRangeRepository: () => { return new CveIdRangeReserveSequential() }, - getUserRepository: () => { return new UserReserveSequentialPriorityIsFull() }, + getBaseUserRepository: () => { return new UserReserveSequentialPriorityIsFull() }, getBaseOrgRepository: () => { return new OrgReserveSequentialPriorityIsFull() } } req.ctx.repositories = factory diff --git a/test/unit-tests/cve/cveCnaContainerCreateTest.js b/test/unit-tests/cve/cveCnaContainerCreateTest.js index 83b562cac..784ec7fb6 100644 --- a/test/unit-tests/cve/cveCnaContainerCreateTest.js +++ b/test/unit-tests/cve/cveCnaContainerCreateTest.js @@ -118,7 +118,7 @@ app.route('/cve-cna-negative-tests/:id') getCveIdRepository: () => { return new MyCveIdNegativeTests() }, getCveRepository: () => { return new MyCveNegativeTests() }, getBaseOrgRepository: () => { return new MyOrg() }, - getUserRepository: () => { return new MyUser() } + getBaseUserRepository: () => { return new MyUser() } } req.ctx.repositories = factory next() @@ -130,7 +130,7 @@ app.route('/cve-cna-positive-tests/:id') getCveIdRepository: () => { return new MyCveIdPositiveTests() }, getCveRepository: () => { return new MyCvePositiveTests() }, getBaseOrgRepository: () => { return new MyOrg() }, - getUserRepository: () => { return new MyUser() } + getBaseUserRepository: () => { return new MyUser() } } req.ctx.repositories = factory next() diff --git a/test/unit-tests/cve/cveCnaContainerUpdateTest.js b/test/unit-tests/cve/cveCnaContainerUpdateTest.js index 8052b4ff7..6cf912a51 100644 --- a/test/unit-tests/cve/cveCnaContainerUpdateTest.js +++ b/test/unit-tests/cve/cveCnaContainerUpdateTest.js @@ -129,7 +129,7 @@ app.route('/cve-cna-negative-tests/:id') getCveIdRepository: () => { return new MyCveIdNegativeTests() }, getCveRepository: () => { return new MyCveNegativeTests() }, getBaseOrgRepository: () => { return new MyOrg() }, - getUserRepository: () => { return new MyUser() } + getBaseUserRepository: () => { return new MyUser() } } req.ctx.repositories = factory next() @@ -141,7 +141,7 @@ app.route('/cve-cna-positive-tests/:id') getCveIdRepository: () => { return new MyCveIdPositiveTests() }, getCveRepository: () => { return new MyCvePositiveTests() }, getBaseOrgRepository: () => { return new MyOrg() }, - getUserRepository: () => { return new MyUser() } + getBaseUserRepository: () => { return new MyUser() } } req.ctx.repositories = factory next() diff --git a/test/unit-tests/cve/cveCreateTest.js b/test/unit-tests/cve/cveCreateTest.js index 678bdc423..fb1dbea36 100644 --- a/test/unit-tests/cve/cveCreateTest.js +++ b/test/unit-tests/cve/cveCreateTest.js @@ -118,7 +118,7 @@ app.route('/cve-create-record-negative-tests/:id') getCveRepository: () => { return new MyCveNegativeTests() }, getCveIdRepository: () => { return new MyCveIdNegativeTests() }, getBaseOrgRepository: () => { return new MyOrg() }, - getUserRepository: () => { return new MyUser() } + getBaseUserRepository: () => { return new MyUser() } } req.ctx.repositories = factory next() @@ -130,7 +130,7 @@ app.route('/cve-create-record-positive-tests/:id') getCveRepository: () => { return new MyCvePositiveTests() }, getCveIdRepository: () => { return new MyCveIdPositiveTests() }, getBaseOrgRepository: () => { return new MyOrg() }, - getUserRepository: () => { return new MyUser() } + getBaseUserRepository: () => { return new MyUser() } } req.ctx.repositories = factory next() diff --git a/test/unit-tests/cve/cveRecordRejectionTest.js b/test/unit-tests/cve/cveRecordRejectionTest.js index 68be16d80..407fe6f3b 100644 --- a/test/unit-tests/cve/cveRecordRejectionTest.js +++ b/test/unit-tests/cve/cveRecordRejectionTest.js @@ -107,7 +107,7 @@ app.route('/cve-reject-negative-tests/:id') getCveIdRepository: () => { return new MyCveIdNegativeTests() }, getCveRepository: () => { return new MyCveNegativeTests() }, getBaseOrgRepository: () => { return new MyOrg() }, - getUserRepository: () => { return new MyUser() } + getBaseUserRepository: () => { return new MyUser() } } req.ctx.repositories = factory next() @@ -119,7 +119,7 @@ app.route('/cve-reject-positive-tests/:id') getCveIdRepository: () => { return new MyCveIdPositiveTests() }, getCveRepository: () => { return new MyCvePositiveTests() }, getBaseOrgRepository: () => { return new MyOrg() }, - getUserRepository: () => { return new MyUser() } + getBaseUserRepository: () => { return new MyUser() } } req.ctx.repositories = factory next() diff --git a/test/unit-tests/cve/cveUpdateTest.js b/test/unit-tests/cve/cveUpdateTest.js index 4386f7887..b7c026c55 100644 --- a/test/unit-tests/cve/cveUpdateTest.js +++ b/test/unit-tests/cve/cveUpdateTest.js @@ -63,7 +63,7 @@ describe('Testing the PUT /cve/:id endpoint in Cve Controller', () => { getCveIdRepository: () => { return new CveIdRepo() }, getCveRepository: () => { return new CveRepo() }, getBaseOrgRepository: () => { return new OrgRepo() }, - getUserRepository: () => { return new UserRepo() } + getBaseUserRepository: () => { return new UserRepo() } } req.ctx.repositories = factory next() @@ -127,7 +127,7 @@ describe('Testing the PUT /cve/:id endpoint in Cve Controller', () => { getCveIdRepository: () => { return new CveIdRepo() }, getCveRepository: () => { return new CveRepo() }, getBaseOrgRepository: () => { return new OrgRepo() }, - getUserRepository: () => { return new UserRepo() } + getBaseUserRepository: () => { return new UserRepo() } } req.ctx.repositories = factory next() @@ -192,7 +192,7 @@ describe('Testing the PUT /cve/:id endpoint in Cve Controller', () => { getCveIdRepository: () => { return new CveIdRepo() }, getCveRepository: () => { return new CveRepo() }, getBaseOrgRepository: () => { return new OrgRepo() }, - getUserRepository: () => { return new UserRepo() } + getBaseUserRepository: () => { return new UserRepo() } } req.ctx.repositories = factory next() @@ -243,7 +243,7 @@ describe('Testing the PUT /cve/:id endpoint in Cve Controller', () => { getCveIdRepository: () => { return new CveIdRepo() }, getCveRepository: () => { return new CveRepo() }, getBaseOrgRepository: () => { return new OrgRepo() }, - getUserRepository: () => { return new UserRepo() } + getBaseUserRepository: () => { return new UserRepo() } } req.ctx.repositories = factory next() @@ -309,7 +309,7 @@ describe('Testing the PUT /cve/:id endpoint in Cve Controller', () => { getCveIdRepository: () => { return new CveIdRepo() }, getCveRepository: () => { return new CveRepo() }, getBaseOrgRepository: () => { return new OrgRepo() }, - getUserRepository: () => { return new UserRepo() } + getBaseUserRepository: () => { return new UserRepo() } } req.ctx.repositories = factory next() @@ -368,7 +368,7 @@ describe('Testing the PUT /cve/:id endpoint in Cve Controller', () => { getCveIdRepository: () => { return new CveIdRepo() }, getCveRepository: () => { return new CveRepo() }, getBaseOrgRepository: () => { return new OrgRepo() }, - getUserRepository: () => { return new UserRepo() } + getBaseUserRepository: () => { return new UserRepo() } } req.ctx.repositories = factory next() diff --git a/test/unit-tests/cve/insertAdpTest.js b/test/unit-tests/cve/insertAdpTest.js index 0b351a4e0..fe8f44373 100644 --- a/test/unit-tests/cve/insertAdpTest.js +++ b/test/unit-tests/cve/insertAdpTest.js @@ -14,7 +14,7 @@ const constants = require('../../../src/constants').getConstants() const BaseOrgRepository = require('../../../src/repositories/baseOrgRepository.js') const CveIdRepository = require('../../../src/repositories/cveIdRepository.js') const CveRepository = require('../../../src/repositories/cveRepository.js') -const UserRepository = require('../../../src/repositories/userRepository.js') +const BaseUserRepository = require('../../../src/repositories/baseUserRepository.js') const adpUUID = faker.datatype.uuid() @@ -50,7 +50,7 @@ const stubCveId = { describe('Testing insertAdp function', () => { let status, json, res, next, getBaseOrgRepository, orgRepo, getCveRepository, cveRepo, getCveIdRepository, - cveIdRepo, getUserRepository, userRepo, adpContainerCopy, + cveIdRepo, getBaseUserRepository, userRepo, adpContainerCopy, cveCopy, req // Stub out functions called in insertAdp and reset them for each test @@ -64,9 +64,9 @@ describe('Testing insertAdp function', () => { getBaseOrgRepository = sinon.stub() getBaseOrgRepository.returns(orgRepo) - userRepo = new UserRepository() - getUserRepository = sinon.stub() - getUserRepository.returns(userRepo) + userRepo = new BaseUserRepository() + getBaseUserRepository = sinon.stub() + getBaseUserRepository.returns(userRepo) cveRepo = new CveRepository() getCveRepository = sinon.stub() @@ -95,7 +95,7 @@ describe('Testing insertAdp function', () => { }, repositories: { getBaseOrgRepository, - getUserRepository, + getBaseUserRepository, getCveRepository, getCveIdRepository }, diff --git a/test/unit-tests/cve/updateCnaTest.js b/test/unit-tests/cve/updateCnaTest.js index 1a718cc3d..b036731ce 100644 --- a/test/unit-tests/cve/updateCnaTest.js +++ b/test/unit-tests/cve/updateCnaTest.js @@ -55,7 +55,7 @@ describe('updateCna function', () => { let userRepo let getCveRepository let getCveIdRepository - let getUserRepository + let getBaseUserRepository let getBaseOrgRepository let cveCopy let cnaContainerCopy @@ -78,8 +78,8 @@ describe('updateCna function', () => { userRepo = { getUserUUID: sinon.stub() } userRepo.getUserUUID.returns(stubCnaUser.UUID) - getUserRepository = sinon.stub() - getUserRepository.returns(userRepo) + getBaseUserRepository = sinon.stub() + getBaseUserRepository.returns(userRepo) cveRepo = { findOneByCveId: sinon.stub(), updateByCveId: sinon.stub() } cveRepo.findOneByCveId.returns({ cve: cveCopy }) @@ -101,7 +101,7 @@ describe('updateCna function', () => { }, repositories: { getBaseOrgRepository, - getUserRepository, + getBaseUserRepository, getCveRepository, getCveIdRepository }, From 11a75477535330c65567a76056663f40d9c952e2 Mon Sep 17 00:00:00 2001 From: James Dalphond Date: Wed, 19 Aug 2026 13:28:17 -0400 Subject: [PATCH 18/20] Remove legacy User dual-writes from BaseOrg admin role updates Make BaseOrg the sole source of ADMIN membership by removing the legacy User collection updates from addAdmin() and removeAdmin(). Legacy user responses continue to derive active_roles from BaseOrg membership through the compatibility projection. Expand admin-role integration coverage to verify that: - legacy grants remain visible through registry reads, - registry revokes remain visible through both API formats, and - registry grants remain visible through legacy reads. --- src/repositories/baseOrgRepository.js | 18 ------------- .../org/legacyAdminRoleRevokeTest.js | 26 ++++++++++++++++--- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/repositories/baseOrgRepository.js b/src/repositories/baseOrgRepository.js index dd15ce920..85924c59f 100644 --- a/src/repositories/baseOrgRepository.js +++ b/src/repositories/baseOrgRepository.js @@ -468,9 +468,6 @@ class BaseOrgRepository extends BaseRepository { * @returns {Promise} */ async addAdmin (orgShortName, userUUID, options = {}, requestingUserUUID = null) { - const UserRepository = require('./userRepository') - const legacyUserRepo = new UserRepository() - const executeOptions = { ...options, new: false } const originalOrg = await BaseOrgModel.findOneAndUpdate( @@ -485,12 +482,6 @@ class BaseOrgRepository extends BaseRepository { await createAuditLogEntry(updatedOrg, originalOrg.toObject(), requestingUserUUID, options) } - await legacyUserRepo.collection.findOneAndUpdate( - { UUID: userUUID }, - { $addToSet: { 'authority.active_roles': 'ADMIN' } }, - options - ) - return updatedOrg } @@ -504,9 +495,6 @@ class BaseOrgRepository extends BaseRepository { * @returns {Promise} */ async removeAdmin (orgShortName, userUUID, options = {}, requestingUserUUID = null) { - const UserRepository = require('./userRepository') - const legacyUserRepo = new UserRepository() - const executeOptions = { ...options, new: false } const originalOrg = await BaseOrgModel.findOneAndUpdate( @@ -521,12 +509,6 @@ class BaseOrgRepository extends BaseRepository { await createAuditLogEntry(updatedOrg, originalOrg.toObject(), requestingUserUUID, options) } - await legacyUserRepo.collection.findOneAndUpdate( - { UUID: userUUID }, - { $pull: { 'authority.active_roles': 'ADMIN' } }, - options - ) - return updatedOrg } diff --git a/test/integration-tests/org/legacyAdminRoleRevokeTest.js b/test/integration-tests/org/legacyAdminRoleRevokeTest.js index c80875376..216a23f1c 100644 --- a/test/integration-tests/org/legacyAdminRoleRevokeTest.js +++ b/test/integration-tests/org/legacyAdminRoleRevokeTest.js @@ -21,7 +21,7 @@ const postNewUser = async (orgShortName, username) => { } describe('Legacy Admin Role Grant and Revoke Test', () => { - it('Should successfully add ADMIN role via legacy endpoint and revoke it, reflecting in both collections', async () => { + it('Should reflect registry and legacy ADMIN updates across both API formats', async () => { const orgShortName = 'test_org_admin_revoke' const username = 'test_user_admin_revoke' @@ -73,8 +73,8 @@ describe('Legacy Admin Role Grant and Revoke Test', () => { expect(res.body.message).to.contain('Role ADMIN revoked from user') }) - // // 5. Ensure that they are not admin in both collections - // // Check Registry Endpoint + // 5. Ensure that they are not admin in both API formats. + // Check Registry Endpoint await chai.request(app) .get(`/api/registry/org/${orgShortName}/user/${username}`) .set(secretariatHeaders) @@ -83,7 +83,7 @@ describe('Legacy Admin Role Grant and Revoke Test', () => { expect(res.body.role).to.not.equal('ADMIN') }) - // // Check Legacy Endpoint + // Check Legacy Endpoint await chai.request(app) .get(`/api/org/${orgShortName}/user/${username}`) .set(secretariatHeaders) @@ -91,5 +91,23 @@ describe('Legacy Admin Role Grant and Revoke Test', () => { expect(res).to.have.status(200) expect(res.body.authority.active_roles).to.not.include('ADMIN') }) + + // 6. Grant ADMIN through the registry endpoint and verify the legacy projection. + await chai.request(app) + .post(`/api/registry/org/${orgShortName}/user/${username}/grant-role`) + .set(secretariatHeaders) + .send({ role: 'ADMIN' }) + .then((res) => { + expect(res).to.have.status(200) + expect(res.body.message).to.contain('Role ADMIN granted to user') + }) + + await chai.request(app) + .get(`/api/org/${orgShortName}/user/${username}`) + .set(secretariatHeaders) + .then((res) => { + expect(res).to.have.status(200) + expect(res.body.authority.active_roles).to.include('ADMIN') + }) }) }) From cae7f151fc23664730c0c068a2ecc046f647d3bd Mon Sep 17 00:00:00 2001 From: James Dalphond Date: Wed, 19 Aug 2026 13:34:45 -0400 Subject: [PATCH 19/20] Remove retired UserRepository factory support Remove the legacy UserRepository factory import and getter now that all production callers use BaseUserRepository. Delete the retired UserRepository implementation and its unit coverage. Update remaining middleware, user-update, and reservation test helpers to use BaseOrgRepository and BaseUserRepository exclusively. --- src/repositories/repositoryFactory.js | 6 -- src/repositories/userRepository.js | 69 ------------------- test-utils/reserveCaseA.non-sequential.js | 6 +- test-utils/reserveCaseB.non-sequential.js | 6 +- test-utils/reserveCaseE.non-sequential.js | 6 +- test-utils/reserveCaseF.non-sequential.js | 6 +- test-utils/reserveCaseG.non-sequential.js | 6 +- .../onlySecretariatOrAdminMiddlewareTest.js | 4 -- .../unit-tests/middleware/validateUserTest.js | 17 +---- test/unit-tests/user/userRepositoryTest.js | 28 -------- test/unit-tests/user/userUpdateTest.js | 42 ++++------- 11 files changed, 32 insertions(+), 164 deletions(-) delete mode 100644 src/repositories/userRepository.js delete mode 100644 test/unit-tests/user/userRepositoryTest.js diff --git a/src/repositories/repositoryFactory.js b/src/repositories/repositoryFactory.js index 8e3dce9c1..8f44c67e3 100644 --- a/src/repositories/repositoryFactory.js +++ b/src/repositories/repositoryFactory.js @@ -1,7 +1,6 @@ const CveRepository = require('./cveRepository') const CveIdRepository = require('./cveIdRepository') const CveIdRangeRepository = require('./cveIdRangeRepository') -const UserRepository = require('./userRepository') const BaseOrgRepository = require('./baseOrgRepository') const BaseUserRepository = require('./baseUserRepository') const ConversationRepository = require('./conversationRepository') @@ -24,11 +23,6 @@ class RepositoryFactory { return repo } - getUserRepository () { - const repo = new UserRepository() - return repo - } - getBaseOrgRepository () { const repo = new BaseOrgRepository() return repo diff --git a/src/repositories/userRepository.js b/src/repositories/userRepository.js deleted file mode 100644 index 346710878..000000000 --- a/src/repositories/userRepository.js +++ /dev/null @@ -1,69 +0,0 @@ -const BaseRepository = require('./baseRepository') -const User = require('../model/user') - -class UserRepository extends BaseRepository { - constructor () { - super(User) - } - - async getUserUUID (userName, orgUUID, options = {}) { - const utils = require('../utils/utils') - return utils.getUserUUID(userName, orgUUID, false, options) - } - - async isAdmin (username, shortname, options = {}) { - const utils = require('../utils/utils') - return utils.isAdmin(username, shortname, false, options) - } - - async isAdminUUID (username, orgUUID) { - const utils = require('../utils/utils') - return utils.isAdminUUID(username, orgUUID) - } - - async findOneByUUID (UUID) { - return this.collection.findOne().byUUID(UUID) - } - - async findUsersByOrgUUID (orgUUID, options = {}) { - // Assumes your User schema has a field named 'org_UUID' linking to the organization. - const filter = { org_UUID: orgUUID } - return this.collection.countDocuments(filter, options) - } - - async updateByUUID (uuid, updatePayload, options = {}) { - const filter = { UUID: uuid } - const updateOperation = { $set: updatePayload } - return this.collection.findOneAndUpdate(filter, updateOperation, options) - } - - async findOneByUserNameAndOrgUUID (userName, orgUUID, projection = null, options = {}) { - const query = { username: userName, org_UUID: orgUUID } - return this.collection.findOne(query, projection, options) - } - - async findUserByUsernameAndOrgUUID (username, orgUUID, options = {}) { - return this.findOneByUserNameAndOrgUUID(username, orgUUID, null, options) - } - - async isUserAdminOfOrgUUID (userUUID, orgUUID, options = {}) { - if (!userUUID || !orgUUID) { - return false - } - - const user = await this.collection.findOne({ UUID: userUUID, org_UUID: orgUUID }, null, options) - return Array.isArray(user?.authority?.active_roles) && user.authority.active_roles.includes('ADMIN') - } - - async updateByUserNameAndOrgUUID (username, orgUUID, user, options = {}) { - const filter = { username: username, org_UUID: orgUUID } - const updatePayload = { $set: user } - return this.collection.findOneAndUpdate(filter, updatePayload, options) - } - - async getAllUsers (options = {}, projection = {}) { - return this.collection.find({}, projection, options) - } -} - -module.exports = UserRepository diff --git a/test-utils/reserveCaseA.non-sequential.js b/test-utils/reserveCaseA.non-sequential.js index b01f73956..b063a35c7 100644 --- a/test-utils/reserveCaseA.non-sequential.js +++ b/test-utils/reserveCaseA.non-sequential.js @@ -15,7 +15,7 @@ async function reserveCveId (req, res) { let amount let shortName let year - const orgRepo = req.ctx.repositories.getOrgRepository() + const orgRepo = req.ctx.repositories.getBaseOrgRepository() Object.keys(req.ctx.query).forEach(k => { const key = k.toLowerCase() @@ -105,8 +105,8 @@ async function nonSequentialReservation (year, amount, shortName, cnaShortName, } // Case 2: Enough IDs in the 'AVAILABLE' pool - const orgRepo = req.ctx.repositories.getOrgRepository() - const userRepo = req.ctx.repositories.getUserRepository() + const orgRepo = req.ctx.repositories.getBaseOrgRepository() + const userRepo = req.ctx.repositories.getBaseUserRepository() let index let counter = 0 let dummyCounter = 0 diff --git a/test-utils/reserveCaseB.non-sequential.js b/test-utils/reserveCaseB.non-sequential.js index 709074263..f80f9da1e 100644 --- a/test-utils/reserveCaseB.non-sequential.js +++ b/test-utils/reserveCaseB.non-sequential.js @@ -16,7 +16,7 @@ async function reserveCveId (req, res) { let amount let shortName let year - const orgRepo = req.ctx.repositories.getOrgRepository() + const orgRepo = req.ctx.repositories.getBaseOrgRepository() Object.keys(req.ctx.query).forEach(k => { const key = k.toLowerCase() @@ -106,8 +106,8 @@ async function nonSequentialReservation (year, amount, shortName, cnaShortName, } // Case 2: Enough IDs in the 'AVAILABLE' pool - const orgRepo = req.ctx.repositories.getOrgRepository() - const userRepo = req.ctx.repositories.getUserRepository() + const orgRepo = req.ctx.repositories.getBaseOrgRepository() + const userRepo = req.ctx.repositories.getBaseUserRepository() let index let counter = 0 let dummyCounter = 0 diff --git a/test-utils/reserveCaseE.non-sequential.js b/test-utils/reserveCaseE.non-sequential.js index fd02b82fb..b782feb96 100644 --- a/test-utils/reserveCaseE.non-sequential.js +++ b/test-utils/reserveCaseE.non-sequential.js @@ -16,7 +16,7 @@ async function reserveCveId (req, res) { let amount let shortName let year - const orgRepo = req.ctx.repositories.getOrgRepository() + const orgRepo = req.ctx.repositories.getBaseOrgRepository() Object.keys(req.ctx.query).forEach(k => { const key = k.toLowerCase() @@ -106,8 +106,8 @@ async function nonSequentialReservation (year, amount, shortName, cnaShortName, } // Case 2: Enough IDs in the 'AVAILABLE' pool - const orgRepo = req.ctx.repositories.getOrgRepository() - const userRepo = req.ctx.repositories.getUserRepository() + const orgRepo = req.ctx.repositories.getBaseOrgRepository() + const userRepo = req.ctx.repositories.getBaseUserRepository() let index let counter = 0 let dummyCounter = 0 diff --git a/test-utils/reserveCaseF.non-sequential.js b/test-utils/reserveCaseF.non-sequential.js index abc638b44..17c60d8ac 100644 --- a/test-utils/reserveCaseF.non-sequential.js +++ b/test-utils/reserveCaseF.non-sequential.js @@ -15,7 +15,7 @@ async function reserveCveId (req, res) { let amount let shortName let year - const orgRepo = req.ctx.repositories.getOrgRepository() + const orgRepo = req.ctx.repositories.getBaseOrgRepository() Object.keys(req.ctx.query).forEach(k => { const key = k.toLowerCase() @@ -49,8 +49,8 @@ async function reserveCveId (req, res) { async function nonSequentialReservation (year, amount, shortName, cnaShortName, requester, availableIds, res, req) { const cveIdRepo = req.ctx.repositories.getCveIdRepository() const cveIdRangeRepo = req.ctx.repositories.getCveIdRangeRepository() - const orgRepo = req.ctx.repositories.getOrgRepository() - const userRepo = req.ctx.repositories.getUserRepository() + const orgRepo = req.ctx.repositories.getBaseOrgRepository() + const userRepo = req.ctx.repositories.getBaseUserRepository() const reqUUID = req.ctx.uuid const CONSTANTS = getConstants() let isFull = false diff --git a/test-utils/reserveCaseG.non-sequential.js b/test-utils/reserveCaseG.non-sequential.js index aec65bc43..81494e5f4 100644 --- a/test-utils/reserveCaseG.non-sequential.js +++ b/test-utils/reserveCaseG.non-sequential.js @@ -15,7 +15,7 @@ async function reserveCveId (req, res) { let amount let shortName let year - const orgRepo = req.ctx.repositories.getOrgRepository() + const orgRepo = req.ctx.repositories.getBaseOrgRepository() Object.keys(req.ctx.query).forEach(k => { const key = k.toLowerCase() @@ -49,8 +49,8 @@ async function reserveCveId (req, res) { async function nonSequentialReservation (year, amount, shortName, cnaShortName, requester, availableIds, res, req) { const cveIdRepo = req.ctx.repositories.getCveIdRepository() const cveIdRangeRepo = req.ctx.repositories.getCveIdRangeRepository() - const orgRepo = req.ctx.repositories.getOrgRepository() - const userRepo = req.ctx.repositories.getUserRepository() + const orgRepo = req.ctx.repositories.getBaseOrgRepository() + const userRepo = req.ctx.repositories.getBaseUserRepository() const reqUUID = req.ctx.uuid const CONSTANTS = getConstants() let isFull = false diff --git a/test/unit-tests/middleware/onlySecretariatOrAdminMiddlewareTest.js b/test/unit-tests/middleware/onlySecretariatOrAdminMiddlewareTest.js index 925cf5c90..299d5a471 100644 --- a/test/unit-tests/middleware/onlySecretariatOrAdminMiddlewareTest.js +++ b/test/unit-tests/middleware/onlySecretariatOrAdminMiddlewareTest.js @@ -48,7 +48,6 @@ app.route('/only-secretariat-or-admin-pass') .post((req, res, next) => { const factory = { getBaseOrgRepository: () => { return new OrgOnlySecretariatOrAdmin() }, - getUserRepository: () => { return new UserOnlySecretariatOrAdmin() }, getBaseUserRepository: () => { return new UserOnlySecretariatOrAdmin() } } req.ctx.repositories = factory @@ -143,7 +142,6 @@ describe('Test only Secretariat or Org Admin user middleware', () => { .post((req, res, next) => { const factory = { getBaseOrgRepository: () => { return new OrgOnlySecretariatOrAdminRejectByUUID() }, - getUserRepository: () => { return new UserOnlySecretariatOrAdminRejectByUUID() }, getBaseUserRepository: () => { return new UserOnlySecretariatOrAdminRejectByUUID() } } req.ctx.repositories = factory @@ -196,7 +194,6 @@ describe('Test only Secretariat or Org Admin user middleware', () => { .post((req, res, next) => { const factory = { getBaseOrgRepository: () => { return new OrgOnlySecretariatOrAdminRejectMissingUserUUID() }, - getUserRepository: () => { return new UserOnlySecretariatOrAdminRejectMissingUserUUID() }, getBaseUserRepository: () => { return new UserOnlySecretariatOrAdminRejectMissingUserUUID() } } req.ctx.repositories = factory @@ -230,7 +227,6 @@ describe('Test only Secretariat or Org Admin user middleware', () => { .post((req, res, next) => { const factory = { getBaseOrgRepository: () => { return new OrgOnlySecretariatOrAdmin() }, - getUserRepository: () => { return new UserOnlySecretariatOrAdmin() }, getBaseUserRepository: () => { return new UserOnlySecretariatOrAdmin() } } req.ctx.repositories = factory diff --git a/test/unit-tests/middleware/validateUserTest.js b/test/unit-tests/middleware/validateUserTest.js index 25c6a976b..29db747b6 100644 --- a/test/unit-tests/middleware/validateUserTest.js +++ b/test/unit-tests/middleware/validateUserTest.js @@ -72,7 +72,6 @@ describe('Testing the user validation middleware', () => { app.route('/validate-user-org-doesnt-exist') .post((req, res, next) => { const factory = { - getUserRepository: () => { return new NullUserRepo() }, getBaseUserRepository: () => { return new NullUserRepo() }, getBaseOrgRepository: () => { return new NullOrgRepo() } } @@ -108,7 +107,6 @@ describe('Testing the user validation middleware', () => { app.route('/validate-user-user-not-found') .post((req, res, next) => { const factory = { - getUserRepository: () => { return new NullUserRepo() }, getBaseUserRepository: () => { return new NullUserRepo() }, getBaseOrgRepository: () => { return new NullOrgRepo() } } @@ -143,9 +141,7 @@ describe('Testing the user validation middleware', () => { it('Secret apikey is incorrect', (done) => { app.route('/validate-user-key-incorrect') .post((req, res, next) => { - const factory = { - getUserRepository: () => { return new UserValidateUserSuccess() }, - getBaseOrgRepository: () => { return new OrgValidateUserSuccess() }, + const factory = { getBaseOrgRepository: () => { return new OrgValidateUserSuccess() }, getBaseUserRepository: () => { return new UserValidateUserSuccess() } } req.ctx.repositories = factory @@ -185,9 +181,7 @@ describe('Testing the user validation middleware', () => { app.route('/validate-user-deactivated') .post((req, res, next) => { - const factory = { - getUserRepository: () => { return new UserValidateUserDeactivated() }, - getBaseOrgRepository: () => { return new OrgValidateUserSuccess() }, + const factory = { getBaseOrgRepository: () => { return new OrgValidateUserSuccess() }, getBaseUserRepository: () => { return new UserValidateUserDeactivated() } } req.ctx.repositories = factory @@ -225,7 +219,6 @@ describe('Testing the user validation middleware', () => { app.route('/validate-user-org-undefined') .post((req, res, next) => { const factory = { - getUserRepository: () => { return new NullUserRepo() }, getBaseUserRepository: () => { return new NullUserRepo() }, getBaseOrgRepository: () => { return new NullOrgRepo() } } @@ -261,7 +254,6 @@ describe('Testing the user validation middleware', () => { app.route('/validate-user-submitter-undefined') .post((req, res, next) => { const factory = { - getUserRepository: () => { return new NullUserRepo() }, getBaseUserRepository: () => { return new NullUserRepo() }, getBaseOrgRepository: () => { return new NullOrgRepo() } } @@ -297,7 +289,6 @@ describe('Testing the user validation middleware', () => { app.route('/validate-user-secret-undefined') .post((req, res, next) => { const factory = { - getUserRepository: () => { return new NullUserRepo() }, getBaseUserRepository: () => { return new NullUserRepo() }, getBaseOrgRepository: () => { return new NullOrgRepo() } } @@ -334,9 +325,7 @@ describe('Testing the user validation middleware', () => { it('User is successfully validated', (done) => { app.route('/validate-user-successful') .post((req, res, next) => { - const factory = { - getUserRepository: () => { return new UserValidateUserSuccess() }, - getBaseOrgRepository: () => { return new OrgValidateUserSuccess() }, + const factory = { getBaseOrgRepository: () => { return new OrgValidateUserSuccess() }, getBaseUserRepository: () => { return new UserValidateUserSuccess() } } req.ctx.repositories = factory diff --git a/test/unit-tests/user/userRepositoryTest.js b/test/unit-tests/user/userRepositoryTest.js deleted file mode 100644 index 2f283e5e2..000000000 --- a/test/unit-tests/user/userRepositoryTest.js +++ /dev/null @@ -1,28 +0,0 @@ -const { expect } = require('chai') -const sinon = require('sinon') - -const UserRepository = require('../../../src/repositories/userRepository') -const utils = require('../../../src/utils/utils') - -describe('Testing UserRepository', () => { - afterEach(() => { - sinon.restore() - }) - - it('Should forward options as the fourth argument when getting a user UUID', async () => { - const userRepo = new UserRepository() - const options = { session: 'mock-session' } - const getUserUUID = sinon.stub(utils, 'getUserUUID').resolves('user-uuid') - - const result = await userRepo.getUserUUID('user@example.org', 'org-uuid', options) - - expect(result).to.equal('user-uuid') - expect(getUserUUID.calledOnce).to.equal(true) - expect(getUserUUID.firstCall.args).to.deep.equal([ - 'user@example.org', - 'org-uuid', - false, - options - ]) - }) -}) diff --git a/test/unit-tests/user/userUpdateTest.js b/test/unit-tests/user/userUpdateTest.js index da04869e3..d7ea37946 100644 --- a/test/unit-tests/user/userUpdateTest.js +++ b/test/unit-tests/user/userUpdateTest.js @@ -172,8 +172,7 @@ describe('Testing the PUT /org/:shortname/user/:username endpoint in Org Control .put((req, res, next) => { const factory = { getBaseOrgRepository: () => { return new OrgUserNotUpdatedOrgDoesntExist() }, - getBaseUserRepository: () => { return new NullUserRepo() }, - getUserRepository: () => { return new NullUserRepo() } + getBaseUserRepository: () => { return new NullUserRepo() } } req.ctx.repositories = factory next() @@ -232,8 +231,7 @@ describe('Testing the PUT /org/:shortname/user/:username endpoint in Org Control .put((req, res, next) => { const factory = { getBaseOrgRepository: () => { return new OrgUserNotUpdatedUserDoesntExist() }, - getBaseUserRepository: () => { return new UserNotUpdatedUserDoesntExist() }, - getUserRepository: () => { return new UserNotUpdatedUserDoesntExist() } + getBaseUserRepository: () => { return new UserNotUpdatedUserDoesntExist() } } req.ctx.repositories = factory next() @@ -278,8 +276,7 @@ describe('Testing the PUT /org/:shortname/user/:username endpoint in Org Control .put((req, res, next) => { const factory = { getBaseOrgRepository: () => { return new OrgUserNotUpdatedOrgQueryDoesntExist() }, - getBaseUserRepository: () => { return new UserNotUpdatedOrgQueryDoesntExist() }, - getUserRepository: () => { return new UserNotUpdatedOrgQueryDoesntExist() } + getBaseUserRepository: () => { return new UserNotUpdatedOrgQueryDoesntExist() } } req.ctx.repositories = factory next() @@ -342,8 +339,7 @@ describe('Testing the PUT /org/:shortname/user/:username endpoint in Org Control .put((req, res, next) => { const factory = { getBaseOrgRepository: () => { return new Org() }, - getBaseUserRepository: () => { return new User() }, - getUserRepository: () => { return new User() } + getBaseUserRepository: () => { return new User() } } req.ctx.repositories = factory next() @@ -408,8 +404,7 @@ describe('Testing the PUT /org/:shortname/user/:username endpoint in Org Control .put((req, res, next) => { const factory = { getBaseOrgRepository: () => { return new Org() }, - getBaseUserRepository: () => { return new User() }, - getUserRepository: () => { return new User() } + getBaseUserRepository: () => { return new User() } } req.ctx.repositories = factory setGoogleOrgUUIDContext(req) @@ -477,8 +472,7 @@ describe('Testing the PUT /org/:shortname/user/:username endpoint in Org Control .put((req, res, next) => { const factory = { getBaseOrgRepository: () => { return new Org() }, - getBaseUserRepository: () => { return new User() }, - getUserRepository: () => { return new User() } + getBaseUserRepository: () => { return new User() } } req.ctx.repositories = factory next() @@ -545,8 +539,7 @@ describe('Testing the PUT /org/:shortname/user/:username endpoint in Org Control .put((req, res, next) => { const factory = { getBaseOrgRepository: () => { return new Org() }, - getBaseUserRepository: () => { return new User() }, - getUserRepository: () => { return new User() } + getBaseUserRepository: () => { return new User() } } req.ctx.repositories = factory setGoogleOrgUUIDContext(req) @@ -577,8 +570,7 @@ describe('Testing the PUT /org/:shortname/user/:username endpoint in Org Control .put((req, res, next) => { const factory = { getBaseOrgRepository: () => { return new OrgUserUpdatedAddingRole() }, - getBaseUserRepository: () => { return new UserUpdatedAddingRole() }, - getUserRepository: () => { return new UserUpdatedAddingRole() } + getBaseUserRepository: () => { return new UserUpdatedAddingRole() } } req.ctx.repositories = factory next() @@ -681,8 +673,7 @@ describe('Testing the PUT /org/:shortname/user/:username endpoint in Org Control .put((req, res, next) => { const factory = { getBaseOrgRepository: () => { return new OrgUserUpdatedAddingRole() }, - getBaseUserRepository: () => { return new UserUpdatedAddingRoleAlreadyExists() }, - getUserRepository: () => { return new UserUpdatedAddingRoleAlreadyExists() } + getBaseUserRepository: () => { return new UserUpdatedAddingRoleAlreadyExists() } } req.ctx.repositories = factory next() @@ -796,8 +787,7 @@ describe('Testing the PUT /org/:shortname/user/:username endpoint in Org Control .put((req, res, next) => { const factory = { getBaseOrgRepository: () => { return new OrgUserUpdatedAddingRole() }, - getBaseUserRepository: () => { return new UserUpdatedRemovingRole() }, - getUserRepository: () => { return new UserUpdatedRemovingRole() } + getBaseUserRepository: () => { return new UserUpdatedRemovingRole() } } req.ctx.repositories = factory next() @@ -892,8 +882,7 @@ describe('Testing the PUT /org/:shortname/user/:username endpoint in Org Control .put((req, res, next) => { const factory = { getBaseOrgRepository: () => { return new OrgUserUpdatedAddingRole() }, - getBaseUserRepository: () => { return new UserUpdatedRemovingRoleAlreadyRemoved() }, - getUserRepository: () => { return new UserUpdatedRemovingRoleAlreadyRemoved() } + getBaseUserRepository: () => { return new UserUpdatedRemovingRoleAlreadyRemoved() } } req.ctx.repositories = factory next() @@ -1000,8 +989,7 @@ describe('Testing the PUT /org/:shortname/user/:username endpoint in Org Control .put((req, res, next) => { const factory = { getBaseOrgRepository: () => { return new Org() }, - getBaseUserRepository: () => { return new User() }, - getUserRepository: () => { return new User() } + getBaseUserRepository: () => { return new User() } } req.ctx.repositories = factory setGoogleOrgUUIDContext(req) @@ -1103,8 +1091,7 @@ describe('Testing the PUT /org/:shortname/user/:username endpoint in Org Control .put((req, res, next) => { const factory = { getBaseOrgRepository: () => { return new Org() }, - getBaseUserRepository: () => { return new User() }, - getUserRepository: () => { return new User() } + getBaseUserRepository: () => { return new User() } } req.ctx.repositories = factory setGoogleOrgUUIDContext(req) @@ -1181,8 +1168,7 @@ describe('Testing the PUT /org/:shortname/user/:username endpoint in Org Control .put((req, res, next) => { const factory = { getBaseOrgRepository: () => { return new OrgUserNotUpdatedOrgQueryDoesntExist() }, - getBaseUserRepository: () => { return new UserNotUpdatedNoQuery() }, - getUserRepository: () => { return new UserNotUpdatedNoQuery() } + getBaseUserRepository: () => { return new UserNotUpdatedNoQuery() } } req.ctx.repositories = factory next() From f0f5ff6c6fbd17149f3a61e84f0a0384927c90dc Mon Sep 17 00:00:00 2001 From: James Dalphond Date: Wed, 19 Aug 2026 16:34:40 -0400 Subject: [PATCH 20/20] Remove legacy Org/User population and migration path Seed canonical BaseOrg/BaseUser fixtures directly, including memberships, admin references, discriminator types, and quotas. Remove the legacy Org/User migration script and obsolete test setup references. Update population helpers and affected tests to use Base repositories only. Retain Monday migration support for existing BaseOrg/BaseUser data. --- .github/workflows/test-http.yml | 2 +- datadump/pre-population/orgs.json | 972 ++++++--- datadump/pre-population/users.json | 1808 ++++++++--------- src/scripts/MondayMigrate.js | 5 +- src/scripts/migrate.js | 271 --- src/scripts/populate.js | 38 +- src/scripts/test_data/postman/README.md | 2 +- src/utils/data.js | 56 +- src/utils/utils.js | 170 +- .../middleware/authenticatedContextTest.js | 73 +- .../user/legacyUserWriteContractTest.js | 3 - .../org/legacy/orgGetIdQuotaTest.js | 29 - 12 files changed, 1554 insertions(+), 1875 deletions(-) delete mode 100644 src/scripts/migrate.js diff --git a/.github/workflows/test-http.yml b/.github/workflows/test-http.yml index 13289186a..7730efb39 100644 --- a/.github/workflows/test-http.yml +++ b/.github/workflows/test-http.yml @@ -26,7 +26,7 @@ jobs: - name: Sleep run: bash -c "while ! docker compose --file docker/docker-compose.yml logs --tail=10 cveawg | grep -q 'Serving on port'; do sleep 1; done" - name: Load Data into MongoDb - run: docker compose -f docker/docker-compose.yml exec -T cveawg npm run populate:dev y; docker compose -f docker/docker-compose.yml exec -T cveawg npm run migrate:test-black-box + run: docker compose -f docker/docker-compose.yml exec -T cveawg npm run populate:dev y - name: Run Black Box Tests run: | docker compose --file test-http/docker/docker-compose.yml exec -T demon pytest src/ | tee test-http/src/testOutput.txt diff --git a/datadump/pre-population/orgs.json b/datadump/pre-population/orgs.json index fcee2199c..3e5acafe0 100644 --- a/datadump/pre-population/orgs.json +++ b/datadump/pre-population/orgs.json @@ -1,341 +1,673 @@ [ - { - "authority": { - "active_roles": [ - "SECRETARIAT", - "CNA" - ] - }, - "name": "MITRE Corporation", - "short_name": "mitre", - "time": { - "modified": "2021-05-11T15:07:19.468216Z", - "created": "2021-05-11T15:07:19.468216Z" - }, - "policies": { - "id_quota": 1248 - } + { + "UUID": "10000000-0000-4000-8000-000000000001", + "__t": "SecretariatOrg", + "long_name": "MITRE Corporation", + "short_name": "mitre", + "aliases": [], + "authority": [ + "SECRETARIAT", + "CNA" + ], + "top_level_root": "true", + "users": [ + "20000000-0000-4000-8000-000000000001", + "20000000-0000-4000-8000-000000000002", + "20000000-0000-4000-8000-000000000003", + "20000000-0000-4000-8000-000000000044", + "20000000-0000-4000-8000-000000000045", + "20000000-0000-4000-8000-000000000046", + "20000000-0000-4000-8000-000000000047", + "20000000-0000-4000-8000-000000000048", + "20000000-0000-4000-8000-000000000049", + "20000000-0000-4000-8000-000000000050", + "20000000-0000-4000-8000-000000000051", + "20000000-0000-4000-8000-000000000052", + "20000000-0000-4000-8000-000000000053", + "20000000-0000-4000-8000-000000000054", + "20000000-0000-4000-8000-000000000055", + "20000000-0000-4000-8000-000000000056", + "20000000-0000-4000-8000-000000000057", + "20000000-0000-4000-8000-000000000058", + "20000000-0000-4000-8000-000000000059", + "20000000-0000-4000-8000-000000000060", + "20000000-0000-4000-8000-000000000061" + ], + "admins": [], + "charter_or_scope": "All vulnerabilities, and Open Source software product vulnerabilities, not already covered by a CNA listed on this website.", + "disclosure_policy": "", + "product_list": null, + "id_quota": 1248, + "private_contacts": [], + "contact_info": { + "emails": [], + "websites": [], + "phone": null }, - { - "authority": { - "active_roles": [ - "CNA" - ] - }, - "name": "Booker LLC", - "short_name": "window_1", - "time": { - "modified": "2021-05-11T15:07:19.468216Z", - "created": "2021-05-11T15:07:19.468216Z" - }, - "policies": { - "id_quota": 1251 - } + "in_use": false, + "created": "2021-05-11T15:07:19.468216Z", + "last_updated": "2021-05-11T15:07:19.468216Z" + }, + { + "UUID": "10000000-0000-4000-8000-000000000002", + "__t": "CNAOrg", + "long_name": "Booker LLC", + "short_name": "window_1", + "aliases": [], + "authority": [ + "CNA" + ], + "top_level_root": "false", + "users": [ + "20000000-0000-4000-8000-000000000004", + "20000000-0000-4000-8000-000000000005" + ], + "admins": [ + "20000000-0000-4000-8000-000000000005" + ], + "charter_or_scope": null, + "disclosure_policy": "", + "product_list": null, + "id_quota": 1251, + "private_contacts": [], + "contact_info": { + "emails": [], + "websites": [], + "phone": null }, - { - "authority": { - "active_roles": [ - "CNA" - ] - }, - "name": "Singh, Smith and Fisher", - "short_name": "church_2", - "time": { - "modified": "2021-05-11T15:07:19.468216Z", - "created": "2021-05-11T15:07:19.468216Z" - }, - "policies": { - "id_quota": 1304 - } + "in_use": false, + "created": "2021-05-11T15:07:19.468216Z", + "last_updated": "2021-05-11T15:07:19.468216Z" + }, + { + "UUID": "10000000-0000-4000-8000-000000000003", + "__t": "CNAOrg", + "long_name": "Singh, Smith and Fisher", + "short_name": "church_2", + "aliases": [], + "authority": [ + "CNA" + ], + "top_level_root": "false", + "users": [ + "20000000-0000-4000-8000-000000000006", + "20000000-0000-4000-8000-000000000007" + ], + "admins": [ + "20000000-0000-4000-8000-000000000007" + ], + "charter_or_scope": null, + "disclosure_policy": "", + "product_list": null, + "id_quota": 1304, + "private_contacts": [], + "contact_info": { + "emails": [], + "websites": [], + "phone": null }, - { - "authority": { - "active_roles": [ - "CNA" - ] - }, - "name": "Foster-Reynolds", - "short_name": "officer_3", - "time": { - "modified": "2021-05-11T15:07:19.468216Z", - "created": "2021-05-11T15:07:19.468216Z" - }, - "policies": { - "id_quota": 669 - } + "in_use": false, + "created": "2021-05-11T15:07:19.468216Z", + "last_updated": "2021-05-11T15:07:19.468216Z" + }, + { + "UUID": "10000000-0000-4000-8000-000000000004", + "__t": "CNAOrg", + "long_name": "Foster-Reynolds", + "short_name": "officer_3", + "aliases": [], + "authority": [ + "CNA" + ], + "top_level_root": "false", + "users": [ + "20000000-0000-4000-8000-000000000008", + "20000000-0000-4000-8000-000000000009" + ], + "admins": [ + "20000000-0000-4000-8000-000000000009" + ], + "charter_or_scope": null, + "disclosure_policy": "", + "product_list": null, + "id_quota": 669, + "private_contacts": [], + "contact_info": { + "emails": [], + "websites": [], + "phone": null }, - { - "authority": { - "active_roles": [ - "CNA", - "ADP" - ] - }, - "name": "Clark and Sons", - "short_name": "range_4", - "time": { - "modified": "2021-05-11T15:07:19.468216Z", - "created": "2021-05-11T15:07:19.468216Z" - }, - "policies": { - "id_quota": 1415 - } + "in_use": false, + "created": "2021-05-11T15:07:19.468216Z", + "last_updated": "2021-05-11T15:07:19.468216Z" + }, + { + "UUID": "10000000-0000-4000-8000-000000000005", + "__t": "CNAOrg", + "long_name": "Clark and Sons", + "short_name": "range_4", + "aliases": [], + "authority": [ + "CNA", + "ADP" + ], + "top_level_root": "false", + "users": [ + "20000000-0000-4000-8000-000000000010", + "20000000-0000-4000-8000-000000000011" + ], + "admins": [ + "20000000-0000-4000-8000-000000000011" + ], + "charter_or_scope": null, + "disclosure_policy": "", + "product_list": null, + "id_quota": 1415, + "private_contacts": [], + "contact_info": { + "emails": [], + "websites": [], + "phone": null }, - { - "authority": { - "active_roles": [ - "CNA", - "ADP" - ] - }, - "name": "Adams, Nielsen and Hensley", - "short_name": "win_5", - "time": { - "modified": "2021-05-11T15:07:19.468216Z", - "created": "2021-05-11T15:07:19.468216Z" - }, - "policies": { - "id_quota": 1309 - } + "in_use": false, + "created": "2021-05-11T15:07:19.468216Z", + "last_updated": "2021-05-11T15:07:19.468216Z" + }, + { + "UUID": "10000000-0000-4000-8000-000000000006", + "__t": "CNAOrg", + "long_name": "Adams, Nielsen and Hensley", + "short_name": "win_5", + "aliases": [], + "authority": [ + "CNA", + "ADP" + ], + "top_level_root": "false", + "users": [ + "20000000-0000-4000-8000-000000000012", + "20000000-0000-4000-8000-000000000013" + ], + "admins": [ + "20000000-0000-4000-8000-000000000013" + ], + "charter_or_scope": null, + "disclosure_policy": "", + "product_list": null, + "id_quota": 1309, + "private_contacts": [], + "contact_info": { + "emails": [], + "websites": [], + "phone": null }, - { - "authority": { - "active_roles": [ - "CNA" - ] - }, - "name": "Carpenter-Huang", - "short_name": "activity_6", - "time": { - "modified": "2021-05-11T15:07:19.468216Z", - "created": "2021-05-11T15:07:19.468216Z" - }, - "policies": { - "id_quota": 657 - } + "in_use": false, + "created": "2021-05-11T15:07:19.468216Z", + "last_updated": "2021-05-11T15:07:19.468216Z" + }, + { + "UUID": "10000000-0000-4000-8000-000000000007", + "__t": "CNAOrg", + "long_name": "Carpenter-Huang", + "short_name": "activity_6", + "aliases": [], + "authority": [ + "CNA" + ], + "top_level_root": "false", + "users": [ + "20000000-0000-4000-8000-000000000014", + "20000000-0000-4000-8000-000000000015" + ], + "admins": [ + "20000000-0000-4000-8000-000000000015" + ], + "charter_or_scope": null, + "disclosure_policy": "", + "product_list": null, + "id_quota": 657, + "private_contacts": [], + "contact_info": { + "emails": [], + "websites": [], + "phone": null }, - { - "authority": { - "active_roles": [ - "CNA" - ] - }, - "name": "Anderson LLC", - "short_name": "seem_7", - "time": { - "modified": "2021-05-11T15:07:19.468216Z", - "created": "2021-05-11T15:07:19.468216Z" - }, - "policies": { - "id_quota": 794 - } + "in_use": false, + "created": "2021-05-11T15:07:19.468216Z", + "last_updated": "2021-05-11T15:07:19.468216Z" + }, + { + "UUID": "10000000-0000-4000-8000-000000000008", + "__t": "CNAOrg", + "long_name": "Anderson LLC", + "short_name": "seem_7", + "aliases": [], + "authority": [ + "CNA" + ], + "top_level_root": "false", + "users": [ + "20000000-0000-4000-8000-000000000016", + "20000000-0000-4000-8000-000000000017" + ], + "admins": [ + "20000000-0000-4000-8000-000000000017" + ], + "charter_or_scope": null, + "disclosure_policy": "", + "product_list": null, + "id_quota": 794, + "private_contacts": [], + "contact_info": { + "emails": [], + "websites": [], + "phone": null }, - { - "authority": { - "active_roles": [ - "CNA" - ] - }, - "name": "Wallace Group", - "short_name": "cause_8", - "time": { - "modified": "2021-05-11T15:07:19.468216Z", - "created": "2021-05-11T15:07:19.468216Z" - }, - "policies": { - "id_quota": 1094 - } + "in_use": false, + "created": "2021-05-11T15:07:19.468216Z", + "last_updated": "2021-05-11T15:07:19.468216Z" + }, + { + "UUID": "10000000-0000-4000-8000-000000000009", + "__t": "CNAOrg", + "long_name": "Wallace Group", + "short_name": "cause_8", + "aliases": [], + "authority": [ + "CNA" + ], + "top_level_root": "false", + "users": [ + "20000000-0000-4000-8000-000000000018", + "20000000-0000-4000-8000-000000000019" + ], + "admins": [ + "20000000-0000-4000-8000-000000000019" + ], + "charter_or_scope": null, + "disclosure_policy": "", + "product_list": null, + "id_quota": 1094, + "private_contacts": [], + "contact_info": { + "emails": [], + "websites": [], + "phone": null }, - { - "authority": { - "active_roles": [ - "CNA" - ] - }, - "name": "Alexander, Benson and Hicks", - "short_name": "admit_9", - "time": { - "modified": "2021-05-11T15:07:19.468216Z", - "created": "2021-05-11T15:07:19.468216Z" - }, - "policies": { - "id_quota": 1043 - } + "in_use": false, + "created": "2021-05-11T15:07:19.468216Z", + "last_updated": "2021-05-11T15:07:19.468216Z" + }, + { + "UUID": "10000000-0000-4000-8000-000000000010", + "__t": "CNAOrg", + "long_name": "Alexander, Benson and Hicks", + "short_name": "admit_9", + "aliases": [], + "authority": [ + "CNA" + ], + "top_level_root": "false", + "users": [ + "20000000-0000-4000-8000-000000000020", + "20000000-0000-4000-8000-000000000021" + ], + "admins": [ + "20000000-0000-4000-8000-000000000021" + ], + "charter_or_scope": null, + "disclosure_policy": "", + "product_list": null, + "id_quota": 1043, + "private_contacts": [], + "contact_info": { + "emails": [], + "websites": [], + "phone": null }, - { - "authority": { - "active_roles": [ - "CNA" - ] - }, - "name": "Dawson PLC", - "short_name": "beat_10", - "time": { - "modified": "2021-05-11T15:07:19.468216Z", - "created": "2021-05-11T15:07:19.468216Z" - }, - "policies": { - "id_quota": 835 - } + "in_use": false, + "created": "2021-05-11T15:07:19.468216Z", + "last_updated": "2021-05-11T15:07:19.468216Z" + }, + { + "UUID": "10000000-0000-4000-8000-000000000011", + "__t": "CNAOrg", + "long_name": "Dawson PLC", + "short_name": "beat_10", + "aliases": [], + "authority": [ + "CNA" + ], + "top_level_root": "false", + "users": [ + "20000000-0000-4000-8000-000000000022", + "20000000-0000-4000-8000-000000000023" + ], + "admins": [ + "20000000-0000-4000-8000-000000000023" + ], + "charter_or_scope": null, + "disclosure_policy": "", + "product_list": null, + "id_quota": 835, + "private_contacts": [], + "contact_info": { + "emails": [], + "websites": [], + "phone": null }, - { - "authority": { - "active_roles": [ - "CNA" - ] - }, - "name": "Alexander, Lewis and Baker", - "short_name": "tax_11", - "time": { - "modified": "2021-05-11T15:07:19.468216Z", - "created": "2021-05-11T15:07:19.468216Z" - }, - "policies": { - "id_quota": 814 - } + "in_use": false, + "created": "2021-05-11T15:07:19.468216Z", + "last_updated": "2021-05-11T15:07:19.468216Z" + }, + { + "UUID": "10000000-0000-4000-8000-000000000012", + "__t": "CNAOrg", + "long_name": "Alexander, Lewis and Baker", + "short_name": "tax_11", + "aliases": [], + "authority": [ + "CNA" + ], + "top_level_root": "false", + "users": [ + "20000000-0000-4000-8000-000000000024", + "20000000-0000-4000-8000-000000000025" + ], + "admins": [ + "20000000-0000-4000-8000-000000000025" + ], + "charter_or_scope": null, + "disclosure_policy": "", + "product_list": null, + "id_quota": 814, + "private_contacts": [], + "contact_info": { + "emails": [], + "websites": [], + "phone": null }, - { - "authority": { - "active_roles": [ - "CNA" - ] - }, - "name": "Smith, Guerrero and Chen", - "short_name": "face_12", - "time": { - "modified": "2021-05-11T15:07:19.468216Z", - "created": "2021-05-11T15:07:19.468216Z" - }, - "policies": { - "id_quota": 732 - } + "in_use": false, + "created": "2021-05-11T15:07:19.468216Z", + "last_updated": "2021-05-11T15:07:19.468216Z" + }, + { + "UUID": "10000000-0000-4000-8000-000000000013", + "__t": "CNAOrg", + "long_name": "Smith, Guerrero and Chen", + "short_name": "face_12", + "aliases": [], + "authority": [ + "CNA" + ], + "top_level_root": "false", + "users": [ + "20000000-0000-4000-8000-000000000026", + "20000000-0000-4000-8000-000000000027" + ], + "admins": [ + "20000000-0000-4000-8000-000000000027" + ], + "charter_or_scope": null, + "disclosure_policy": "", + "product_list": null, + "id_quota": 732, + "private_contacts": [], + "contact_info": { + "emails": [], + "websites": [], + "phone": null }, - { - "authority": { - "active_roles": [ - "CNA" - ] - }, - "name": "Davis, Carr and Castaneda", - "short_name": "deal_13", - "time": { - "modified": "2021-05-11T15:07:19.468216Z", - "created": "2021-05-11T15:07:19.468216Z" - }, - "policies": { - "id_quota": 759 - } + "in_use": false, + "created": "2021-05-11T15:07:19.468216Z", + "last_updated": "2021-05-11T15:07:19.468216Z" + }, + { + "UUID": "10000000-0000-4000-8000-000000000014", + "__t": "CNAOrg", + "long_name": "Davis, Carr and Castaneda", + "short_name": "deal_13", + "aliases": [], + "authority": [ + "CNA" + ], + "top_level_root": "false", + "users": [ + "20000000-0000-4000-8000-000000000028", + "20000000-0000-4000-8000-000000000029" + ], + "admins": [ + "20000000-0000-4000-8000-000000000029" + ], + "charter_or_scope": null, + "disclosure_policy": "", + "product_list": null, + "id_quota": 759, + "private_contacts": [], + "contact_info": { + "emails": [], + "websites": [], + "phone": null }, - { - "authority": { - "active_roles": [ - "CNA" - ] - }, - "name": "Adams, White and Graham", - "short_name": "environmental_14", - "time": { - "modified": "2021-05-11T15:07:19.468216Z", - "created": "2021-05-11T15:07:19.468216Z" - }, - "policies": { - "id_quota": 1464 - } + "in_use": false, + "created": "2021-05-11T15:07:19.468216Z", + "last_updated": "2021-05-11T15:07:19.468216Z" + }, + { + "UUID": "10000000-0000-4000-8000-000000000015", + "__t": "CNAOrg", + "long_name": "Adams, White and Graham", + "short_name": "environmental_14", + "aliases": [], + "authority": [ + "CNA" + ], + "top_level_root": "false", + "users": [ + "20000000-0000-4000-8000-000000000030", + "20000000-0000-4000-8000-000000000031" + ], + "admins": [ + "20000000-0000-4000-8000-000000000031" + ], + "charter_or_scope": null, + "disclosure_policy": "", + "product_list": null, + "id_quota": 1464, + "private_contacts": [], + "contact_info": { + "emails": [], + "websites": [], + "phone": null }, - { - "authority": { - "active_roles": [ - "CNA" - ] - }, - "name": "Moon-Parrish", - "short_name": "evidence_15", - "time": { - "modified": "2021-05-11T15:07:19.468216Z", - "created": "2021-05-11T15:07:19.468216Z" - }, - "policies": { - "id_quota": 942 - } + "in_use": false, + "created": "2021-05-11T15:07:19.468216Z", + "last_updated": "2021-05-11T15:07:19.468216Z" + }, + { + "UUID": "10000000-0000-4000-8000-000000000016", + "__t": "CNAOrg", + "long_name": "Moon-Parrish", + "short_name": "evidence_15", + "aliases": [], + "authority": [ + "CNA" + ], + "top_level_root": "false", + "users": [ + "20000000-0000-4000-8000-000000000032", + "20000000-0000-4000-8000-000000000033" + ], + "admins": [ + "20000000-0000-4000-8000-000000000033" + ], + "charter_or_scope": null, + "disclosure_policy": "", + "product_list": null, + "id_quota": 942, + "private_contacts": [], + "contact_info": { + "emails": [], + "websites": [], + "phone": null }, - { - "authority": { - "active_roles": [ - "CNA" - ] - }, - "name": "Potter Ltd", - "short_name": "response_16", - "time": { - "modified": "2021-05-11T15:07:19.468216Z", - "created": "2021-05-11T15:07:19.468216Z" - }, - "policies": { - "id_quota": 826 - } + "in_use": false, + "created": "2021-05-11T15:07:19.468216Z", + "last_updated": "2021-05-11T15:07:19.468216Z" + }, + { + "UUID": "10000000-0000-4000-8000-000000000017", + "__t": "CNAOrg", + "long_name": "Potter Ltd", + "short_name": "response_16", + "aliases": [], + "authority": [ + "CNA" + ], + "top_level_root": "false", + "users": [ + "20000000-0000-4000-8000-000000000034", + "20000000-0000-4000-8000-000000000035" + ], + "admins": [ + "20000000-0000-4000-8000-000000000035" + ], + "charter_or_scope": null, + "disclosure_policy": "", + "product_list": null, + "id_quota": 826, + "private_contacts": [], + "contact_info": { + "emails": [], + "websites": [], + "phone": null }, - { - "authority": { - "active_roles": [ - "CNA" - ] - }, - "name": "Webb, Cook and Wilson", - "short_name": "if_17", - "time": { - "modified": "2021-05-11T15:07:19.468216Z", - "created": "2021-05-11T15:07:19.468216Z" - }, - "policies": { - "id_quota": 952 - } + "in_use": false, + "created": "2021-05-11T15:07:19.468216Z", + "last_updated": "2021-05-11T15:07:19.468216Z" + }, + { + "UUID": "10000000-0000-4000-8000-000000000018", + "__t": "CNAOrg", + "long_name": "Webb, Cook and Wilson", + "short_name": "if_17", + "aliases": [], + "authority": [ + "CNA" + ], + "top_level_root": "false", + "users": [ + "20000000-0000-4000-8000-000000000036", + "20000000-0000-4000-8000-000000000037" + ], + "admins": [ + "20000000-0000-4000-8000-000000000037" + ], + "charter_or_scope": null, + "disclosure_policy": "", + "product_list": null, + "id_quota": 952, + "private_contacts": [], + "contact_info": { + "emails": [], + "websites": [], + "phone": null }, - { - "authority": { - "active_roles": [ - "CNA" - ] - }, - "name": "Montoya, Smith and Jones", - "short_name": "most_18", - "time": { - "modified": "2021-05-11T15:07:19.468216Z", - "created": "2021-05-11T15:07:19.468216Z" - }, - "policies": { - "id_quota": 1358 - } + "in_use": false, + "created": "2021-05-11T15:07:19.468216Z", + "last_updated": "2021-05-11T15:07:19.468216Z" + }, + { + "UUID": "10000000-0000-4000-8000-000000000019", + "__t": "CNAOrg", + "long_name": "Montoya, Smith and Jones", + "short_name": "most_18", + "aliases": [], + "authority": [ + "CNA" + ], + "top_level_root": "false", + "users": [ + "20000000-0000-4000-8000-000000000038", + "20000000-0000-4000-8000-000000000039" + ], + "admins": [ + "20000000-0000-4000-8000-000000000039" + ], + "charter_or_scope": null, + "disclosure_policy": "", + "product_list": null, + "id_quota": 1358, + "private_contacts": [], + "contact_info": { + "emails": [], + "websites": [], + "phone": null }, - { - "authority": { - "active_roles": [ - "CNA" - ] - }, - "name": "Young Inc", - "short_name": "interesting_19", - "time": { - "modified": "2021-05-11T15:07:19.468216Z", - "created": "2021-05-11T15:07:19.468216Z" - }, - "policies": { - "id_quota": 896 - } + "in_use": false, + "created": "2021-05-11T15:07:19.468216Z", + "last_updated": "2021-05-11T15:07:19.468216Z" + }, + { + "UUID": "10000000-0000-4000-8000-000000000020", + "__t": "CNAOrg", + "long_name": "Young Inc", + "short_name": "interesting_19", + "aliases": [], + "authority": [ + "CNA" + ], + "top_level_root": "false", + "users": [ + "20000000-0000-4000-8000-000000000040", + "20000000-0000-4000-8000-000000000041" + ], + "admins": [ + "20000000-0000-4000-8000-000000000041" + ], + "charter_or_scope": null, + "disclosure_policy": "", + "product_list": null, + "id_quota": 896, + "private_contacts": [], + "contact_info": { + "emails": [], + "websites": [], + "phone": null }, - { - "authority": { - "active_roles": [ - "CNA" - ] - }, - "name": "Chase, May and Jones", - "short_name": "sister_20", - "time": { - "modified": "2021-05-11T15:07:19.468216Z", - "created": "2021-05-11T15:07:19.468216Z" - }, - "policies": { - "id_quota": 1408 - } - } -] \ No newline at end of file + "in_use": false, + "created": "2021-05-11T15:07:19.468216Z", + "last_updated": "2021-05-11T15:07:19.468216Z" + }, + { + "UUID": "10000000-0000-4000-8000-000000000021", + "__t": "CNAOrg", + "long_name": "Chase, May and Jones", + "short_name": "sister_20", + "aliases": [], + "authority": [ + "CNA" + ], + "top_level_root": "false", + "users": [ + "20000000-0000-4000-8000-000000000042", + "20000000-0000-4000-8000-000000000043" + ], + "admins": [ + "20000000-0000-4000-8000-000000000043" + ], + "charter_or_scope": null, + "disclosure_policy": "", + "product_list": null, + "id_quota": 1408, + "private_contacts": [], + "contact_info": { + "emails": [], + "websites": [], + "phone": null + }, + "in_use": false, + "created": "2021-05-11T15:07:19.468216Z", + "last_updated": "2021-05-11T15:07:19.468216Z" + } +] diff --git a/datadump/pre-population/users.json b/datadump/pre-population/users.json index 588f13071..076179768 100644 --- a/datadump/pre-population/users.json +++ b/datadump/pre-population/users.json @@ -1,1017 +1,795 @@ [ - { - "username": "cps@mitre.org", - "cna_short_name": "mitre", - "active": true, - "name": { - "first": "Jeremy", - "last": "Williams", - "middle": "C", - "suffix": "III" - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - } - }, - { - "username": "admin2@mitre.org", - "cna_short_name": "mitre", - "active": true, - "name": { - "first": "Sheila", - "last": "Thompson", - "middle": "O", - "suffix": "Jr." - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - } - }, - { - "username": "sandrajenkins@mitre.com", - "cna_short_name": "mitre", - "active": true, - "name": { - "first": "Sandra", - "last": "Jenkins", - "middle": "E", - "suffix": "PhD" - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - } - }, - { - "username": "ashleyruiz@window_1.com", - "cna_short_name": "window_1", - "active": true, - "name": { - "first": "Ashley", - "last": "Ruiz", - "middle": "O", - "suffix": "III" - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - } - }, - { - "username": "window_1_admin@window_1.com", - "cna_short_name": "window_1", - "active": true, - "name": { - "first": "Ashley", - "last": "Ruiz", - "middle": "O", - "suffix": "III" - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - }, - "authority": { - "active_roles": [ - "ADMIN" - ] - } - }, - { - "username": "lisasparks@church_2.com", - "cna_short_name": "church_2", - "active": true, - "name": { - "first": "Lisa", - "last": "Sparks", - "middle": "R", - "suffix": "II" - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - } - }, - { - "username": "church_2_admin@church_2.com", - "cna_short_name": "church_2", - "active": true, - "name": { - "first": "Lisa", - "last": "Sparks", - "middle": "R", - "suffix": "II" - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - }, - "authority": { - "active_roles": [ - "ADMIN" - ] - } - }, - { - "username": "stephanieoconnor@officer_3.com", - "cna_short_name": "officer_3", - "active": true, - "name": { - "first": "Stephanie", - "last": "Oconnor", - "middle": "D", - "suffix": "I" - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - } - }, - { - "username": "officer_3_admin@officer_3.com", - "cna_short_name": "officer_3", - "active": true, - "name": { - "first": "Stephanie", - "last": "Oconnor", - "middle": "D", - "suffix": "I" - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - }, - "authority": { - "active_roles": [ - "ADMIN" - ] - } - }, - { - "username": "scottmitchell@range_4.com", - "cna_short_name": "range_4", - "active": true, - "name": { - "first": "Scott", - "last": "Mitchell", - "middle": "F", - "suffix": "PhD" - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - } - }, - { - "username": "range_4_admin@range_4.com", - "cna_short_name": "range_4", - "active": true, - "name": { - "first": "Scott", - "last": "Mitchell", - "middle": "F", - "suffix": "PhD" - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - }, - "authority": { - "active_roles": [ - "ADMIN" - ] - } - }, - { - "username": "jasminesmith@win_5.com", - "cna_short_name": "win_5", - "active": true, - "name": { - "first": "Jasmine", - "last": "Smith", - "middle": "M", - "suffix": "" - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - } - }, - { - "username": "win_5_admin@win_5.com", - "cna_short_name": "win_5", - "active": true, - "name": { - "first": "Jasmine", - "last": "Smith", - "middle": "M", - "suffix": "" - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - }, - "authority": { - "active_roles": [ - "ADMIN" - ] - } - }, - { - "username": "benjaminsimmons@activity_6.com", - "cna_short_name": "activity_6", - "active": true, - "name": { - "first": "Benjamin", - "last": "Simmons", - "middle": "S", - "suffix": "Sr." - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - } - }, - { - "username": "activity_6_admin@activity_6.com", - "cna_short_name": "activity_6", - "active": true, - "name": { - "first": "Benjamin", - "last": "Simmons", - "middle": "S", - "suffix": "Sr." - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - }, - "authority": { - "active_roles": [ - "ADMIN" - ] - } - }, - { - "username": "lorraineatkinson@seem_7.com", - "cna_short_name": "seem_7", - "active": true, - "name": { - "first": "Lorraine", - "last": "Atkinson", - "middle": "K", - "suffix": "I" - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - } - }, - { - "username": "seem_7_admin@seem_7.com", - "cna_short_name": "seem_7", - "active": true, - "name": { - "first": "Lorraine", - "last": "Atkinson", - "middle": "K", - "suffix": "I" - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - }, - "authority": { - "active_roles": [ - "ADMIN" - ] - } - }, - { - "username": "shanebrown@cause_8.com", - "cna_short_name": "cause_8", - "active": true, - "name": { - "first": "Shane", - "last": "Brown", - "middle": "D", - "suffix": "II" - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - } - }, - { - "username": "cause_8_admin@cause_8.com", - "cna_short_name": "cause_8", - "active": true, - "name": { - "first": "Shane", - "last": "Brown", - "middle": "D", - "suffix": "II" - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - }, - "authority": { - "active_roles": [ - "ADMIN" - ] - } - }, - { - "username": "loribrown@admit_9.com", - "cna_short_name": "admit_9", - "active": true, - "name": { - "first": "Lori", - "last": "Brown", - "middle": "D", - "suffix": "I" - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - } - }, - { - "username": "admit_9_admin@admit_9.com", - "cna_short_name": "admit_9", - "active": true, - "name": { - "first": "Lori", - "last": "Brown", - "middle": "D", - "suffix": "I" - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - }, - "authority": { - "active_roles": [ - "ADMIN" - ] - } - }, - { - "username": "patriciawilliams@beat_10.com", - "cna_short_name": "beat_10", - "active": true, - "name": { - "first": "Patricia", - "last": "Williams", - "middle": "W", - "suffix": "Jr." - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - } - }, - { - "username": "beat_10_admin@beat_10.com", - "cna_short_name": "beat_10", - "active": true, - "name": { - "first": "Patricia", - "last": "Williams", - "middle": "W", - "suffix": "Jr." - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - }, - "authority": { - "active_roles": [ - "ADMIN" - ] - } - }, - { - "username": "christophermoore@tax_11.com", - "cna_short_name": "tax_11", - "active": true, - "name": { - "first": "Christopher", - "last": "Moore", - "middle": "R", - "suffix": "Sr." - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - } - }, - { - "username": "tax_11_admin@tax_11.com", - "cna_short_name": "tax_11", - "active": true, - "name": { - "first": "Christopher", - "last": "Moore", - "middle": "R", - "suffix": "Sr." - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - }, - "authority": { - "active_roles": [ - "ADMIN" - ] - } - }, - { - "username": "carlcisneros@face_12.com", - "cna_short_name": "face_12", - "active": true, - "name": { - "first": "Carl", - "last": "Cisneros", - "middle": "A", - "suffix": "I" - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - } - }, - { - "username": "face_12_admin@face_12.com", - "cna_short_name": "face_12", - "active": true, - "name": { - "first": "Carl", - "last": "Cisneros", - "middle": "A", - "suffix": "I" - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - }, - "authority": { - "active_roles": [ - "ADMIN" - ] - } - }, - { - "username": "jamiejones@deal_13.com", - "cna_short_name": "deal_13", - "active": true, - "name": { - "first": "Jamie", - "last": "Jones", - "middle": "E", - "suffix": "" - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - } - }, - { - "username": "deal_13_admin@deal_13.com", - "cna_short_name": "deal_13", - "active": true, - "name": { - "first": "Jamie", - "last": "Jones", - "middle": "E", - "suffix": "" - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - }, - "authority": { - "active_roles": [ - "ADMIN" - ] - } - }, - { - "username": "johnsandoval@environmental_14.com", - "cna_short_name": "environmental_14", - "active": true, - "name": { - "first": "John", - "last": "Sandoval", - "middle": "C", - "suffix": "Sr." - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - } - }, - { - "username": "environmental_14_admin@environmental_14.com", - "cna_short_name": "environmental_14", - "active": true, - "name": { - "first": "John", - "last": "Sandoval", - "middle": "C", - "suffix": "Sr." - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - }, - "authority": { - "active_roles": [ - "ADMIN" - ] - } - }, - { - "username": "timothymyers@evidence_15.com", - "cna_short_name": "evidence_15", - "active": true, - "name": { - "first": "Timothy", - "last": "Myers", - "middle": "S", - "suffix": "" - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - } - }, - { - "username": "evidence_15_admin@evidence_15.com", - "cna_short_name": "evidence_15", - "active": true, - "name": { - "first": "Timothy", - "last": "Myers", - "middle": "S", - "suffix": "" - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - }, - "authority": { - "active_roles": [ - "ADMIN" - ] - } - }, - { - "username": "thomaswilliams@response_16.com", - "cna_short_name": "response_16", - "active": true, - "name": { - "first": "Thomas", - "last": "Williams", - "middle": "E", - "suffix": "II" - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - } - }, - { - "username": "response_16_admin@response_16.com", - "cna_short_name": "response_16", - "active": true, - "name": { - "first": "Thomas", - "last": "Williams", - "middle": "E", - "suffix": "II" - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - }, - "authority": { - "active_roles": [ - "ADMIN" - ] - } - }, - { - "username": "diamondnorman@if_17.com", - "cna_short_name": "if_17", - "active": true, - "name": { - "first": "Diamond", - "last": "Norman", - "middle": "T", - "suffix": "Jr." - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - } - }, - { - "username": "if_17_admin@if_17.com", - "cna_short_name": "if_17", - "active": true, - "name": { - "first": "Diamond", - "last": "Norman", - "middle": "T", - "suffix": "Jr." - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - }, - "authority": { - "active_roles": [ - "ADMIN" - ] - } - }, - { - "username": "johnsummers@most_18.com", - "cna_short_name": "most_18", - "active": true, - "name": { - "first": "John", - "last": "Summers", - "middle": "O", - "suffix": "III" - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - } - }, - { - "username": "most_18_admin@most_18.com", - "cna_short_name": "most_18", - "active": true, - "name": { - "first": "John", - "last": "Summers", - "middle": "O", - "suffix": "III" - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - }, - "authority": { - "active_roles": [ - "ADMIN" - ] - } - }, - { - "username": "heatherolson@interesting_19.com", - "cna_short_name": "interesting_19", - "active": true, - "name": { - "first": "Heather", - "last": "Olson", - "middle": "A", - "suffix": "" - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - } - }, - { - "username": "interesting_19_admin@interesting_19.com", - "cna_short_name": "interesting_19", - "active": true, - "name": { - "first": "Heather", - "last": "Olson", - "middle": "A", - "suffix": "" - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - }, - "authority": { - "active_roles": [ - "ADMIN" - ] - } - }, - { - "username": "andreasparks@sister_20.com", - "cna_short_name": "sister_20", - "active": true, - "name": { - "first": "Andrea", - "last": "Sparks", - "middle": "N", - "suffix": "I" - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - } - }, - { - "username": "sister_20_admin@sister_20.com", - "cna_short_name": "sister_20", - "active": true, - "name": { - "first": "Andrea", - "last": "Sparks", - "middle": "N", - "suffix": "I" - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - }, - "authority": { - "active_roles": [ - "ADMIN" - ] - } - }, - { - "username": "cps2@mitre.org", - "cna_short_name": "mitre", - "active": true, - "name": { - "first": "", - "last": "", - "middle": "W", - "suffix": "Sr." - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - } - }, - { - "username": "test_secretariat_0@mitre.org", - "cna_short_name": "mitre", - "active": true, - "name": { - "first": "", - "last": "", - "middle": "T", - "suffix": "III" - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - } - }, - { - "username": "test_secretariat_1@mitre.org", - "cna_short_name": "mitre", - "active": true, - "name": { - "first": "", - "last": "", - "middle": "R", - "suffix": "III" - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - } - }, - { - "username": "test_secretariat_2@mitre.org", - "cna_short_name": "mitre", - "active": true, - "name": { - "first": "", - "last": "", - "middle": "N", - "suffix": "Jr." - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - } - }, - { - "username": "test_secretariat_3@mitre.org", - "cna_short_name": "mitre", - "active": true, - "name": { - "first": "", - "last": "", - "middle": "F", - "suffix": "" - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - } - }, - { - "username": "test_secretariat_4@mitre.org", - "cna_short_name": "mitre", - "active": true, - "name": { - "first": "", - "last": "", - "middle": "S", - "suffix": "PhD" - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - } - }, - { - "username": "test_secretariat_5@mitre.org", - "cna_short_name": "mitre", - "active": true, - "name": { - "first": "", - "last": "", - "middle": "S", - "suffix": "Jr." - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - } - }, - { - "username": "test_secretariat_6@mitre.org", - "cna_short_name": "mitre", - "active": true, - "name": { - "first": "", - "last": "", - "middle": "F", - "suffix": "II" - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - } - }, - { - "username": "test_secretariat_7@mitre.org", - "cna_short_name": "mitre", - "active": true, - "name": { - "first": "", - "last": "", - "middle": "N", - "suffix": "Sr." - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - } - }, - { - "username": "test_secretariat_8@mitre.org", - "cna_short_name": "mitre", - "active": true, - "name": { - "first": "", - "last": "", - "middle": "Q", - "suffix": "Sr." - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - } - }, - { - "username": "test_secretariat_9@mitre.org", - "cna_short_name": "mitre", - "active": true, - "name": { - "first": "", - "last": "", - "middle": "P", - "suffix": "II" - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - } - }, - { - "username": "test_secretariat_10@mitre.org", - "cna_short_name": "mitre", - "active": true, - "name": { - "first": "", - "last": "", - "middle": "P", - "suffix": "" - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - } - }, - { - "username": "test_secretariat_11@mitre.org", - "cna_short_name": "mitre", - "active": true, - "name": { - "first": "", - "last": "", - "middle": "T", - "suffix": "I" - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - } - }, - { - "username": "test_secretariat_12@mitre.org", - "cna_short_name": "mitre", - "active": true, - "name": { - "first": "", - "last": "", - "middle": "T", - "suffix": "I" - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - } - }, - { - "username": "test_secretariat_13@mitre.org", - "cna_short_name": "mitre", - "active": true, - "name": { - "first": "", - "last": "", - "middle": "B", - "suffix": "Sr." - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - } - }, - { - "username": "test_secretariat_14@mitre.org", - "cna_short_name": "mitre", - "active": true, - "name": { - "first": "", - "last": "", - "middle": "S", - "suffix": "Sr." - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - } - }, - { - "username": "test_secretariat_15@mitre.org", - "cna_short_name": "mitre", - "active": true, - "name": { - "first": "", - "last": "", - "middle": "P", - "suffix": "II" - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - } - }, - { - "username": "test_secretariat_16@mitre.org", - "cna_short_name": "mitre", - "active": true, - "name": { - "first": "", - "last": "", - "middle": "Q", - "suffix": "" - }, - "time": { - "modified": "2021-05-11T15:07:20.674347Z", - "created": "2021-05-11T15:07:20.674347Z" - } - } + { + "UUID": "20000000-0000-4000-8000-000000000001", + "username": "cps@mitre.org", + "name": { + "first": "Jeremy", + "last": "Williams", + "middle": "C", + "suffix": "III" + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000002", + "username": "admin2@mitre.org", + "name": { + "first": "Sheila", + "last": "Thompson", + "middle": "O", + "suffix": "Jr." + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000003", + "username": "sandrajenkins@mitre.com", + "name": { + "first": "Sandra", + "last": "Jenkins", + "middle": "E", + "suffix": "PhD" + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000004", + "username": "ashleyruiz@window_1.com", + "name": { + "first": "Ashley", + "last": "Ruiz", + "middle": "O", + "suffix": "III" + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000005", + "username": "window_1_admin@window_1.com", + "name": { + "first": "Ashley", + "last": "Ruiz", + "middle": "O", + "suffix": "III" + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000006", + "username": "lisasparks@church_2.com", + "name": { + "first": "Lisa", + "last": "Sparks", + "middle": "R", + "suffix": "II" + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000007", + "username": "church_2_admin@church_2.com", + "name": { + "first": "Lisa", + "last": "Sparks", + "middle": "R", + "suffix": "II" + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000008", + "username": "stephanieoconnor@officer_3.com", + "name": { + "first": "Stephanie", + "last": "Oconnor", + "middle": "D", + "suffix": "I" + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000009", + "username": "officer_3_admin@officer_3.com", + "name": { + "first": "Stephanie", + "last": "Oconnor", + "middle": "D", + "suffix": "I" + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000010", + "username": "scottmitchell@range_4.com", + "name": { + "first": "Scott", + "last": "Mitchell", + "middle": "F", + "suffix": "PhD" + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000011", + "username": "range_4_admin@range_4.com", + "name": { + "first": "Scott", + "last": "Mitchell", + "middle": "F", + "suffix": "PhD" + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000012", + "username": "jasminesmith@win_5.com", + "name": { + "first": "Jasmine", + "last": "Smith", + "middle": "M", + "suffix": "" + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000013", + "username": "win_5_admin@win_5.com", + "name": { + "first": "Jasmine", + "last": "Smith", + "middle": "M", + "suffix": "" + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000014", + "username": "benjaminsimmons@activity_6.com", + "name": { + "first": "Benjamin", + "last": "Simmons", + "middle": "S", + "suffix": "Sr." + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000015", + "username": "activity_6_admin@activity_6.com", + "name": { + "first": "Benjamin", + "last": "Simmons", + "middle": "S", + "suffix": "Sr." + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000016", + "username": "lorraineatkinson@seem_7.com", + "name": { + "first": "Lorraine", + "last": "Atkinson", + "middle": "K", + "suffix": "I" + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000017", + "username": "seem_7_admin@seem_7.com", + "name": { + "first": "Lorraine", + "last": "Atkinson", + "middle": "K", + "suffix": "I" + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000018", + "username": "shanebrown@cause_8.com", + "name": { + "first": "Shane", + "last": "Brown", + "middle": "D", + "suffix": "II" + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000019", + "username": "cause_8_admin@cause_8.com", + "name": { + "first": "Shane", + "last": "Brown", + "middle": "D", + "suffix": "II" + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000020", + "username": "loribrown@admit_9.com", + "name": { + "first": "Lori", + "last": "Brown", + "middle": "D", + "suffix": "I" + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000021", + "username": "admit_9_admin@admit_9.com", + "name": { + "first": "Lori", + "last": "Brown", + "middle": "D", + "suffix": "I" + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000022", + "username": "patriciawilliams@beat_10.com", + "name": { + "first": "Patricia", + "last": "Williams", + "middle": "W", + "suffix": "Jr." + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000023", + "username": "beat_10_admin@beat_10.com", + "name": { + "first": "Patricia", + "last": "Williams", + "middle": "W", + "suffix": "Jr." + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000024", + "username": "christophermoore@tax_11.com", + "name": { + "first": "Christopher", + "last": "Moore", + "middle": "R", + "suffix": "Sr." + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000025", + "username": "tax_11_admin@tax_11.com", + "name": { + "first": "Christopher", + "last": "Moore", + "middle": "R", + "suffix": "Sr." + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000026", + "username": "carlcisneros@face_12.com", + "name": { + "first": "Carl", + "last": "Cisneros", + "middle": "A", + "suffix": "I" + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000027", + "username": "face_12_admin@face_12.com", + "name": { + "first": "Carl", + "last": "Cisneros", + "middle": "A", + "suffix": "I" + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000028", + "username": "jamiejones@deal_13.com", + "name": { + "first": "Jamie", + "last": "Jones", + "middle": "E", + "suffix": "" + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000029", + "username": "deal_13_admin@deal_13.com", + "name": { + "first": "Jamie", + "last": "Jones", + "middle": "E", + "suffix": "" + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000030", + "username": "johnsandoval@environmental_14.com", + "name": { + "first": "John", + "last": "Sandoval", + "middle": "C", + "suffix": "Sr." + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000031", + "username": "environmental_14_admin@environmental_14.com", + "name": { + "first": "John", + "last": "Sandoval", + "middle": "C", + "suffix": "Sr." + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000032", + "username": "timothymyers@evidence_15.com", + "name": { + "first": "Timothy", + "last": "Myers", + "middle": "S", + "suffix": "" + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000033", + "username": "evidence_15_admin@evidence_15.com", + "name": { + "first": "Timothy", + "last": "Myers", + "middle": "S", + "suffix": "" + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000034", + "username": "thomaswilliams@response_16.com", + "name": { + "first": "Thomas", + "last": "Williams", + "middle": "E", + "suffix": "II" + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000035", + "username": "response_16_admin@response_16.com", + "name": { + "first": "Thomas", + "last": "Williams", + "middle": "E", + "suffix": "II" + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000036", + "username": "diamondnorman@if_17.com", + "name": { + "first": "Diamond", + "last": "Norman", + "middle": "T", + "suffix": "Jr." + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000037", + "username": "if_17_admin@if_17.com", + "name": { + "first": "Diamond", + "last": "Norman", + "middle": "T", + "suffix": "Jr." + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000038", + "username": "johnsummers@most_18.com", + "name": { + "first": "John", + "last": "Summers", + "middle": "O", + "suffix": "III" + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000039", + "username": "most_18_admin@most_18.com", + "name": { + "first": "John", + "last": "Summers", + "middle": "O", + "suffix": "III" + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000040", + "username": "heatherolson@interesting_19.com", + "name": { + "first": "Heather", + "last": "Olson", + "middle": "A", + "suffix": "" + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000041", + "username": "interesting_19_admin@interesting_19.com", + "name": { + "first": "Heather", + "last": "Olson", + "middle": "A", + "suffix": "" + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000042", + "username": "andreasparks@sister_20.com", + "name": { + "first": "Andrea", + "last": "Sparks", + "middle": "N", + "suffix": "I" + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000043", + "username": "sister_20_admin@sister_20.com", + "name": { + "first": "Andrea", + "last": "Sparks", + "middle": "N", + "suffix": "I" + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000044", + "username": "cps2@mitre.org", + "name": { + "first": "", + "last": "", + "middle": "W", + "suffix": "Sr." + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000045", + "username": "test_secretariat_0@mitre.org", + "name": { + "first": "", + "last": "", + "middle": "T", + "suffix": "III" + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000046", + "username": "test_secretariat_1@mitre.org", + "name": { + "first": "", + "last": "", + "middle": "R", + "suffix": "III" + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000047", + "username": "test_secretariat_2@mitre.org", + "name": { + "first": "", + "last": "", + "middle": "N", + "suffix": "Jr." + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000048", + "username": "test_secretariat_3@mitre.org", + "name": { + "first": "", + "last": "", + "middle": "F", + "suffix": "" + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000049", + "username": "test_secretariat_4@mitre.org", + "name": { + "first": "", + "last": "", + "middle": "S", + "suffix": "PhD" + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000050", + "username": "test_secretariat_5@mitre.org", + "name": { + "first": "", + "last": "", + "middle": "S", + "suffix": "Jr." + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000051", + "username": "test_secretariat_6@mitre.org", + "name": { + "first": "", + "last": "", + "middle": "F", + "suffix": "II" + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000052", + "username": "test_secretariat_7@mitre.org", + "name": { + "first": "", + "last": "", + "middle": "N", + "suffix": "Sr." + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000053", + "username": "test_secretariat_8@mitre.org", + "name": { + "first": "", + "last": "", + "middle": "Q", + "suffix": "Sr." + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000054", + "username": "test_secretariat_9@mitre.org", + "name": { + "first": "", + "last": "", + "middle": "P", + "suffix": "II" + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000055", + "username": "test_secretariat_10@mitre.org", + "name": { + "first": "", + "last": "", + "middle": "P", + "suffix": "" + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000056", + "username": "test_secretariat_11@mitre.org", + "name": { + "first": "", + "last": "", + "middle": "T", + "suffix": "I" + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000057", + "username": "test_secretariat_12@mitre.org", + "name": { + "first": "", + "last": "", + "middle": "T", + "suffix": "I" + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000058", + "username": "test_secretariat_13@mitre.org", + "name": { + "first": "", + "last": "", + "middle": "B", + "suffix": "Sr." + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000059", + "username": "test_secretariat_14@mitre.org", + "name": { + "first": "", + "last": "", + "middle": "S", + "suffix": "Sr." + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000060", + "username": "test_secretariat_15@mitre.org", + "name": { + "first": "", + "last": "", + "middle": "P", + "suffix": "II" + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + }, + { + "UUID": "20000000-0000-4000-8000-000000000061", + "username": "test_secretariat_16@mitre.org", + "name": { + "first": "", + "last": "", + "middle": "Q", + "suffix": "" + }, + "status": "active", + "created": "2021-05-11T15:07:20.674347Z", + "last_updated": "2021-05-11T15:07:20.674347Z" + } ] diff --git a/src/scripts/MondayMigrate.js b/src/scripts/MondayMigrate.js index 37286fcb0..6f7e9b4cc 100644 --- a/src/scripts/MondayMigrate.js +++ b/src/scripts/MondayMigrate.js @@ -1,9 +1,10 @@ /** * MondayMigrate.js * - * Optional migration step for applying Monday export data to already migrated BaseOrg records. + * Optional migration step for applying Monday export data to populated BaseOrg records. * - * Before running this, run the base migrate.js script and make sure export.xlsx is available in src/scripts. + * Run this against a database that already contains BaseOrg and BaseUser records, and make sure + * export.xlsx is available in src/scripts. */ require('dotenv').config() diff --git a/src/scripts/migrate.js b/src/scripts/migrate.js deleted file mode 100644 index 1bc3d8eb9..000000000 --- a/src/scripts/migrate.js +++ /dev/null @@ -1,271 +0,0 @@ -/** - * migrate.js - * - * This script is intended to be used to migrate existing user and org data into the UserRegistry. - * This will need to be modified if intended to run against databases that did not originally have the same data. - * - * Before running this, the remote database ports must be forwarded to local ports. - */ - -require('dotenv').config() -const { v4: uuidv4 } = require('uuid') -const fs = require('fs') -const path = require('path') -const { MongoClient } = require('mongodb') -const _ = require('lodash') -const dbConnStr = process.env.MONGO_CONN_STRING - -const rawData = fs.readFileSync(path.join(__dirname, 'CNAlist.json')) - -const cnaList = JSON.parse(rawData) -const cveBoardUUID = uuidv4() -let allUsers -let allOrgs -let mitreUUID - -function deepRemoveEmpty (obj) { - if (_.isArray(obj)) { - return obj - .map(v => deepRemoveEmpty(v)) - .filter(v => { - return !(v === null || (_.isArray(v) && v.length === 0) || (_.isPlainObject(v) && _.isEmpty(v))) - }) - } else if (_.isPlainObject(obj)) { - return _.transform(obj, (result, value, key) => { - const cleaned = deepRemoveEmpty(value) - if (!(cleaned === null || (_.isArray(cleaned) && cleaned.length === 0) || (_.isPlainObject(cleaned) && _.isEmpty(cleaned)))) { - result[key] = cleaned - } - }) - } - return obj -} - -async function run () { - const dbClient = new MongoClient(dbConnStr) - try { - // Initialize connection to both DocDBs - await dbClient.connect() - - // Get connection to specific DB within each DocDB - const db = await dbClient.db(process.env.MONGO_DB_NAME) - - // Get all users - const usersCursor = db.collection('User').find() - allUsers = await usersCursor.toArray() - - // Add the CVE Board as an org - // await addCVEBoard(db) - - // Get UUIDs for MITRE and the CVE Board - const orgsCursor = db.collection('Org').find() - allOrgs = await orgsCursor.toArray() - mitreUUID = allOrgs.filter(org => org.short_name === 'mitre')[0].UUID - - // Each helper handlers querying changes from srcDB and updating trgDB - await orgHelper(db) - await userHelper(db) - await glossaryHelper(db) - } catch (err) { - // Ensures that the client will close when you finish/error - await dbClient.close() - - console.error(err) - } finally { - // Ensures that the client will close when you finish/error - await dbClient.close() - } -} - -run() - -// eslint-disable-next-line no-unused-vars -async function addCVEBoard (db) { - console.log('Adding CVE Board...') - const trgOrgCol = await db.collection('BaseOrg') - - // Upsert will create new org record if one doesn't exist - const options = { upsert: true } - - const trgQuery = { - short_name: 'cve_board' - } - - // Doc to update existing org record, or to be created - const updateDoc = { - $set: { - UUID: cveBoardUUID, - long_name: 'CVE Board', - short_name: 'cve_board', - aliases: [], - authority: null, - reports_to: null, - oversees: [mitreUUID], - top_level_root: 'true', - users: null, - charter_or_scope: null, - disclosure_policy: null, - product_list: null, - id_quota: null, - private_contacts: [], - contact_info: { - phone: null, - emails: [], - websites: [] - }, - inUse: null, - created: null, - last_updated: null - } - } - const test = deepRemoveEmpty(updateDoc) - console.log(test) - await trgOrgCol.updateOne(trgQuery, test, options) -} - -async function orgHelper (db) { - console.log('Running Org sync...') - const trgOrgCol = await db.collection('BaseOrg') - - // Upsert will create new org record if one doesn't exist - const options = { upsert: true } - - let trgQuery - let updateDoc - - for (const doc of allOrgs) { - // Query to match cve-id in target DB - trgQuery = { - short_name: doc.short_name - } - - // Find associated information within the CNA List - let currentCNA - - for (const cna of cnaList) { - if (doc.short_name === cna.shortName) { - currentCNA = cna - } - } - - // Find associated users with the Org - const orgUsers = [] - const admins = [] - - for (const user of allUsers) { - if (user.org_UUID === doc.UUID) { - orgUsers.push(user.UUID) - if (user.authority?.active_roles?.includes('ADMIN')) { - admins.push(user.UUID) - } - } - } - - // Establish hierarchy of orgs - const parent = null - const children = [] - // if (doc.short_name.toLowerCase().includes('mitre')) { - // parent = cveBoardUUID - // } else { - // parent = mitreUUID - // } - - // Set top_level_root, charter_or_scope, disclosure_policy, org_email, website - let rootTlr = false - let charterScope = null - let disclosure = null - let email = null - let site = null - - if (currentCNA) { - // Pull from the CNA object - rootTlr = Object.hasOwn(currentCNA, 'CNA') ? currentCNA.CNA.isRoot : false - charterScope = Object.hasOwn(currentCNA, 'scope') ? currentCNA.scope : null - disclosure = (currentCNA.disclosurePolicy || []) - .map(policy => policy?.url) // Extract the URL (safely) - .filter(url => url) // Remove empty/null/undefined URLs - .join(';') - email = currentCNA.contact[0].email.length > 0 ? currentCNA.contact[0].email[0].emailAddr : null - site = currentCNA.contact[0].contact.length > 0 ? currentCNA.contact[0].contact[0].url : null - } - // Doc to update existing org record, or to be created - - let type = 'CNAOrg' - if (doc.short_name.toLowerCase().includes('mitre')) { type = 'SecretariatOrg' } - updateDoc = { - $set: { - UUID: doc.UUID, - __t: type, - long_name: doc.name, - short_name: doc.short_name, - aliases: [], // don't have now - authority: doc.authority.active_roles, - reports_to: parent, - oversees: children, - top_level_root: rootTlr ? 'true' : 'false', - users: orgUsers, - charter_or_scope: charterScope, - disclosure_policy: disclosure, - product_list: null, // don't have now - id_quota: doc.policies?.id_quota, - admins: admins, - private_contacts: [], // don't have now - contact_info: { - emails: email ? [email] : [], - websites: site ? [site] : [], - phone: null - }, - inUse: doc.inUse, - created: doc.time.created, - last_updated: doc.time.modified - } - } - const test = deepRemoveEmpty(updateDoc) - await trgOrgCol.updateOne(trgQuery, test, options) - } -} - -async function userHelper (db) { - console.log('Running User sync...') - const trgUserCol = await db.collection('BaseUser') - - // Upsert will create new org record if one doesn't exist - const options = { upsert: true } - - let trgQuery - let updateDoc - - for (const doc of allUsers) { - // Query to match user UUID in target DB - trgQuery = { - UUID: doc.UUID - } - - // Doc to update existing user record, or to be created - updateDoc = { - $set: { - UUID: doc.UUID, - 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, - last_active: null - } - } - - await trgUserCol.updateOne(trgQuery, updateDoc, options) - } -} - -async function glossaryHelper (db) { - console.log('Ensuring Glossary collection exists...') - // Create collection if it doesn't exist - await db.createCollection('Glossary').catch((err) => { - if (err.codeName !== 'NamespaceExists') { - console.warn('Could not create Glossary collection', err) - } - }) -} diff --git a/src/scripts/populate.js b/src/scripts/populate.js index a51d3046a..318a4cba0 100644 --- a/src/scripts/populate.js +++ b/src/scripts/populate.js @@ -14,9 +14,12 @@ const logger = require('../middleware/logger') const CveIdRange = require('../model/cve-id-range') const CveId = require('../model/cve-id') const Cve = require('../model/cve') -const Org = require('../model/org') -const User = require('../model/user') const BaseOrg = require('../model/baseorg') +const CNAOrg = require('../model/cnaorg') +const ADPOrg = require('../model/adporg') +const BulkDownloadOrg = require('../model/bulkdownloadorg') +const SecretariatOrg = require('../model/secretariatorg') +const RootOrg = require('../model/rootorg') const BaseUser = require('../model/baseuser') const ReviewObject = require('../model/reviewobject') const Conversation = require('../model/conversation') @@ -25,12 +28,25 @@ const Glossary = require('../model/glossary') const error = new errors.IDRError() +const baseOrgModels = { + CNAOrg, + ADPOrg, + BulkDownloadOrg, + SecretariatOrg, + RootOrg +} + +function getBaseOrgModel (org) { + // Insert through the discriminator so fields such as id_quota are not stripped by BaseOrg. + const model = baseOrgModels[org.__t] + if (!model) throw new Error(`Unknown BaseOrg fixture type: ${org.__t}`) + return model +} + const populateTheseCollections = { Cve: Cve, 'Cve-Id-Range': CveIdRange, 'Cve-Id': CveId, - User: User, - Org: Org, BaseOrg: BaseOrg, BaseUser: BaseUser, ReviewObject: ReviewObject, @@ -46,8 +62,8 @@ const indexesToCreate = { { 'cve.containers.cna.providerMetadata.dateUpdated': 1 } ], 'Cve-Id': [{ cve_id: 1 }, { owning_cna: 1, state: 1 }, { reserved: 1 }], - User: [{ UUID: 1 }], - Org: [{ UUID: 1 }, { 'authority.active_roles': 1 }], + BaseUser: [{ UUID: 1 }], + BaseOrg: [{ UUID: 1 }, { authority: 1 }], Glossary: [{ services_short_name: 1 }] } @@ -94,17 +110,17 @@ db.once('open', async () => { } } - // Org + // BaseOrg fixture records already contain their discriminator, membership, and admin references. await dataUtils.populateCollection( './datadump/pre-population/orgs.json', - Org, dataUtils.newOrgTransform + BaseOrg, undefined, undefined, { timestamps: false }, getBaseOrgModel ) - // User, depends on Org + // BaseUser fixture records already belong to BaseOrg through the BaseOrg membership lists. const hash = await dataUtils.preprocessUserSecrets() await dataUtils.populateCollection( './datadump/pre-population/users.json', - User, dataUtils.newUserTransform, hash + BaseUser, dataUtils.addSecretToBaseUser, hash, { timestamps: false } ) const populatePromises = [] @@ -123,7 +139,7 @@ db.once('open', async () => { )) } - // CVE ID, depends on User and Org + // CVE ID, depends on BaseUser and BaseOrg populatePromises.push(dataUtils.populateCollection( './datadump/pre-population/cve-ids.json', CveId, dataUtils.newCveIdTransform diff --git a/src/scripts/test_data/postman/README.md b/src/scripts/test_data/postman/README.md index b9613310e..fdccd0ed7 100644 --- a/src/scripts/test_data/postman/README.md +++ b/src/scripts/test_data/postman/README.md @@ -10,7 +10,7 @@ This folder contains a Postman collection and environment for exercising the reg The default environment assumes a local development database and API created with: ```sh -npm run populate:dev; npm run migrate:dev; npm run dev +npm run populate:dev; npm run dev ``` If you are targeting a different database or API host, update the imported environment values before running the collection. diff --git a/src/utils/data.js b/src/utils/data.js index 529ab4c7d..b9df3d622 100644 --- a/src/utils/data.js +++ b/src/utils/data.js @@ -11,9 +11,7 @@ const color = require('kleur') const config = require('config') const cryptoRandomString = require('crypto-random-string') const JSONStream = require('JSONStream') -const mongoose = require('mongoose') const prompt = require('prompt-sync')({ sigint: true }) -const uuid = require('uuid') const errors = require('../utils/error') const logger = require('../middleware/logger') @@ -24,12 +22,6 @@ const apiKeyFile = 'user-secret.txt' const error = new errors.IDRError() -async function newOrgTransform (org) { - org.UUID = uuid.v4() - org.inUse = false - return org -} - async function preprocessUserSecrets () { if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test') { const secretKey = process.env.LOCAL_KEY @@ -43,13 +35,15 @@ async function preprocessUserSecrets () { return hash } else { - // delete file for user secrets if one already exists - fs.unlink(apiKeyFile, err => { - if (err && err.code !== 'ENOENT') { + // Delete the previous generated-key file before creating replacement entries. + try { + await fs.promises.unlink(apiKeyFile) + } catch (err) { + if (err.code !== 'ENOENT') { logger.error(error.fileDeleteError(err)) - mongoose.connection.close() + throw err } - }) + } console.log( color.bold().black().bgWhite('The users\' API secret can be found in:') + ' ' + @@ -58,15 +52,13 @@ async function preprocessUserSecrets () { } } -async function newUserTransform (user, hash) { - const tmpOrgUUID = await utils.getOrgUUID(user.cna_short_name) - user.org_UUID = tmpOrgUUID - user.UUID = uuid.v4() +async function addSecretToBaseUser (baseUser, hash) { + const user = { ...baseUser } // shared secret key in development environments if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test') { user.secret = hash - } else if (process.env.NODE_ENV === 'integration') { + } else { const CONSTANTS = getConstants() const randomKey = cryptoRandomString({ length: CONSTANTS.CRYPTO_RANDOM_STRING_LENGTH }) user.secret = await argon2.hash(randomKey) @@ -74,25 +66,12 @@ async function newUserTransform (user, hash) { // write each user's API key to file // necessary when standing up any new shared instance of the system const payload = { username: user.username, secret: randomKey } - fs.writeFile(apiKeyFile, JSON.stringify(payload) + '\n', { flag: 'a' }, (err) => { - if (err) { - logger.error(error.fileWriteError(err)) - mongoose.connection.close() - } - }) + await fs.promises.appendFile(apiKeyFile, JSON.stringify(payload) + '\n') } return user } -async function newRegistryUserTransform (user, hash) { - // shared secret key in development environments - if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test') { - user.secret = hash - } - return user -} - async function newCveIdTransform (cveId) { const tmpRequestingCnaUUID = await utils.getOrgUUID(cveId.requested_by.cna) const tmpOwningCnaUUID = await utils.getOrgUUID(cveId.owning_cna) @@ -128,9 +107,9 @@ function getUserPopulateInput (collectionNames) { /* populates any collection given the file path for an input JSON, * the data model for the collection, a function that transforms the - * data, and a hash that only gets use in the User collection + * data, optional context for that transform, insert options, and an optional model resolver. */ -function populateCollection (filePath, dataModel, dataTransform = async function (x) { return x }, hash) { +function populateCollection (filePath, dataModel, dataTransform = async function (x) { return x }, transformContext, insertOptions = {}, resolveDataModel) { const dataName = dataModel.collection.collectionName logger.info(`Populating ${dataName} collection...`) return new Promise(function (resolve, reject) { @@ -140,8 +119,9 @@ function populateCollection (filePath, dataModel, dataTransform = async function .pipe(JSONStream.parse('*')) .on('data', async function (data) { this.pause() - data = await dataTransform(data, hash) - promises.push(dataModel.insertMany(data)) + data = await dataTransform(data, transformContext) + const targetDataModel = resolveDataModel ? resolveDataModel(data) : dataModel + promises.push(targetDataModel.insertMany(data, insertOptions)) this.resume() promisesShifter(promises) }) @@ -167,9 +147,7 @@ function promisesShifter (promises) { module.exports = { getUserPopulateInput, newCveIdTransform, - newOrgTransform, - newUserTransform, - newRegistryUserTransform, + addSecretToBaseUser, newCveTransform, populateCollection, preprocessUserSecrets diff --git a/src/utils/utils.js b/src/utils/utils.js index c1c66cc71..7dc657b02 100644 --- a/src/utils/utils.js +++ b/src/utils/utils.js @@ -1,51 +1,40 @@ -const Org = require('../model/org') -const User = require('../model/user') - const BaseOrg = require('../model/baseorg') const BaseUserRepository = require('../repositories/baseUserRepository') -const getConstants = require('../constants').getConstants const _ = require('lodash') const { DateTime } = require('luxon') -const BaseOrgRepository = require('../repositories/baseOrgRepository') -async function getOrgUUID (shortName, useRegistry = false, options = {}) { - const ModelToQuery = useRegistry ? BaseOrg : Org +function getExecutionOptions (options) { + return options && typeof options === 'object' ? options : {} +} + +function orgHasRole (org, role) { + return Array.isArray(org?.authority) && org.authority.includes(role) +} + +async function getOrgUUID (shortName, options = {}) { const query = { short_name: shortName } const projection = 'UUID' // We only need the UUID field // It's often good practice to use .lean() for read-only operations // if you don't need full Mongoose documents. - const executionOptions = { ...options } + const executionOptions = { ...getExecutionOptions(options) } if (executionOptions.lean === undefined) executionOptions.lean = true - const orgDocument = await ModelToQuery.findOne(query, projection, executionOptions) + const orgDocument = await BaseOrg.findOne(query, projection, executionOptions) return orgDocument ? orgDocument.UUID : null } -async function getUserUUID (userIdentifier, orgUUID, useRegistry = false, options = {}) { - let query - - if (!useRegistry) { - // For User, query by username and org_UUID - query = { - username: userIdentifier, // Matches the 'username' field in User schema - org_UUID: orgUUID // Matches the 'org_UUID' field in User schema - } - const projection = 'UUID' // We only need the user's UUID field - const executionOptions = { ...options } - if (executionOptions.lean === undefined) executionOptions.lean = true - - const userDocument = await User.findOne(query, projection, executionOptions) - - return userDocument ? userDocument.UUID : null - } else { - const baseUserRepository = new BaseUserRepository() - const userDocument = await baseUserRepository.findOneByUserNameAndOrgUUID(userIdentifier, orgUUID, options) - return userDocument ? userDocument.UUID : null - } +async function getUserUUID (userIdentifier, orgUUID, options = {}) { + const baseUserRepository = new BaseUserRepository() + const userDocument = await baseUserRepository.findOneByUserNameAndOrgUUID( + userIdentifier, + orgUUID, + getExecutionOptions(options) + ) + return userDocument ? userDocument.UUID : null } function getUserFullName (user) { @@ -56,105 +45,48 @@ function getUserFullName (user) { else return `${user.name.first} ${user.name.last}` } -async function isSecretariat (shortName, useRegistry = false, options = {}) { - let result = false - let orgUUID = null - let secretariats = [] - - const CONSTANTS = getConstants() - if (useRegistry) { - orgUUID = await getOrgUUID(shortName, useRegistry, options) // may be null if org does not exists - secretariats = await BaseOrg.find({ authority: { $in: [CONSTANTS.AUTH_ROLE_ENUM.SECRETARIAT] } }) - } else { - orgUUID = await getOrgUUID(shortName, false, options) // may be null if org does not exists - secretariats = await Org.find({ 'authority.active_roles': { $in: [CONSTANTS.AUTH_ROLE_ENUM.SECRETARIAT] } }) - } - - if (orgUUID) { - secretariats.forEach((obj) => { - if (obj.UUID === orgUUID) { - result = true // org is secretariat - } - }) - } - - return result +async function isSecretariat (shortName, options = {}) { + const org = await BaseOrg.findOne( + { short_name: shortName }, + 'authority', + getExecutionOptions(options) + ) + return orgHasRole(org, 'SECRETARIAT') } -async function isSecretariatUUID (orgUUID) { - let result = false - const CONSTANTS = getConstants() - const secretariats = await BaseOrg.find({ authority: { $in: [CONSTANTS.AUTH_ROLE_ENUM.SECRETARIAT] } }) - - if (orgUUID) { - secretariats.forEach((obj) => { - if (obj.UUID === orgUUID) { - result = true // org is secretariat - } - }) - } - - return result // org is not secretariat +async function isSecretariatUUID (orgUUID, options = {}) { + const org = await BaseOrg.findOne( + { UUID: orgUUID }, + 'authority', + getExecutionOptions(options) + ) + return orgHasRole(org, 'SECRETARIAT') } -async function isBulkDownload (shortName) { - let result = false - const CONSTANTS = getConstants() - const orgUUID = await getOrgUUID(shortName) // may be null if org does not exists - const bulkDownloadOrgs = await BaseOrg.find({ authority: { $in: [CONSTANTS.AUTH_ROLE_ENUM.BULK_DOWNLOAD] } }) - - if (orgUUID) { - bulkDownloadOrgs.forEach((obj) => { - if (obj.UUID === orgUUID) { - result = true // org has the bulk download role - } - }) - } - - return result // org does not have bulk download as a role +async function isBulkDownload (shortName, options = {}) { + const org = await BaseOrg.findOne( + { short_name: shortName }, + 'authority', + getExecutionOptions(options) + ) + return orgHasRole(org, 'BULK_DOWNLOAD') } -async function isAdmin (requesterUsername, requesterShortName, isRegistry = false, options = {}) { - let result = false - const CONSTANTS = getConstants() - const requesterOrgUUID = await getOrgUUID(requesterShortName, isRegistry, options) // may be null if org does not exists - +async function isAdmin (requesterUsername, requesterShortName, options = {}) { const baseUserRepository = new BaseUserRepository() - if (requesterOrgUUID) { - const user = isRegistry ? await baseUserRepository.findOneByUserNameAndOrgUUID(requesterUsername, requesterOrgUUID) : await User.findOne().byUserNameAndOrgUUID(requesterUsername, requesterOrgUUID) - - if (user) { - if (isRegistry) { - result = baseUserRepository.isAdmin(requesterUsername, requesterShortName, options) - } else { - result = user.authority.active_roles.includes(CONSTANTS.USER_ROLE_ENUM.ADMIN) - } - } - } - - return result // org is not secretariat + return baseUserRepository.isAdmin(requesterUsername, requesterShortName, getExecutionOptions(options)) } -async function isAdminUUID (requesterUsername, requesterOrgUUID, isRegistry = false, options = {}) { - let result = false - const CONSTANTS = getConstants() - +async function isAdminUUID (requesterUsername, requesterOrgUUID, options = {}) { + const executionOptions = getExecutionOptions(options) const baseUserRepository = new BaseUserRepository() - const baseOrgRepository = new BaseOrgRepository() - if (requesterOrgUUID) { - const orgObject = await baseOrgRepository.findOneByUUID(requesterOrgUUID, options) - const user = isRegistry ? await baseUserRepository.findOneByUserNameAndOrgUUID(requesterUsername, requesterOrgUUID) : await User.findOne().byUserNameAndOrgUUID(requesterUsername, requesterOrgUUID) - - if (user && orgObject) { - if (isRegistry) { - result = baseUserRepository.isAdmin(requesterUsername, orgObject.short_name, options) - } else { - result = user.authority.active_roles.includes(CONSTANTS.USER_ROLE_ENUM.ADMIN) - } - } - } - - return result // org is not secretariat + const org = await BaseOrg.findOne( + { UUID: requesterOrgUUID }, + 'short_name', + executionOptions + ) + if (!org) return false + return baseUserRepository.isAdmin(requesterUsername, org.short_name, executionOptions) } function reqCtxMapping (req, keyType, keys) { diff --git a/test/integration-tests/middleware/authenticatedContextTest.js b/test/integration-tests/middleware/authenticatedContextTest.js index ac6ad8176..bd35b596d 100644 --- a/test/integration-tests/middleware/authenticatedContextTest.js +++ b/test/integration-tests/middleware/authenticatedContextTest.js @@ -9,9 +9,7 @@ const getConstants = require('../../../src/constants').getConstants const mwFixtures = require('../../unit-tests/middleware/mockObjects.middleware') const serviceApp = require('../../../src/index') const BaseOrg = require('../../../src/model/baseorg') -const Org = require('../../../src/model/org') const BaseUser = require('../../../src/model/baseuser') -const User = require('../../../src/model/user') const CveId = require('../../../src/model/cve-id') const CONSTANTS = getConstants() @@ -171,14 +169,14 @@ describe('Authenticated request context middleware integration', () => { expect(res.body.cve_ids).to.deep.equal([]) }) - context('DB-backed duplicate short-name regression', () => { + context('DB-backed authenticated-org UUID regression', () => { const duplicateShortName = 'authctx_dup' const authenticatedOrgUUID = '11111111-1111-4111-8111-111111111111' - const legacySecretariatOrgUUID = '22222222-2222-4222-8222-222222222222' + const otherOrgUUID = '22222222-2222-4222-8222-222222222222' const authenticatedUserUUID = '33333333-3333-4333-8333-333333333333' const authenticatedUsername = 'authctx_dup_user@example.org' const authenticatedCveId = 'CVE-2099-900001' - const legacySecretariatCveId = 'CVE-2099-900002' + const otherCveId = 'CVE-2099-900002' const testSecret = 'S96E4QT-SMT4YE3-KX03X6K-4615CED' async function cleanupDuplicateShortNameFixtures () { @@ -188,25 +186,13 @@ describe('Authenticated request context middleware integration', () => { { short_name: duplicateShortName } ] }) - await Org.deleteMany({ - $or: [ - { UUID: { $in: [authenticatedOrgUUID, legacySecretariatOrgUUID] } }, - { short_name: duplicateShortName } - ] - }) await BaseUser.deleteMany({ $or: [ { UUID: authenticatedUserUUID }, { username: authenticatedUsername } ] }) - await User.deleteMany({ - $or: [ - { UUID: authenticatedUserUUID }, - { username: authenticatedUsername } - ] - }) - await CveId.deleteMany({ cve_id: { $in: [authenticatedCveId, legacySecretariatCveId] } }) + await CveId.deleteMany({ cve_id: { $in: [authenticatedCveId, otherCveId] } }) } before(async function () { @@ -237,47 +223,6 @@ describe('Authenticated request context middleware integration', () => { status: 'active' }) - await Org.collection.insertOne({ - UUID: legacySecretariatOrgUUID, - name: 'Legacy Secretariat Duplicate Short Name', - short_name: duplicateShortName, - authority: { - active_roles: [CONSTANTS.AUTH_ROLE_ENUM.SECRETARIAT] - }, - policies: { - id_quota: 5 - }, - inUse: true - }) - - await Org.collection.insertOne({ - UUID: authenticatedOrgUUID, - name: 'Authenticated Duplicate Short Name CNA', - short_name: duplicateShortName, - authority: { - active_roles: [CONSTANTS.AUTH_ROLE_ENUM.CNA] - }, - policies: { - id_quota: 5 - }, - inUse: true - }) - - await User.collection.insertOne({ - UUID: authenticatedUserUUID, - username: authenticatedUsername, - org_UUID: authenticatedOrgUUID, - secret: mwFixtures.existentUser.secret, - active: true, - authority: { - active_roles: [] - }, - name: { - first: 'Authenticated', - last: 'Requester' - } - }) - await CveId.collection.insertMany([ { cve_id: authenticatedCveId, @@ -291,12 +236,12 @@ describe('Authenticated request context middleware integration', () => { reserved: new Date('2026-01-01T00:00:00.000Z') }, { - cve_id: legacySecretariatCveId, + cve_id: otherCveId, cve_year: '2099', state: CONSTANTS.CVE_STATES.RESERVED, - owning_cna: legacySecretariatOrgUUID, + owning_cna: otherOrgUUID, requested_by: { - cna: legacySecretariatOrgUUID, + cna: otherOrgUUID, user: authenticatedUserUUID }, reserved: new Date('2026-01-01T00:00:00.000Z') @@ -309,7 +254,7 @@ describe('Authenticated request context middleware integration', () => { await cleanupDuplicateShortNameFixtures() }) - it('scopes controller access to the authenticated org UUID when legacy short-name lookup would be Secretariat', async () => { + it('scopes controller access to the authenticated org UUID rather than an unrelated CVE-ID owner', async () => { const headers = { 'content-type': 'application/json', [CONSTANTS.AUTH_HEADERS.ORG]: duplicateShortName, @@ -324,7 +269,7 @@ describe('Authenticated request context middleware integration', () => { expect(res).to.have.status(200) const cveIds = res.body.cve_ids.map(cveId => cveId.cve_id) expect(cveIds).to.include(authenticatedCveId) - expect(cveIds).to.not.include(legacySecretariatCveId) + expect(cveIds).to.not.include(otherCveId) }) }) }) diff --git a/test/integration-tests/user/legacyUserWriteContractTest.js b/test/integration-tests/user/legacyUserWriteContractTest.js index f8e4e32be..c91561860 100644 --- a/test/integration-tests/user/legacyUserWriteContractTest.js +++ b/test/integration-tests/user/legacyUserWriteContractTest.js @@ -8,7 +8,6 @@ const constants = require('../constants.js') const app = require('../../../src/index.js') const BaseOrg = require('../../../src/model/baseorg') const BaseUser = require('../../../src/model/baseuser') -const LegacyUser = require('../../../src/model/user') const secretariatHeaders = { ...constants.headers, 'content-type': 'application/json' } @@ -46,12 +45,10 @@ describe('Legacy user write response contract', () => { const createdBaseUser = await BaseUser.findOne({ UUID: userUUID }) const orgAfterCreate = await BaseOrg.findOne({ short_name: orgShortName }) - const legacyUser = await LegacyUser.findOne({ UUID: userUUID }) expect(createdBaseUser).to.exist expect(createdBaseUser.status).to.equal('active') expect(orgAfterCreate.users).to.include(userUUID) expect(orgAfterCreate.admins).to.include(userUUID) - expect(legacyUser).to.be.null await chai.request(app) .put(`/api/org/${orgShortName}/user/${encodeURIComponent(username)}?name.first=Updated&active=false`) diff --git a/test/unit-tests/org/legacy/orgGetIdQuotaTest.js b/test/unit-tests/org/legacy/orgGetIdQuotaTest.js index 6456840f4..3a78167de 100644 --- a/test/unit-tests/org/legacy/orgGetIdQuotaTest.js +++ b/test/unit-tests/org/legacy/orgGetIdQuotaTest.js @@ -12,8 +12,6 @@ app.use(express.urlencoded({ extended: false })) // Allows us to handle url enco const middleware = require('../../../../src/middleware/middleware') app.use(middleware.createCtxAndReqUUID) -const Org = require('../../../../src/model/org') -const getConstants = require('../../../../src/constants').getConstants const errors = require('../../../../src/controller/org.controller/error') const error = new errors.OrgControllerError() @@ -44,33 +42,6 @@ describe('Testing the GET /org/:shortname/id_quota endpoint in Org Controller', }) context('Negative Tests', () => { - it('Org with a negative id_quota was not saved', (done) => { - const org = new Org(orgFixtures.orgWithNegativeIdQuota) - org.save() - .then(() => { - expect(false) - }) - .catch((error) => { - const CONSTANTS = getConstants() - expect(error.errors['policies.id_quota'].message).to.equal(CONSTANTS.MONGOOSE_VALIDATION.Org_policies_id_quota_min_message) - }) - done() - }) - - it('Org with an id_quota greater than the max was not saved', (done) => { - const org = new Org(orgFixtures.orgExceedingMaxIdQuota) - - org.save() - .then(() => { - expect(false) - }) - .catch((error) => { - const CONSTANTS = getConstants() - expect(error.errors['policies.id_quota'].message).to.equal(CONSTANTS.MONGOOSE_VALIDATION.Org_policies_id_quota_max_message) - }) - done() - }) - it('Requestor is not secretariat or a user of the same org', (done) => { class OrgNotOwnerOrSecretariatIdQuota { async isSecretariat () {