From 4c690406377b648c8f79b09e01cf88434af23f0e Mon Sep 17 00:00:00 2001 From: Chenyang Li Date: Mon, 13 Jul 2026 11:40:19 -0400 Subject: [PATCH 01/11] #1843 Use field projections for orgs and users in getFilteredCveId Add optional projection parameters to OrgRepository.getAllOrgs and UserRepository.getAllUsers so callers can limit returned fields. getFilteredCveId now fetches only UUID and short_name for orgs, and UUID, username, and org_UUID for users, instead of entire documents. --- src/controller/cve-id.controller/cve-id.controller.js | 5 +++-- src/repositories/orgRepository.js | 4 ++-- src/repositories/userRepository.js | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/controller/cve-id.controller/cve-id.controller.js b/src/controller/cve-id.controller/cve-id.controller.js index 8172793ac..a82938b69 100644 --- a/src/controller/cve-id.controller/cve-id.controller.js +++ b/src/controller/cve-id.controller/cve-id.controller.js @@ -39,8 +39,9 @@ async function getFilteredCveId (req, res, next) { const requesterOrgUUID = await authContext.getRequesterOrgUUID(req, orgRepo) // Create map of orgUUID to shortnames and users to simplify aggregation later - const orgs = await orgRepo.getAllOrgs() - const users = await userRepo.getAllUsers() + // 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 = {} diff --git a/src/repositories/orgRepository.js b/src/repositories/orgRepository.js index 3eb5d7684..48f47ee93 100644 --- a/src/repositories/orgRepository.js +++ b/src/repositories/orgRepository.js @@ -47,8 +47,8 @@ class OrgRepository extends BaseRepository { return utils.isBulkDownload(shortName) } - async getAllOrgs () { - return this.collection.find() + async getAllOrgs (options = {}, projection = {}) { + return this.collection.find({}, projection, options) } async deleteOneByShortName (shortName, options = {}) { diff --git a/src/repositories/userRepository.js b/src/repositories/userRepository.js index 9cd152b28..1d2d43f5e 100644 --- a/src/repositories/userRepository.js +++ b/src/repositories/userRepository.js @@ -61,8 +61,8 @@ class UserRepository extends BaseRepository { return this.collection.findOneAndUpdate(filter, updatePayload, options) } - async getAllUsers () { - return this.collection.find() + async getAllUsers (options = {}, projection = {}) { + return this.collection.find({}, projection, options) } } From c609aaa4a64414b234a92a466cc009d3e2136a4b Mon Sep 17 00:00:00 2001 From: Chenyang Li Date: Thu, 16 Jul 2026 11:03:11 -0400 Subject: [PATCH 02/11] Assert getAllOrgs and getAllUsers are called with the expected field projections --- test/unit-tests/cve-id/cveIdGetAllTest.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/unit-tests/cve-id/cveIdGetAllTest.js b/test/unit-tests/cve-id/cveIdGetAllTest.js index e30098a7f..2946f871c 100644 --- a/test/unit-tests/cve-id/cveIdGetAllTest.js +++ b/test/unit-tests/cve-id/cveIdGetAllTest.js @@ -143,6 +143,12 @@ 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 () => { + 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) + }) + it('Should swap UUIDs for names in Cve-ids', async () => { await cveIdController.CVEID_GET_FILTER(req, res, next) expect(res.status.args[0][0]).to.equal(200) From e0e601bc84b41f63b5d176d1140f41369731b81a Mon Sep 17 00:00:00 2001 From: david-rocca Date: Mon, 20 Jul 2026 14:32:17 -0400 Subject: [PATCH 03/11] remove legacy registry=true support --- api-docs/openapi.json | 47 +--- src/controller/org.controller/index.js | 4 +- .../org.controller/org.controller.js | 157 +++++------- .../org.controller/org.middleware.js | 231 +----------------- src/controller/user.controller/index.js | 17 +- .../user.controller/user.controller.js | 4 +- .../user.controller/user.middleware.js | 2 +- src/middleware/middleware.js | 6 - src/swagger.js | 9 - .../org/regularUsersTestRegistry.js | 10 - test/integration-tests/user/getUsersTest.js | 6 +- test/unit-tests/org/orgCreateTest.js | 3 +- 12 files changed, 73 insertions(+), 423 deletions(-) diff --git a/api-docs/openapi.json b/api-docs/openapi.json index f9fbbb5bd..d925207de 100644 --- a/api-docs/openapi.json +++ b/api-docs/openapi.json @@ -11,7 +11,7 @@ }, "servers": [ { - "url": "https://cveawg-dev.mitre.org/api" + "url": "urlplaceholder" } ], "paths": { @@ -3334,13 +3334,6 @@ "description": "

Access Control

User must belong to an organization with the Secretariat role

Expected Behavior

Secretariat: Retrieves information about all organizations

", "operationId": "orgAll", "parameters": [ - { - "name": "registry", - "in": "query", - "schema": { - "type": "string" - } - }, { "$ref": "#/components/parameters/pageQuery" }, @@ -3633,13 +3626,6 @@ }, "description": "The shortname of the organization" }, - { - "name": "registry", - "in": "query", - "schema": { - "type": "string" - } - }, { "$ref": "#/components/parameters/id_quota" }, @@ -3839,13 +3825,6 @@ }, "description": "The shortname of the organization" }, - { - "name": "registry", - "in": "query", - "schema": { - "type": "string" - } - }, { "$ref": "#/components/parameters/pageQuery" }, @@ -4365,9 +4344,6 @@ { "$ref": "#/components/parameters/pageQuery" }, - { - "$ref": "#/components/parameters/registry" - }, { "$ref": "#/components/parameters/apiEntityHeader" }, @@ -4454,9 +4430,6 @@ { "$ref": "#/components/parameters/pageQuery" }, - { - "$ref": "#/components/parameters/registry" - }, { "$ref": "#/components/parameters/apiEntityHeader" }, @@ -4473,14 +4446,7 @@ "content": { "application/json": { "schema": { - "oneOf": [ - { - "$ref": "../schemas/user/list-users-response.json" - }, - { - "$ref": "../schemas/registry-user/list-registry-users-response.json" - } - ] + "$ref": "../schemas/user/list-users-response.json" } } } @@ -6104,15 +6070,6 @@ "minimum": 1 } }, - "registry": { - "in": "query", - "name": "registry", - "description": "When set to true, the endpoint will expect request data to conform to the applicable User Registry schema, and will provide response data conforming to the applicable User Registry schema. Defaults to false.", - "required": false, - "schema": { - "type": "boolean" - } - }, "short_name": { "in": "query", "name": "short_name", diff --git a/src/controller/org.controller/index.js b/src/controller/org.controller/index.js index 8e362cf2f..d3b472d1b 100644 --- a/src/controller/org.controller/index.js +++ b/src/controller/org.controller/index.js @@ -9,7 +9,7 @@ const { body, param, query } = require('express-validator') const { parseGetParams, parsePostParams, parsePutParams, parseError, isUserRole, isValidUsername, isOrgRole, validateUpdateOrgParameters, shortCircuitLegacyCpsMitreOrgParameters } = require('./org.middleware') // Only God and Javascript know swhy its saying it is not used when it is..... // eslint-disable-next-line no-unused-vars -const { toUpperCaseArray, isFlatStringArray, handleRegistryParameter } = require('../../middleware/middleware') +const { toUpperCaseArray, isFlatStringArray } = require('../../middleware/middleware') const getConstants = require('../../../src/constants').getConstants const CONSTANTS = getConstants() @@ -1268,7 +1268,6 @@ router.get('/org', } } */ - mw.handleRegistryParameter, mw.validateUser, mw.onlySecretariat, query().custom((query) => { return mw.validateQueryParameterNames(query, ['page']) }), @@ -1670,7 +1669,6 @@ router.get('/org/:shortname/users', } } */ - mw.handleRegistryParameter, mw.validateUser, param(['shortname']).isString().trim().notEmpty().isLength({ min: CONSTANTS.MIN_SHORTNAME_LENGTH, max: CONSTANTS.MAX_SHORTNAME_LENGTH }), query().custom((query) => { return mw.validateQueryParameterNames(query, ['page']) }), diff --git a/src/controller/org.controller/org.controller.js b/src/controller/org.controller/org.controller.js index cdadb284e..9bff904f0 100644 --- a/src/controller/org.controller/org.controller.js +++ b/src/controller/org.controller/org.controller.js @@ -8,6 +8,11 @@ const validateUUID = require('uuid').validate const _ = require('lodash') const authContext = require('../../utils/authContext') +const LEGACY_ORG_FORMAT = true +const REGISTRY_ORG_FORMAT = false +const LEGACY_USER_OBJECT = false +const REGISTRY_USER_OBJECT = true + /** * Get the details of all orgs. * Called by GET /api/org @@ -32,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 }, true) + const returnValue = await repo.getAllOrgs({ ...options }, LEGACY_ORG_FORMAT) logger.info({ uuid: req.ctx.uuid, message: 'The orgs were sent to the user.' }) return res.status(200).json(returnValue) @@ -58,21 +63,20 @@ async function getOrg (req, res, next) { const requesterOrgShortName = req.ctx.org const identifier = req.ctx.params.identifier const identifierIsUUID = validateUUID(identifier) - const returnLegacyFormat = true let returnValue try { - const requesterOrg = await authContext.getRequesterOrg(req, repo, {}, returnLegacyFormat) + const requesterOrg = await authContext.getRequesterOrg(req, repo, {}, LEGACY_ORG_FORMAT) // Ensure requester org exists if (!requesterOrg) { return res.status(404).json(error.orgDne(requesterOrgShortName, 'requesterOrgShortName', 'header')) } - const isSecretariat = await authContext.isRequesterSecretariat(req, repo, {}, returnLegacyFormat) + const isSecretariat = await authContext.isRequesterSecretariat(req, repo, {}, LEGACY_ORG_FORMAT) const isRequesterSameOrg = identifierIsUUID ? requesterOrg.UUID === identifier - : await authContext.isRequesterSameOrg(req, repo, identifier, {}, returnLegacyFormat) + : await authContext.isRequesterSameOrg(req, repo, identifier, {}, LEGACY_ORG_FORMAT) // Ensure that if the requester is not Secretariat, they can't view orgs other than their own if (!isRequesterSameOrg && !isSecretariat) { @@ -80,7 +84,7 @@ async function getOrg (req, res, next) { return res.status(403).json(error.notSameOrgOrSecretariat()) } - returnValue = await repo.getOrg(identifier, identifierIsUUID, {}, returnLegacyFormat) + returnValue = await repo.getOrg(identifier, identifierIsUUID, {}, LEGACY_ORG_FORMAT) } catch (err) { // Handle the specific error thrown by BaseOrgRepository.getOrg if (err.message && err.message.includes('Unknown Org type requested')) { @@ -139,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 }, {}, !req.useRegistry) + const isSameOrg = await authContext.isRequesterSameOrg(req, orgRepo, { UUID: orgUUID, short_name: orgShortName }, {}, LEGACY_ORG_FORMAT) 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, !!req.useRegistry) + const payload = await userRepo.getAllUsersByOrgShortname(orgShortName, options, LEGACY_USER_OBJECT) logger.info({ uuid: req.ctx.uuid, message: `The users of ${orgShortName} organization were sent to the user.` }) return res.status(200).json(payload) @@ -169,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, {}, !req.useRegistry) + const isSecretariat = await authContext.isRequesterSecretariat(req, orgRepo, {}, LEGACY_ORG_FORMAT) const orgUUID = await orgRepo.getOrgUUID(orgShortName) - const isSameOrg = await authContext.isRequesterSameOrg(req, orgRepo, { UUID: orgUUID, short_name: orgShortName }, {}, !req.useRegistry) + const isSameOrg = await authContext.isRequesterSameOrg(req, orgRepo, { UUID: orgUUID, short_name: orgShortName }, {}, LEGACY_ORG_FORMAT) 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()) @@ -185,7 +189,7 @@ 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, {}, !!req.useRegistry) + const result = await userRepo.findOneByUsernameAndOrgShortname(username, orgShortName, {}, LEGACY_USER_OBJECT) if (!result) { logger.info({ uuid: req.ctx.uuid, message: username + ' does not exist.' }) @@ -218,22 +222,24 @@ async function getOrgIdQuota (req, res, next) { try { const orgRepo = req.ctx.repositories.getBaseOrgRepository() const shortName = req.ctx.params.shortname + const isRegistry = req.useRegistry === true + const returnLegacyFormat = isRegistry ? REGISTRY_ORG_FORMAT : LEGACY_ORG_FORMAT - const isSecretariat = await authContext.isRequesterSecretariat(req, orgRepo, {}, !req.useRegistry) - const isSameOrg = await authContext.isRequesterSameOrg(req, orgRepo, shortName, {}, !req.useRegistry) + const isSecretariat = await authContext.isRequesterSecretariat(req, orgRepo, {}, returnLegacyFormat) + const isSameOrg = await authContext.isRequesterSameOrg(req, orgRepo, shortName, {}, returnLegacyFormat) 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, {}, !req.useRegistry) + const org = await orgRepo.getOrg(shortName, false, {}, returnLegacyFormat) 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, !req.useRegistry) + const returnPayload = await orgRepo.getOrgIdQuota(org, returnLegacyFormat) 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) { @@ -264,21 +270,8 @@ async function createOrg (req, res, next) { try { session.startTransaction({ readPreference: 'primary' }) - if (req.useRegistry) { - // If we are creating an org via the registry flag, we can do a full validation. - const result = await repo.validateOrg(body, { session }) - if (!result.isValid) { - logger.error(JSON.stringify({ uuid: req.ctx.uuid, message: 'CVE JSON schema validation FAILED.' })) - await session.abortTransaction() - if (!Array.isArray(body?.authority) || body?.authority.some(item => typeof item !== 'string')) { - return res.status(400).json({ error: 'BAD_INPUT', message: 'Parameters were invalid', details: [{ param: 'authority', msg: 'Parameter must be a one-dimensional array of strings' }] }) - } - return res.status(400).json({ error: 'BAD_INPUT', message: 'Parameters were invalid', errors: result.errors }) - } - } - // Check to see if the org already exits - if (await repo.orgExists(body?.short_name, { session }, !req.useRegistry)) { + if (await repo.orgExists(body?.short_name, { session }, LEGACY_ORG_FORMAT)) { logger.info({ uuid: req.ctx.uuid, message: body?.short_name + ' organization was not created because it already exists.' }) await session.abortTransaction() return res.status(400).json(error.orgExists(body?.short_name)) @@ -295,9 +288,9 @@ async function createOrg (req, res, next) { return res.status(400).json(error.aliasCollision(collisionString)) } const userRepo = req.ctx.repositories.getBaseUserRepository() - const isSecretariat = await authContext.isRequesterSecretariat(req, repo, { session }, !req.useRegistry) - const requestingUserUUID = await authContext.getRequesterUserUUID(req, userRepo, repo, { session }, !!req.useRegistry) - returnValue = await repo.createOrg(req.ctx.body, { session, upsert: true }, !req.useRegistry, requestingUserUUID, isSecretariat) + const isSecretariat = await authContext.isRequesterSecretariat(req, repo, { session }, LEGACY_ORG_FORMAT) + const requestingUserUUID = await authContext.getRequesterUserUUID(req, userRepo, repo, { session }, LEGACY_USER_OBJECT) + returnValue = await repo.createOrg(req.ctx.body, { session, upsert: true }, LEGACY_ORG_FORMAT, requestingUserUUID, isSecretariat) await session.commitTransaction() } catch (error) { @@ -351,23 +344,6 @@ async function updateOrg (req, res, next) { try { session.startTransaction({ readPreference: 'primary' }) - // TODO: Check to see if this check is needed for both options - if (req.useRegistry) { - if (queryParametersJson['active_roles.add']) { - if (!Array.isArray(queryParametersJson.active_roles?.add) || queryParametersJson.active_roles?.add.some(item => typeof item !== 'string')) { - await session.abortTransaction() - return res.status(400).json({ message: 'Parameters were invalid', details: [{ param: 'authority', msg: 'Parameter must be a one-dimensional array of strings' }] }) - } - } - - if (queryParametersJson['active_roles.remove']) { - if (!Array.isArray(queryParametersJson.active_roles?.remove) || queryParametersJson.active_roles?.remove.some(item => typeof item !== 'string')) { - await session.abortTransaction() - return res.status(400).json({ message: 'Parameters were invalid', details: [{ param: 'authority', msg: 'Parameter must be a one-dimensional array of strings' }] }) - } - } - } - if (!(await orgRepository.orgExists(shortNameUrlParameter, { session }))) { logger.info({ uuid: req.ctx.uuid, message: `Organization ${shortNameUrlParameter} not found.` }) await session.abortTransaction() @@ -392,9 +368,9 @@ async function updateOrg (req, res, next) { } const userRepo = req.ctx.repositories.getBaseUserRepository() - const requestingUserUUID = await authContext.getRequesterUserUUID(req, userRepo, orgRepository, { session }, !!req.useRegistry) - const isSecretariat = await authContext.isRequesterSecretariat(req, orgRepository, { session }, !req.useRegistry) - const isAdmin = await authContext.isRequesterAdmin(req, userRepo, orgRepository, { session }, !!req.useRegistry) + const requestingUserUUID = await authContext.getRequesterUserUUID(req, userRepo, orgRepository, { session }, LEGACY_USER_OBJECT) + const isSecretariat = await authContext.isRequesterSecretariat(req, orgRepository, { session }, LEGACY_ORG_FORMAT) + const isAdmin = await authContext.isRequesterAdmin(req, userRepo, orgRepository, { session }, LEGACY_USER_OBJECT) if (!isSecretariat) { const secretariatOnlyFields = getConstants().SECRETARIAT_ONLY_FIELDS @@ -405,7 +381,7 @@ async function updateOrg (req, res, next) { return res.status(403).json(error.secretariatOnlyEditing(restrictedFieldsSent)) } } - const updatedOrg = await orgRepository.updateOrg(shortNameUrlParameter, queryParametersJson, { session }, !req.useRegistry, requestingUserUUID, isAdmin, isSecretariat) + const updatedOrg = await orgRepository.updateOrg(shortNameUrlParameter, queryParametersJson, { session }, LEGACY_ORG_FORMAT, requestingUserUUID, isAdmin, isSecretariat) responseMessage = { message: `${updatedOrg.short_name} organization was successfully updated.`, updated: updatedOrg } // Clarify message payload = { @@ -433,7 +409,7 @@ async function updateOrg (req, res, next) { /** * Creates a user only if the org exists and the user does not exist for the specified shortname and username. - * Called by POST /api/registry/org/{shortname}/user, POST /api/org/{shortname}/user + * Called by POST /api/org/{shortname}/user * * @param {Object} req - The request object * @param {Object} res - The response object @@ -446,7 +422,6 @@ async function createUser (req, res, next) { const userRepo = req.ctx.repositories.getBaseUserRepository() const orgRepo = req.ctx.repositories.getBaseOrgRepository() const orgShortName = req.ctx.params.shortname - const constants = getConstants() let returnValue // Check to make sure Org Exists first @@ -469,30 +444,13 @@ async function createUser (req, res, next) { try { session.startTransaction({ readPreference: 'primary' }) - if (req.useRegistry) { - const result = await userRepo.validateUser(body) - if (body?.role && typeof body?.role !== 'string') { - await session.abortTransaction() - return res.status(400).json({ message: 'Parameters were invalid', details: [{ param: 'role', msg: 'Parameter must be a string' }] }) - } - if (body?.role && !constants.USER_ROLES.includes(body?.role)) { - await session.abortTransaction() - return res.status(400).json({ message: 'Parameters were invalid', details: [{ param: 'role', msg: `Role must be one of the following: ${constants.USER_ROLES}` }] }) - } - if (!result.isValid) { - logger.error(JSON.stringify({ uuid: req.ctx.uuid, message: 'User JSON schema validation FAILED.' })) - await session.abortTransaction() - return res.status(400).json({ message: 'Parameters were invalid', errors: result.errors }) - } - } else { - if (!body?.username || typeof body?.username !== 'string') { - await session.abortTransaction() - return res.status(400).json({ message: 'Parameters were invalid', details: [{ param: 'username', msg: 'Parameter must be a non empty string' }] }) - } + if (!body?.username || typeof body?.username !== 'string') { + await session.abortTransaction() + return res.status(400).json({ message: 'Parameters were invalid', details: [{ param: 'username', msg: 'Parameter must be a non empty string' }] }) } // Ask repo if user already exists - if (await userRepo.orgHasUser(orgShortName, body?.username, { session }, !!req.useRegistry)) { + if (await userRepo.orgHasUser(orgShortName, body?.username, { session }, LEGACY_USER_OBJECT)) { logger.info({ uuid: req.ctx.uuid, message: `${body?.username} user was not created because it already exists.` }) await session.abortTransaction() return res.status(400).json(error.userExists(body?.username)) @@ -500,10 +458,10 @@ async function createUser (req, res, next) { let isRequesterAdminOrSecretariat if (!req.ctx.authenticated && !req.ctx.orgUUID && typeof userRepo.isAdminOrSecretariat === 'function') { - isRequesterAdminOrSecretariat = await userRepo.isAdminOrSecretariat(orgShortName, req.ctx.user, req.ctx.org, { session }, !!req.useRegistry) + isRequesterAdminOrSecretariat = await userRepo.isAdminOrSecretariat(orgShortName, req.ctx.user, req.ctx.org, { session }, LEGACY_USER_OBJECT) } else { - const isRequesterSecretariat = await authContext.isRequesterSecretariat(req, orgRepo, { session }, !req.useRegistry) - const isRequesterAdminOfTargetOrg = await authContext.isRequesterAdminOfOrg(req, userRepo, orgRepo, orgShortName, { session }, !!req.useRegistry) + const isRequesterSecretariat = await authContext.isRequesterSecretariat(req, orgRepo, { session }, LEGACY_ORG_FORMAT) + const isRequesterAdminOfTargetOrg = await authContext.isRequesterAdminOfOrg(req, userRepo, orgRepo, orgShortName, { session }, LEGACY_USER_OBJECT) isRequesterAdminOrSecretariat = isRequesterSecretariat || isRequesterAdminOfTargetOrg } @@ -518,8 +476,8 @@ async function createUser (req, res, next) { return res.status(400).json(error.userLimitReached()) } - const requestingUserUUID = await authContext.getRequesterUserUUID(req, userRepo, orgRepo, { session }, !!req.useRegistry) - returnValue = await userRepo.createUser(orgShortName, body, { session, upsert: true }, !!req.useRegistry, requestingUserUUID) + const requestingUserUUID = await authContext.getRequesterUserUUID(req, userRepo, orgRepo, { session }, LEGACY_USER_OBJECT) + returnValue = await userRepo.createUser(orgShortName, body, { session, upsert: true }, LEGACY_USER_OBJECT, requestingUserUUID) await session.commitTransaction() } catch (error) { await session.abortTransaction() @@ -552,7 +510,7 @@ async function createUser (req, res, next) { /** * Updates a user only if the user exist for the specified username. * If no user exists, it does not create the user. - * Called by PUT /org/{shortname}/user/{username}, PUT /org/{shortname}/user/{username} + * Called by PUT /org/{shortname}/user/{username} * * @param {Object} req - The request object * @param {Object} res - The response object @@ -575,26 +533,20 @@ async function updateUser (req, res, next) { const queryParametersJson = req.ctx.query // Get requester UUID for later - const requesterUUID = await authContext.getRequesterUserUUID(req, userRepo, orgRepo, { session }, !!req.useRegistry) - const targetUserUUID = await userRepo.getUserUUID(usernameParams, shortNameParams, { session }, !!req.useRegistry) + const requesterUUID = await authContext.getRequesterUserUUID(req, userRepo, orgRepo, { session }, LEGACY_USER_OBJECT) + const targetUserUUID = await userRepo.getUserUUID(usernameParams, shortNameParams, { session }, LEGACY_USER_OBJECT) - const isRequesterSecretariat = await authContext.isRequesterSecretariat(req, orgRepo, { session }, !req.useRegistry) - const isAdmin = await authContext.isRequesterAdmin(req, userRepo, orgRepo, { session }, !!req.useRegistry) + const isRequesterSecretariat = await authContext.isRequesterSecretariat(req, orgRepo, { session }, LEGACY_ORG_FORMAT) + const isAdmin = await authContext.isRequesterAdmin(req, userRepo, orgRepo, { session }, LEGACY_USER_OBJECT) const targetOrgUUID = await orgRepo.getOrgUUID(shortNameParams, { session }) - // if (req.useRegistry) { - // if (body?.role && typeof body?.role !== 'string') { - // return res.status(400).json({ message: 'Parameters were invalid', details: [{ param: 'role', msg: 'Parameter must be a string' }] }) - // } - // } - if (!targetOrgUUID) { logger.info({ uuid: req.ctx.uuid, message: `Target organization ${shortNameParams} does not exist.` }) await session.abortTransaction() return res.status(404).json(error.orgDnePathParam(shortNameParams)) } - const requesterSameOrg = await authContext.isRequesterSameOrg(req, orgRepo, { UUID: targetOrgUUID, short_name: shortNameParams }, { session }, !req.useRegistry) + const requesterSameOrg = await authContext.isRequesterSameOrg(req, orgRepo, { UUID: targetOrgUUID, short_name: shortNameParams }, { session }, LEGACY_ORG_FORMAT) if (!requesterSameOrg && !isRequesterSecretariat) { logger.info({ uuid: req.ctx.uuid, message: `${shortNameParams} organization data can only be modified by users of the same organization or the Secretariat.` }) await session.abortTransaction() @@ -694,7 +646,7 @@ async function updateUser (req, res, next) { } } - const payload = await userRepo.updateUser(usernameParams, shortNameParams, queryParametersJson, { session }, !!req.useRegistry, requesterUUID) + const payload = await userRepo.updateUser(usernameParams, shortNameParams, queryParametersJson, { session }, LEGACY_USER_OBJECT, requesterUUID) await session.commitTransaction() return res.status(200).json({ message: `${usernameParams} was successfully updated.`, updated: payload }) } catch (err) { @@ -730,12 +682,15 @@ async function resetSecret (req, res, next) { const orgRepo = req.ctx.repositories.getBaseOrgRepository() const userRepo = req.ctx.repositories.getBaseUserRepository() + const isRegistry = req.useRegistry === true + const returnLegacyFormat = isRegistry ? REGISTRY_ORG_FORMAT : LEGACY_ORG_FORMAT + const isRegistryUserObject = isRegistry ? REGISTRY_USER_OBJECT : LEGACY_USER_OBJECT try { session.startTransaction({ readPreference: 'primary' }) // Check if target org exists - const targetOrgUUID = await orgRepo.getOrgUUID(targetOrgShortName, { session }, !req.useRegistry) + const targetOrgUUID = await orgRepo.getOrgUUID(targetOrgShortName, { session }, returnLegacyFormat) if (!targetOrgUUID) { logger.info({ uuid: req.ctx.uuid, message: 'Org DNE' }) await session.abortTransaction() @@ -743,11 +698,11 @@ async function resetSecret (req, res, next) { } const targetOrg = { UUID: targetOrgUUID, short_name: targetOrgShortName } - const requesterUserUUID = await authContext.getRequesterUserUUID(req, userRepo, orgRepo, { session }, !!req.useRegistry) - const isRequesterSecretariat = await authContext.isRequesterSecretariat(req, orgRepo, { session }, !req.useRegistry) + const requesterUserUUID = await authContext.getRequesterUserUUID(req, userRepo, orgRepo, { session }, isRegistryUserObject) + const isRequesterSecretariat = await authContext.isRequesterSecretariat(req, orgRepo, { session }, returnLegacyFormat) if (!isRequesterSecretariat) { - const requesterSameOrg = await authContext.isRequesterSameOrg(req, orgRepo, targetOrg, { session }, !req.useRegistry) + const requesterSameOrg = await authContext.isRequesterSameOrg(req, orgRepo, targetOrg, { session }, returnLegacyFormat) if (!requesterSameOrg) { logger.info({ uuid: req.ctx.uuid, message: 'The api secret can only be reset by the Secretariat, an Org admin or if the requester is the user.' }) await session.abortTransaction() @@ -756,7 +711,7 @@ async function resetSecret (req, res, next) { } // Check if target user exists in target org - const targetUserUUID = await userRepo.getUserUUID(targetUsername, targetOrgShortName, { session }, !!req.useRegistry) + const targetUserUUID = await userRepo.getUserUUID(targetUsername, targetOrgShortName, { session }, isRegistryUserObject) if (!targetUserUUID) { logger.info({ uuid: req.ctx.uuid, message: 'User DNE' }) await session.abortTransaction() @@ -768,7 +723,7 @@ async function resetSecret (req, res, next) { // 1. WE are not the same user if (requesterUserUUID !== targetUserUUID) { // Check to see if we are the admin of the target organization - const isAdminOfTargetOrg = await authContext.isRequesterAdminOfOrg(req, userRepo, orgRepo, targetOrg, { session }, !!req.useRegistry) + const isAdminOfTargetOrg = await authContext.isRequesterAdminOfOrg(req, userRepo, orgRepo, targetOrg, { session }, isRegistryUserObject) if (!isAdminOfTargetOrg) { logger.info({ uuid: req.ctx.uuid, message: 'The api secret can only be reset by the Secretariat, an Org admin or if the requester is the user.' }) @@ -778,7 +733,7 @@ async function resetSecret (req, res, next) { } } - const updatedSecret = await userRepo.resetSecret(targetUsername, targetOrgShortName, { session }, !!req.useRegistry) + const updatedSecret = await userRepo.resetSecret(targetUsername, targetOrgShortName, { session }, isRegistryUserObject) logger.info({ uuid: req.ctx.uuid, message: `The API secret was successfully reset and sent to ${targetUsername}` }) const payload = { diff --git a/src/controller/org.controller/org.middleware.js b/src/controller/org.controller/org.middleware.js index d8331ecc9..e8f642899 100644 --- a/src/controller/org.controller/org.middleware.js +++ b/src/controller/org.controller/org.middleware.js @@ -2,7 +2,7 @@ const getConstants = require('../../constants').getConstants const { validationResult } = require('express-validator') const errors = require('./error') const error = new errors.OrgControllerError() -const { body, param, query } = require('express-validator') +const { param, query } = require('express-validator') const { toUpperCaseArray, isFlatStringArray } = require('../../middleware/middleware') const CONSTANTS = getConstants() const errorMsgs = require('../../middleware/errorMessages') @@ -22,186 +22,8 @@ function isOrgRole (val) { return true } -function validateCreateOrgParameters () { - return async (req, res, next) => { - const useRegistry = req.query.registry === 'true' - let validations = [] - if (useRegistry) { - // Not allowed - // users, , in_use, created, last_updated - const orgOptions = ['CNA', 'Secretariat', 'Bulk Download', 'ADP'] - validations = [ - body(['short_name']).isString() - .trim() - .notEmpty() - .isLength({ min: CONSTANTS.MIN_SHORTNAME_LENGTH, max: CONSTANTS.MAX_SHORTNAME_LENGTH }), - body(['long_name']).isString() - .trim() - .notEmpty(), - body(['cve_program_org_function']) - .default('CNA') - .isString() - .isIn(orgOptions), - body(['oversees']).default([]) - .isArray(), - body(['top_level_root']).default('') - .isString(), - body(['advisory_locations']) - .default([]) - .custom(isFlatStringArray), - body(['advisory_location_require_credentials']) - .default(false) - .isBoolean(), - body(['vulnerability_advisory_location_for_web_scraping']) - .default([]) - .custom(isFlatStringArray), - body(['tl_root_start_date']) - .default(null) - .isDate(), - body(['is_cna_discussion_list']) - .default(false) - .isBoolean(), - body([ - 'program_data.cve_website_update_date', - 'program_data.partner_active_date', - 'program_data.partner_inactive_date' - ]) - .optional({ nullable: true }) - .isDate(), - body(['program_data.cve_website_update_needed']) - .optional() - .isBoolean(), - body( - [ - 'charter_or_scope', - 'disclosure_policy', - 'product_list', - 'contact_info.websites', - 'contact_info.emails', - 'contact_info.phone', - '', - '', - 'partner_role_type', - 'partner_number', - 'partner_country', - 'program_data.status', - 'industry' - ]) - .default('') - .isString(), - body(['authority.active_roles']) - .default([CONSTANTS.AUTH_ROLE_ENUM.CNA]) - .custom(isFlatStringArray) - .customSanitizer(toUpperCaseArray) - .custom(isOrgRole), - body(['id_quota']) - .default(CONSTANTS.DEFAULT_ID_QUOTA) - .not() - .isArray() - .isInt({ min: CONSTANTS.MONGOOSE_VALIDATION.Org_policies_id_quota_min, max: CONSTANTS.MONGOOSE_VALIDATION.Org_policies_id_quota_max }) - .withMessage(errorMsgs.ID_QUOTA), - ...isNotAllowed('reports_to', 'name', 'users', '', 'in_use', 'created', 'last_updated', 'policies.id_quota') - ] - } else { - validations = [ - body(['short_name']).isString() - .trim() - .notEmpty() - .isLength({ min: CONSTANTS.MIN_SHORTNAME_LENGTH, max: CONSTANTS.MAX_SHORTNAME_LENGTH }), - body(['name']).isString() - .trim() - .notEmpty(), - body(['authority.active_roles']) - .default([CONSTANTS.AUTH_ROLE_ENUM.CNA]) - .custom(isFlatStringArray) - .customSanitizer(toUpperCaseArray) - .custom(isOrgRole), - body(['policies.id_quota']) - .default(CONSTANTS.DEFAULT_ID_QUOTA) - .not() - .isArray() - .isInt({ min: CONSTANTS.MONGOOSE_VALIDATION.Org_policies_id_quota_min, max: CONSTANTS.MONGOOSE_VALIDATION.Org_policies_id_quota_max }) - .withMessage(errorMsgs.ID_QUOTA), - ...isNotAllowed( - 'oversees', - 'long_name', - 'cve_program_org_function', - 'in_use', - 'created', - 'top_level_root', - 'aliases', - 'id_quota', - 'contact_info.phone', - 'contact_info.websites', - 'contact_info.emails', - 'contact_info', - 'users', - 'charter_or_scope', - 'disclosure_policy', - 'product_list', - 'contact_info.websites', - 'contact_info.emails', - 'contact_info.phone', - 'private_contacts', - 'partner_role_type', - 'partner_number', - 'partner_country', - 'program_data.cve_website_update_date', - 'program_data.cve_website_update_needed', - 'program_data.status', - 'advisory_locations', - 'advisory_location_require_credentials', - 'vulnerability_advisory_location_for_web_scraping', - 'industry', - 'tl_root_start_date', - 'is_cna_discussion_list') - ] - } - - const results = [] - for (const validation of validations) { - const result = await validation.run(req) - if (!result.isEmpty()) { - results.push(...result.errors) - } - } - if (results.length > 0) { - return res.status(400).json({ message: 'Parameters were invalid', details: results }) - } - next() - } -} - -function validateUserIdOrUsername () { - return async (req, res, next) => { - const useRegistry = req.query.registry === 'true' - const validations = [] - if (useRegistry) { - validations.push( - body('user_id') // Condition to run validation - .isString() - .trim() - .notEmpty(isValidUsername)) - } else { - validations.push(body('username').isString().trim().notEmpty(isValidUsername)) - } - const results = [] - for (const validation of validations) { - const result = await validation.run(req) - if (!result.isEmpty()) { - results.push(...result.errors) - } - } - if (results.length > 0) { - return res.status(400).json({ message: 'Parameters were invalid', details: results }) - } - next() - } -} - function validateUpdateOrgParameters () { return async (req, res, next) => { - const useRegistry = req.query.registry === 'true' const allowedParams = [...QUERY_PARAMETERS.shared] const registryParametersOnly = [...QUERY_PARAMETERS.registryOnly] @@ -218,43 +40,8 @@ function validateUpdateOrgParameters () { .custom(isFlatStringArray) .customSanitizer(toUpperCaseArray), // Path parameter validation - param(['shortname']).isString().trim().isLength({ min: CONSTANTS.MIN_SHORTNAME_LENGTH, max: CONSTANTS.MAX_SHORTNAME_LENGTH })] - if (useRegistry) { - validations.push( - query(['oversees']).optional().isArray(), - query(['top_level_root']).optional().isString(), - query([ - 'charter_or_scope', - 'disclosure_policy', - 'product_list', - 'contact_info.websites', - 'contact_info.emails', - 'contact_info.phone', - '', - '', - 'partner_role_type', - 'partner_number', - 'partner_country', - 'program_data.cve_website_update_date', - 'program_data.cve_website_update_needed', - 'program_data.status', - 'advisory_location_require_credentials', - 'vulnerability_advisory_location_for_web_scraping', - 'advisory_locations', - 'industry', - 'tl_root_start_date', - 'is_cna_discussion_list' - ]) - .optional() - .isString() - .trim() - ) - } else { - validations.push( - // Block registry-only parameters - ...isNotAllowedQuery(...registryParametersOnly) - ) - } + param(['shortname']).isString().trim().isLength({ min: CONSTANTS.MIN_SHORTNAME_LENGTH, max: CONSTANTS.MAX_SHORTNAME_LENGTH }), + ...isNotAllowedQuery(...registryParametersOnly)] const results = [] for (const validation of validations) { @@ -270,16 +57,6 @@ function validateUpdateOrgParameters () { } } -function isNotAllowed (...fields) { - return fields.map(field => - body(field) - .if((value, { req }) => _.has(req.body, field)) - .custom(() => { - throw new Error(`${field} must not be present`) - }) - ) -} - function isNotAllowedQuery (...fields) { return fields.map(field => query(field) @@ -417,8 +194,6 @@ module.exports = { isOrgRole, isUserRole, isValidUsername, - validateCreateOrgParameters, validateUpdateOrgParameters, - validateUserIdOrUsername, shortCircuitLegacyCpsMitreOrgParameters } diff --git a/src/controller/user.controller/index.js b/src/controller/user.controller/index.js index c7d4a73d8..ddbb44823 100644 --- a/src/controller/user.controller/index.js +++ b/src/controller/user.controller/index.js @@ -1,13 +1,10 @@ const express = require('express') const router = express.Router() const mw = require('../../middleware/middleware') -const { query, param } = require('express-validator') +const { query } = require('express-validator') const controller = require('./user.controller') const registryUserController = require('../registry-user.controller/registry-user.controller.js') const { parseGetParams, parseError } = require('./user.middleware') -// Only God and Javascript know why its saying it is not used when it is..... -// eslint-disable-next-line no-unused-vars -const { handleRegistryParameter } = require('../../middleware/middleware') const getConstants = require('../../constants').getConstants const CONSTANTS = getConstants() @@ -23,7 +20,6 @@ router.get('/registry/users',

Secretariat: Retrieves information about all users for all organizations

" #swagger.parameters['$ref'] = [ '#/components/parameters/pageQuery', - '#/components/parameters/registry', '#/components/parameters/apiEntityHeader', '#/components/parameters/apiUserHeader', '#/components/parameters/apiSecretHeader' @@ -102,7 +98,6 @@ router.get('/users',

Secretariat: Retrieves information about all users for all organizations

" #swagger.parameters['$ref'] = [ '#/components/parameters/pageQuery', - '#/components/parameters/registry', '#/components/parameters/apiEntityHeader', '#/components/parameters/apiUserHeader', '#/components/parameters/apiSecretHeader' @@ -112,10 +107,7 @@ router.get('/users', content:{ "application/json":{ schema: { - oneOf: [ - { $ref: '../schemas/user/list-users-response.json' }, - { $ref: '../schemas/registry-user/list-registry-users-response.json' } - ] + $ref: '../schemas/user/list-users-response.json' } } } @@ -161,12 +153,11 @@ router.get('/users', } } */ - param(['registry']).optional().isBoolean(), - mw.handleRegistryParameter, mw.validateUser, mw.onlySecretariat, + query().custom((query) => { return mw.validateQueryParameterNames(query, ['page']) }), query(['page']).optional().isInt({ min: CONSTANTS.PAGINATOR_PAGE }), - query(['page', 'registry']).custom((val) => { return mw.containsNoInvalidCharacters(val) }), + query(['page']).custom((val) => { return mw.containsNoInvalidCharacters(val) }), parseError, parseGetParams, controller.ALL_USERS) diff --git a/src/controller/user.controller/user.controller.js b/src/controller/user.controller/user.controller.js index 3d78e4a43..0b9fc31d5 100644 --- a/src/controller/user.controller/user.controller.js +++ b/src/controller/user.controller/user.controller.js @@ -3,6 +3,8 @@ require('dotenv').config() const logger = require('../../middleware/logger') const getConstants = require('../../constants').getConstants +const LEGACY_USER_OBJECT = false + /** * Get the details of all users * Called by GET /api/users @@ -22,7 +24,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) + const returnValue = await repo.getAllUsers(options, LEGACY_USER_OBJECT) 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/controller/user.controller/user.middleware.js b/src/controller/user.controller/user.middleware.js index e9477fb70..95a900313 100644 --- a/src/controller/user.controller/user.middleware.js +++ b/src/controller/user.controller/user.middleware.js @@ -4,7 +4,7 @@ const error = new errors.UserControllerError() const utils = require('../../utils/utils') function parseGetParams (req, res, next) { - utils.reqCtxMapping(req, 'query', ['page', 'registry']) + utils.reqCtxMapping(req, 'query', ['page']) next() } diff --git a/src/middleware/middleware.js b/src/middleware/middleware.js index aa32a66b6..29fd01910 100644 --- a/src/middleware/middleware.js +++ b/src/middleware/middleware.js @@ -96,11 +96,6 @@ async function optionallyValidateUser (req, res, next) { } } -const handleRegistryParameter = (req, res, next) => { - req.useRegistry = req.query.registry === 'true' - next() -} - const useRegistry = () => { return (req, res, next) => { req.useRegistry = true @@ -572,7 +567,6 @@ module.exports = { setCacheControl, optionallyValidateUser, validateUser, - handleRegistryParameter, useRegistry, onlySecretariat, onlySecretariatOrBulkDownload, diff --git a/src/swagger.js b/src/swagger.js index 6f491125f..4b18a646f 100644 --- a/src/swagger.js +++ b/src/swagger.js @@ -479,15 +479,6 @@ const doc = { minimum: 1 } }, - registry: { - in: 'query', - name: 'registry', - description: 'When set to true, the endpoint will expect request data to conform to the applicable User Registry schema, and will provide response data conforming to the applicable User Registry schema. Defaults to false.', - required: false, - schema: { - type: 'boolean' - } - }, short_name: { in: 'query', name: 'short_name', diff --git a/test/integration-tests/org/regularUsersTestRegistry.js b/test/integration-tests/org/regularUsersTestRegistry.js index bdcb58b80..252122f48 100644 --- a/test/integration-tests/org/regularUsersTestRegistry.js +++ b/test/integration-tests/org/regularUsersTestRegistry.js @@ -318,16 +318,6 @@ describe('Testing regular user permissions for /api/registry/org/ endpoints with expect(res).to.have.status(403) expect(res.body.error).to.contain('NOT_SAME_USER_OR_SECRETARIAT') }) - /* Commenting out since authority.active_roles are not returned in the GET request response for registry=true */ - // await chai.request(app) - // .get(`/api/org/${org}/user/${user}?registry=true`) - // .set(constants.nonSecretariatUserHeaders2) - // .send({ - // }) - // .then((res) => { - // expect(res).to.have.status(200) - // console.log(res.body) - // }) }) }) }) diff --git a/test/integration-tests/user/getUsersTest.js b/test/integration-tests/user/getUsersTest.js index 10f4a5401..ff140f85e 100644 --- a/test/integration-tests/user/getUsersTest.js +++ b/test/integration-tests/user/getUsersTest.js @@ -33,14 +33,12 @@ describe('Testing global user list endpoints', () => { }) }) - it('Should get all registry users from /users with registry query as Secretariat', async () => { + it('Should reject registry query parameter on /users', async () => { await chai.request(app) .get('/api/users?registry=true') .set(secretariatHeaders) .then((res) => { - 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).to.have.status(400) }) }) }) diff --git a/test/unit-tests/org/orgCreateTest.js b/test/unit-tests/org/orgCreateTest.js index bb0ed99dc..a2d28ff7a 100644 --- a/test/unit-tests/org/orgCreateTest.js +++ b/test/unit-tests/org/orgCreateTest.js @@ -178,8 +178,7 @@ describe('Testing the ORG_CREATE_SINGLE controller', () => { uuid: faker.datatype.uuid(), repositories: { getOrgRepository, getBaseOrgRepository, getUserRepository, getBaseUserRepository }, body: testOrgPayload - }, - query: { registry: 'false' } + } } await orgController.ORG_CREATE_SINGLE(req, res, next) From 9643c41d27161f0b0d4af4817467c9843290c234 Mon Sep 17 00:00:00 2001 From: david-rocca Date: Mon, 20 Jul 2026 16:22:54 -0400 Subject: [PATCH 04/11] Clean up of unused endpoints --- api-docs/openapi.json | 1211 ++++++++------ .../delete-registry-org-response.json | 17 + .../delete-registry-user-response.json | 17 + src/controller/org.controller/index.js | 1188 -------------- .../org.controller/org.controller.js | 4 +- .../org.controller/org.middleware.js | 6 + .../registry-org.controller/index.js | 552 ------- .../registry-org.middleware.js | 74 - .../registry-user.controller/index.js | 397 ----- .../registry-user.middleware.js | 42 - src/controller/registry.controller/index.js | 1430 +++++++++++++++++ .../org.error.js} | 1 + .../org.registry.controller.js} | 21 +- .../user.registry.controller.js} | 102 +- src/controller/user.controller/index.js | 79 - src/routes.config.js | 7 +- src/swagger.js | 3 +- .../registry-org/createUserByOrgTest.js | 12 +- .../registry-org/registryOrgCRUDTest.js | 18 +- .../registryOrgDiscriminatorAuthorityTest.js | 2 +- .../registryOrgWithJointReviewTest.js | 10 +- .../registry-org/verifyDeepRemoveEmpty.js | 4 +- .../registry-user/registryUserCRUDTest.js | 48 +- .../registry/registryRouteMigrationTest.js | 39 + .../review-object/reviewObjectTest.js | 2 +- .../controllerSessionCleanupTest.js | 47 +- .../org/registryOrgControllerTest.js | 2 +- .../org/registryOrgGetSingleTest.js | 2 +- .../registry-org/registryOrgPayloadTest.js | 4 +- .../user/registryUserControllerTest.js | 134 +- 30 files changed, 2308 insertions(+), 3167 deletions(-) create mode 100644 schemas/registry-org/delete-registry-org-response.json create mode 100644 schemas/registry-user/delete-registry-user-response.json delete mode 100644 src/controller/registry-org.controller/index.js delete mode 100644 src/controller/registry-org.controller/registry-org.middleware.js delete mode 100644 src/controller/registry-user.controller/index.js delete mode 100644 src/controller/registry-user.controller/registry-user.middleware.js create mode 100644 src/controller/registry.controller/index.js rename src/controller/{registry-org.controller/error.js => registry.controller/org.error.js} (98%) rename src/controller/{registry-org.controller/registry-org.controller.js => registry.controller/org.registry.controller.js} (98%) rename src/controller/{registry-user.controller/registry-user.controller.js => registry.controller/user.registry.controller.js} (87%) create mode 100644 test/integration-tests/registry/registryRouteMigrationTest.js diff --git a/api-docs/openapi.json b/api-docs/openapi.json index d925207de..efd583b28 100644 --- a/api-docs/openapi.json +++ b/api-docs/openapi.json @@ -11,7 +11,7 @@ }, "servers": [ { - "url": "urlplaceholder" + "url": "https://cveawg-dev.mitre.org/api" } ], "paths": { @@ -1893,14 +1893,14 @@ } } }, - "/registry/org": { + "/org": { "get": { "tags": [ - "Registry Organization" + "Organization" ], - "summary": "Retrieves all registry organizations (accessible to Secretariat)", - "description": "

Access Control

User must belong to an organization with the Secretariat role

Expected Behavior

Secretariat: Retrieves information about all registry organizations

", - "operationId": "registryOrgAll", + "summary": "Retrieves all organizations (accessible to Secretariat)", + "description": "

Access Control

User must belong to an organization with the Secretariat role

Expected Behavior

Secretariat: Retrieves information about all organizations

", + "operationId": "orgAll", "parameters": [ { "$ref": "#/components/parameters/pageQuery" @@ -1917,11 +1917,18 @@ ], "responses": { "200": { - "description": "Returns information about all registry organizations, along with pagination fields if results span multiple pages of data", + "description": "Returns information about all organizations, along with pagination fields if results span multiple pages of data", "content": { "application/json": { "schema": { - "$ref": "../schemas/registry-org/list-registry-orgs-response.json" + "oneOf": [ + { + "$ref": "../schemas/org/list-orgs-response.json" + }, + { + "$ref": "../schemas/registry-org/list-registry-orgs-response.json" + } + ] } } } @@ -1980,10 +1987,10 @@ }, "post": { "tags": [ - "Registry Organization" + "Organization" ], - "summary": "Creates an organization (accessible to Secretariat)", - "description": "

Access Control

User must belong to an organization with the Secretariat role

Expected Behavior

Secretariat: Creates a new organization

", + "summary": "Creates an organization as specified in the request body (accessible to Secretariat)", + "description": "

Access Control

User must belong to an organization with the Secretariat role

Expected Behavior

Secretariat: Creates an organization

", "operationId": "orgCreateSingle", "parameters": [ { @@ -1998,11 +2005,18 @@ ], "responses": { "200": { - "description": "Returns information about all organizations, along with pagination fields if results span multiple pages of data", + "description": "Returns information about the organization created", "content": { "application/json": { "schema": { - "$ref": "../schemas/registry-org/list-registry-orgs-response.json" + "oneOf": [ + { + "$ref": "../schemas/org/create-org-response.json" + }, + { + "$ref": "../schemas/registry-org/create-registry-org-response.json" + } + ] } } } @@ -2063,54 +2077,30 @@ "content": { "application/json": { "schema": { - "anyOf": [ - { - "$ref": "../schemas/registry-org/SecretariatOrg.json" - }, - { - "$ref": "../schemas/registry-org/CNAOrg.json" - }, - { - "$ref": "../schemas/registry-org/ADPOrg.json" - }, - { - "$ref": "../schemas/registry-org/BulkDownloadOrg.json" - } - ] - }, - "example": { - "short_name": "fake_company", - "long_name": "Fake Company", - "id_quota": 1000, - "authority": [ - "CNA" - ] + "$ref": "../schemas/org/create-org-request.json" } } } } } }, - "/registry/org/{shortname}/users": { + "/org/{identifier}": { "get": { "tags": [ - "Registry User" + "Organization" ], - "summary": "Retrieves all users for the organization with the specified short name (accessible to same-organization users or Secretariat)", - "description": "

Access Control

Authenticated users can access this endpoint only for their own organization. Secretariat users can access any organization.

Expected Behavior

Regular, CNA & Admin Users: Retrieves information about users in the same organization

Secretariat: Retrieves all user information for any organization

", - "operationId": "userOrgAll", + "summary": "Retrieves information about the organization specified by short name or UUID (accessible to same-organization users or Secretariat)", + "description": "

Access Control

Authenticated users can access this endpoint only for their own organization. Secretariat users can access any organization.

Expected Behavior

Regular, CNA & Admin Users: Retrieves organization record for the specified shortname or UUID if it is the user's organization

Secretariat: Retrieves information about any organization

", + "operationId": "orgSingle", "parameters": [ { - "name": "shortname", + "name": "identifier", "in": "path", "required": true, "schema": { "type": "string" }, - "description": "The shortname of the organization" - }, - { - "$ref": "#/components/parameters/pageQuery" + "description": "The shortname or UUID of the organization" }, { "$ref": "#/components/parameters/apiEntityHeader" @@ -2124,33 +2114,11 @@ ], "responses": { "200": { - "description": "Returns all users for the organization, along with pagination fields if results span multiple pages of data", + "description": "Returns the organization information", "content": { "application/json": { "schema": { - "$ref": "../schemas/registry-user/list-registry-users-response.json" - }, - "example": { - "totalCount": 1, - "itemsPerPage": 100, - "pageCount": 1, - "currentPage": 1, - "prevPage": null, - "nextPage": null, - "users": [ - { - "UUID": "fe566221-6a2c-4279-8800-4d3795325997", - "username": "jdoe", - "name": { - "first": "John", - "last": "Doe" - }, - "role": "ADMIN", - "status": "active", - "created": "2021-02-12T17:15:37.382Z", - "last_updated": "2021-02-12T17:15:37.382Z" - } - ] + "$ref": "../schemas/org/get-org-response.json" } } } @@ -2208,14 +2176,14 @@ } } }, - "/registry/org/{shortname}/id_quota": { - "get": { + "/org/{shortname}": { + "put": { "tags": [ - "Registry Organization" + "Organization" ], - "summary": "Retrieves an organization's CVE ID quota (accessible to same-organization users or Secretariat)", - "description": "

Access Control

Authenticated users can access this endpoint only for their own organization. Secretariat users can access any organization.

Expected Behavior

Regular, CNA & Admin Users: Retrieves the CVE ID quota for the user's organization

Secretariat: Retrieves the CVE ID quota for any organization

", - "operationId": "orgIdQuota", + "summary": "Updates information about the organization specified by short name (accessible to Secretariat)", + "description": "

Access Control

User must belong to an organization with the Secretariat role

Expected Behavior

Secretariat: Updates any organization's information

", + "operationId": "orgUpdateSingle", "parameters": [ { "name": "shortname", @@ -2226,6 +2194,21 @@ }, "description": "The shortname of the organization" }, + { + "$ref": "#/components/parameters/id_quota" + }, + { + "$ref": "#/components/parameters/name" + }, + { + "$ref": "#/components/parameters/newShortname" + }, + { + "$ref": "#/components/parameters/active_roles_add" + }, + { + "$ref": "#/components/parameters/active_roles_remove" + }, { "$ref": "#/components/parameters/apiEntityHeader" }, @@ -2238,11 +2221,11 @@ ], "responses": { "200": { - "description": "Returns the CVE ID quota for an organization", + "description": "Returns information about the organization updated", "content": { "application/json": { "schema": { - "$ref": "../schemas/registry-org/get-registry-org-quota-response.json" + "$ref": "../schemas/org/update-org-response.json" } } } @@ -2300,47 +2283,23 @@ } } }, - "/registry/org/{identifier}": { + "/org/{shortname}/id_quota": { "get": { "tags": [ - "Registry Organization" + "Organization" ], - "summary": "Retrieves information about the registry organization specified by short name or UUID (accessible to same-organization users or Secretariat)", - "description": "

Access Control

Authenticated users can access this endpoint only for their own organization. Secretariat users can access any organization.

Expected Behavior

Regular, CNA & Admin Users: Retrieves registry organization record for the specified shortname or UUID if it is the user's organization

Secretariat: Retrieves information about any registry organization

", - "operationId": "registryOrgSingle", + "summary": "Retrieves an organization's CVE ID quota (accessible to same-organization users or Secretariat)", + "description": "

Access Control

Authenticated users can access this endpoint only for their own organization. Secretariat users can access any organization.

Expected Behavior

Regular, CNA & Admin Users: Retrieves the CVE ID quota for the user's organization

Secretariat: Retrieves the CVE ID quota for any organization

", + "operationId": "orgIdQuota", "parameters": [ { - "name": "identifier", + "name": "shortname", "in": "path", "required": true, "schema": { "type": "string" }, - "description": "The shortname or UUID of the registry organization" - }, - { - "name": "expand", - "in": "query", - "description": "Optional expanded related data. Accepted value: users.", - "required": false, - "schema": { - "type": "object", - "properties": { - "type": { - "type": "string", - "example": "string" - }, - "enum": { - "type": "array", - "example": [ - "users" - ], - "items": { - "type": "string" - } - } - } - } + "description": "The shortname of the organization" }, { "$ref": "#/components/parameters/apiEntityHeader" @@ -2354,11 +2313,11 @@ ], "responses": { "200": { - "description": "Returns the registry organization information", + "description": "Returns the CVE ID quota for an organization", "content": { "application/json": { "schema": { - "$ref": "../schemas/registry-org/get-registry-org-response.json" + "$ref": "../schemas/org/get-org-quota-response.json" } } } @@ -2416,14 +2375,14 @@ } } }, - "/registry/org/{shortname}/user/{username}": { + "/org/{shortname}/users": { "get": { "tags": [ - "Registry User" + "Users" ], - "summary": "Retrieves information about a user for the specified username and organization short name (accessible to same-organization users or Secretariat)", - "description": "

Access Control

Authenticated users can access this endpoint only for users in their own organization. Secretariat users can access any user.

Expected Behavior

Regular, CNA & Admin Users: Retrieves information about a registry user in the same organization

Secretariat: Retrieves any registry user's information

", - "operationId": "registryUserSingle", + "summary": "Retrieves all users for the organization with the specified short name (accessible to same-organization users or Secretariat)", + "description": "

Access Control

Authenticated users can access this endpoint only for their own organization. Secretariat users can access any organization.

Expected Behavior

Regular, CNA & Admin Users: Retrieves information about users in the same organization

Secretariat: Retrieves all user information for any organization

", + "operationId": "userOrgAll", "parameters": [ { "name": "shortname", @@ -2431,15 +2390,11 @@ "required": true, "schema": { "type": "string" - } + }, + "description": "The shortname of the organization" }, { - "name": "username", - "in": "path", - "required": true, - "schema": { - "type": "string" - } + "$ref": "#/components/parameters/pageQuery" }, { "$ref": "#/components/parameters/apiEntityHeader" @@ -2453,11 +2408,11 @@ ], "responses": { "200": { - "description": "Returns information about the specified registry user", + "description": "Returns all users for the organization, along with pagination fields if results span multiple pages of data", "content": { "application/json": { "schema": { - "$ref": "../schemas/registry-user/get-registry-user-response.json" + "$ref": "../schemas/user/list-users-response.json" } } } @@ -2513,14 +2468,16 @@ } } } - }, - "put": { + } + }, + "/org/{shortname}/user": { + "post": { "tags": [ - "Registry User" + "Users" ], - "summary": "Updates information about a user for the specified username and organization shortname (accessible to self, same-organization Admins, or Secretariat)", - "description": "

Access Control

Authenticated users can update their own name fields. Organization admins can update users in their organization. Secretariat users can update users in any organization.

Expected Behavior

Regular User: Updates the user's own information. Only name fields may be changed.

Admin User: Updates information about a user in the Admin's organization. Allowed to change all fields except org_short_name.

Secretariat: Updates information about a user in any organization. Allowed to change all fields.

", - "operationId": "registryUserUpdateSingle", + "summary": "Create a user with the provided short name as the owning organization (accessible to Secretariat or target organization Admin)", + "description": "

Access Control

User must belong to an organization with the Secretariat role or be an Admin of the target organization

Expected Behavior

Admin User: Creates a user for the Admin's organization

Secretariat: Creates a user for any organization

", + "operationId": "userCreateSingle", "parameters": [ { "name": "shortname", @@ -2531,21 +2488,6 @@ }, "description": "The shortname of the organization" }, - { - "name": "username", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "The username of the user" - }, - { - "$ref": "#/components/parameters/active" - }, - { - "$ref": "#/components/parameters/orgShortname" - }, { "$ref": "#/components/parameters/apiEntityHeader" }, @@ -2558,25 +2500,11 @@ ], "responses": { "200": { - "description": "Returns the updated user information", + "description": "Returns the new user information (with the secret)", "content": { "application/json": { "schema": { - "$ref": "../schemas/registry-user/update-registry-user-response.json" - }, - "example": { - "message": "jdoe was successfully updated.", - "updated": { - "UUID": "fe566221-6a2c-4279-8800-4d3795325997", - "username": "jdoe", - "name": { - "first": "John", - "last": "Doe" - }, - "status": "active", - "created": "2021-02-12T17:15:37.382Z", - "last_updated": "2021-02-12T17:15:37.382Z" - } + "$ref": "../schemas/user/create-user-response.json" } } } @@ -2631,17 +2559,27 @@ } } } - } - } - }, - "/registry/org/{shortname}": { - "put": { - "tags": [ - "Registry Organization" - ], - "summary": "Updates information about the organization specified by short name (accessible to Secretariat or same-organization Admin)", - "description": "

Access Control

User must belong to an organization with the Secretariat role or be an Admin of the requested organization.

With Joint Approval required for the following fields:

Expected Behavior

This endpoint expects a full organization object in the request body.

Secretariat: Updates any organization's information

Organization Admin: Requests changes to its organization's information

  • short_name
  • long_name
  • authority
  • aliases
  • oversees
  • top_level_root
  • charter_or_scope
  • product_list
  • disclosure_policy
  • contact_info.websites
  • contact_info.emails
  • contact_info.phone
  • partner_role_type
  • partner_country
  • advisory_locations
  • advisory_location_require_credentials
  • vulnerability_advisory_location_for_web_scraping
  • industry
  • tl_root_start_date
  • is_cna_discussion_list
", - "operationId": "orgUpdateSingle", + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "../schemas/user/create-user-request.json" + } + } + } + } + } + }, + "/org/{shortname}/user/{username}": { + "get": { + "tags": [ + "Users" + ], + "summary": "Retrieves information about a user for the specified username and organization short name (accessible to same-organization users or Secretariat)", + "description": "

Access Control

Authenticated users can access this endpoint only for users in their own organization. Secretariat users can access any user.

Expected Behavior

Regular, CNA & Admin Users: Retrieves information about a user in the same organization

Secretariat: Retrieves any user's information

", + "operationId": "userSingle", "parameters": [ { "name": "shortname", @@ -2652,6 +2590,15 @@ }, "description": "The shortname of the organization" }, + { + "name": "username", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The username of the user" + }, { "$ref": "#/components/parameters/apiEntityHeader" }, @@ -2664,11 +2611,11 @@ ], "responses": { "200": { - "description": "Returns information about the organization updated", + "description": "Returns information about the specified user", "content": { "application/json": { "schema": { - "$ref": "../schemas/registry-org/update-registry-org-response.json" + "$ref": "../schemas/user/get-user-response.json" } } } @@ -2723,35 +2670,15 @@ } } } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "../schemas/registry-org/update-registry-org-request.json" - }, - "example": { - "short_name": "fake_company", - "long_name": "Fake Company", - "id_quota": 1000, - "authority": [ - "CNA" - ] - } - } - } } - } - }, - "/registry/org/{shortname}/user": { - "post": { + }, + "put": { "tags": [ - "Registry User" + "Users" ], - "summary": "Create a user with the provided short name as the owning organization (accessible to Secretariat or target organization Admin)", - "description": "

Access Control

User must belong to an organization with the Secretariat role or be an Admin of the target organization

Expected Behavior

Admin User: Creates a user for the Admin's organization

Secretariat: Creates a user for any organization

", - "operationId": "registryUserCreateSingle", + "summary": "Updates information about a user for the specified username and organization shortname (accessible to self, same-organization Admins, or Secretariat)", + "description": "

Access Control

Authenticated users can update their own name fields. Organization admins can update users in their organization. Secretariat users can update users in any organization.

Expected Behavior

Regular User: Updates the user's own information. Only name fields may be changed.

Admin User: Updates information about a user in the Admin's organization. Allowed to change all fields except org_short_name.

Secretariat: Updates information about a user in any organization. Allowed to change all fields.

", + "operationId": "userUpdateSingle", "parameters": [ { "name": "shortname", @@ -2762,6 +2689,42 @@ }, "description": "The shortname of the organization" }, + { + "name": "username", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The username of the user" + }, + { + "$ref": "#/components/parameters/active" + }, + { + "$ref": "#/components/parameters/activeUserRolesAdd" + }, + { + "$ref": "#/components/parameters/activeUserRolesRemove" + }, + { + "$ref": "#/components/parameters/nameFirst" + }, + { + "$ref": "#/components/parameters/nameLast" + }, + { + "$ref": "#/components/parameters/nameMiddle" + }, + { + "$ref": "#/components/parameters/nameSuffix" + }, + { + "$ref": "#/components/parameters/newUsername" + }, + { + "$ref": "#/components/parameters/orgShortname" + }, { "$ref": "#/components/parameters/apiEntityHeader" }, @@ -2774,26 +2737,11 @@ ], "responses": { "200": { - "description": "Returns the new user information (with the secret)", + "description": "Returns the updated user information", "content": { "application/json": { "schema": { - "$ref": "../schemas/registry-user/create-registry-user-response.json" - } - }, - "example": { - "message": "jdoe was successfully created.", - "created": { - "UUID": "fe566221-6a2c-4279-8800-4d3795325997", - "username": "jdoe", - "name": { - "first": "John", - "last": "Doe" - }, - "status": "active", - "secret": "12345-abcde-67890", - "created": "2021-02-12T17:15:37.382Z", - "last_updated": "2021-02-12T17:15:37.382Z" + "$ref": "../schemas/user/update-user-response.json" } } } @@ -2848,31 +2796,13 @@ } } } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "../schemas/registry-user/create-registry-user-request.json" - } - }, - "example": { - "username": "jdoe", - "status": "active", - "name": { - "first": "John", - "last": "Doe" - } - } - } } } }, - "/registry/org/{shortname}/user/{username}/reset_secret": { + "/org/{shortname}/user/{username}/reset_secret": { "put": { "tags": [ - "Registry User" + "Users" ], "summary": "Reset the API key for a user (accessible to self, same-organization Admins, or Secretariat)", "description": "

Access Control

Authenticated users can reset their own API secret. Organization admins can reset users in their organization. Secretariat users can reset any user's API secret.

Expected Behavior

Regular User: Resets user's own API secret

Admin User: Resets any user's API secret in the Admin's organization

Secretariat: Resets any user's API secret

", @@ -2970,32 +2900,118 @@ } } }, - "/registry/org/{shortname}/user/{username}/grant-role": { - "post": { + "/users": { + "get": { "tags": [ - "Registry User" + "Users" ], - "summary": "Grants a role to a user (accessible to Secretariat or Org Admin)", - "description": "

Access Control

User must belong to an organization with the Secretariat role or be an Admin of the target organization

Expected Behavior

Admin User: Grants a role to a user in the Admin's organization

Secretariat: Grants a role to a user in any organization

", - "operationId": "registryUserGrantRole", + "summary": "Retrieves information about all registered users (accessible to Secretariat)", + "description": "

Access Control

User must belong to an organization with the Secretariat role

Expected Behavior

Secretariat: Retrieves information about all users for all organizations

", + "operationId": "userAll", "parameters": [ { - "name": "shortname", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "The shortname of the organization" + "$ref": "#/components/parameters/pageQuery" }, { - "name": "username", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "The username of the user" + "$ref": "#/components/parameters/apiEntityHeader" + }, + { + "$ref": "#/components/parameters/apiUserHeader" + }, + { + "$ref": "#/components/parameters/apiSecretHeader" + } + ], + "responses": { + "200": { + "description": "Returns all users, along with pagination fields if results span multiple pages of data.", + "content": { + "application/json": { + "schema": { + "$ref": "../schemas/user/list-users-response.json" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "../schemas/errors/bad-request.json" + } + } + } + }, + "401": { + "description": "Not Authenticated", + "content": { + "application/json": { + "schema": { + "$ref": "../schemas/errors/generic.json" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "../schemas/errors/generic.json" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "../schemas/errors/generic.json" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "../schemas/errors/generic.json" + } + } + } + } + } + } + }, + "/health-check": { + "get": { + "tags": [ + "Utilities" + ], + "summary": "Checks that the system is running (accessible to all users)", + "description": "

Access Control

Endpoint is accessible to all

Expected Behavior

Returns a 200 response code when CVE Services are running

", + "operationId": "healthCheck", + "responses": { + "200": { + "description": "Returns a 200 response code" + } + } + } + }, + "/registry/org": { + "get": { + "tags": [ + "Registry Organization" + ], + "summary": "Retrieves all registry organizations (accessible to Secretariat)", + "description": "

Access Control

User must belong to an organization with the Secretariat role

Expected Behavior

Secretariat: Retrieves information about all registry organizations

", + "operationId": "registryOrgAll", + "parameters": [ + { + "$ref": "#/components/parameters/pageQuery" }, { "$ref": "#/components/parameters/apiEntityHeader" @@ -3009,16 +3025,92 @@ ], "responses": { "200": { - "description": "Role granted successfully", + "description": "Returns information about all registry organizations, along with pagination fields if results span multiple pages of data", "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "message": { - "type": "string" - } - } + "$ref": "../schemas/registry-org/list-registry-orgs-response.json" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "../schemas/errors/bad-request.json" + } + } + } + }, + "401": { + "description": "Not Authenticated", + "content": { + "application/json": { + "schema": { + "$ref": "../schemas/errors/generic.json" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "../schemas/errors/generic.json" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "../schemas/errors/generic.json" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "../schemas/errors/generic.json" + } + } + } + } + } + }, + "post": { + "tags": [ + "Registry Organization" + ], + "summary": "Creates an organization (accessible to Secretariat)", + "description": "

Access Control

User must belong to an organization with the Secretariat role

Expected Behavior

Secretariat: Creates a new organization

", + "operationId": "registryOrgCreateSingle", + "parameters": [ + { + "$ref": "#/components/parameters/apiEntityHeader" + }, + { + "$ref": "#/components/parameters/apiUserHeader" + }, + { + "$ref": "#/components/parameters/apiSecretHeader" + } + ], + "responses": { + "200": { + "description": "Returns information about all organizations, along with pagination fields if results span multiple pages of data", + "content": { + "application/json": { + "schema": { + "$ref": "../schemas/registry-org/list-registry-orgs-response.json" } } } @@ -3079,17 +3171,27 @@ "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "role": { - "type": "string", - "enum": [ - "ADMIN" - ] + "anyOf": [ + { + "$ref": "../schemas/registry-org/SecretariatOrg.json" + }, + { + "$ref": "../schemas/registry-org/CNAOrg.json" + }, + { + "$ref": "../schemas/registry-org/ADPOrg.json" + }, + { + "$ref": "../schemas/registry-org/BulkDownloadOrg.json" } - }, - "required": [ - "role" + ] + }, + "example": { + "short_name": "fake_company", + "long_name": "Fake Company", + "id_quota": 1000, + "authority": [ + "CNA" ] } } @@ -3097,14 +3199,14 @@ } } }, - "/registry/org/{shortname}/user/{username}/revoke-role": { - "post": { + "/registry/org/{shortname}/users": { + "get": { "tags": [ "Registry User" ], - "summary": "Revokes a role from a user (accessible to Secretariat or Org Admin)", - "description": "

Access Control

User must belong to an organization with the Secretariat role or be an Admin of the target organization

Expected Behavior

Admin User: Revokes a role from a user in the Admin's organization

Secretariat: Revokes a role from a user in any organization

", - "operationId": "registryUserRevokeRole", + "summary": "Retrieves all users for the organization with the specified short name (accessible to same-organization users or Secretariat)", + "description": "

Access Control

Authenticated users can access this endpoint only for their own organization. Secretariat users can access any organization.

Expected Behavior

Regular, CNA & Admin Users: Retrieves information about users in the same organization

Secretariat: Retrieves all user information for any organization

", + "operationId": "registryOrgUsersAll", "parameters": [ { "name": "shortname", @@ -3116,13 +3218,7 @@ "description": "The shortname of the organization" }, { - "name": "username", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "The username of the user" + "$ref": "#/components/parameters/pageQuery" }, { "$ref": "#/components/parameters/apiEntityHeader" @@ -3136,16 +3232,33 @@ ], "responses": { "200": { - "description": "Role revoked successfully", + "description": "Returns all users for the organization, along with pagination fields if results span multiple pages of data", "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "message": { - "type": "string" + "$ref": "../schemas/registry-user/list-registry-users-response.json" + }, + "example": { + "totalCount": 1, + "itemsPerPage": 100, + "pageCount": 1, + "currentPage": 1, + "prevPage": null, + "nextPage": null, + "users": [ + { + "UUID": "fe566221-6a2c-4279-8800-4d3795325997", + "username": "jdoe", + "name": { + "first": "John", + "last": "Doe" + }, + "role": "ADMIN", + "status": "active", + "created": "2021-02-12T17:15:37.382Z", + "last_updated": "2021-02-12T17:15:37.382Z" } - } + ] } } } @@ -3200,38 +3313,17 @@ } } } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "role": { - "type": "string", - "enum": [ - "ADMIN" - ] - } - }, - "required": [ - "role" - ] - } - } - } } } }, - "/registry/org/{shortname}/conversation/{index}": { - "put": { + "/registry/org/{shortname}/id_quota": { + "get": { "tags": [ "Registry Organization" ], - "summary": "Update the conversation at the given index for the given organization (accessible to Secretariat or original same-organization author)", - "description": "

Access Control

User must belong to an organization with the Secretariat role or be the original author of the conversation in the same organization

Expected Behavior

Original Author: Allowed to update only the message body of a conversation posted by them

Secretariat: Allowed to update the message body and/or visibility of any conversation

", - "operationId": "registryUserUpdateConversation", + "summary": "Retrieves an organization's CVE ID quota (accessible to same-organization users or Secretariat)", + "description": "

Access Control

Authenticated users can access this endpoint only for their own organization. Secretariat users can access any organization.

Expected Behavior

Regular, CNA & Admin Users: Retrieves the CVE ID quota for the user's organization

Secretariat: Retrieves the CVE ID quota for any organization

", + "operationId": "registryOrgIdQuota", "parameters": [ { "name": "shortname", @@ -3242,15 +3334,6 @@ }, "description": "The shortname of the organization" }, - { - "name": "index", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "The index of the conversation to update" - }, { "$ref": "#/components/parameters/apiEntityHeader" }, @@ -3263,11 +3346,11 @@ ], "responses": { "200": { - "description": "Returns the updated conversation", + "description": "Returns the CVE ID quota for an organization", "content": { "application/json": { "schema": { - "$ref": "../schemas/conversation/update-conversation-response.json" + "$ref": "../schemas/registry-org/get-registry-org-quota-response.json" } } } @@ -3325,17 +3408,47 @@ } } }, - "/org": { + "/registry/org/{identifier}": { "get": { "tags": [ - "Organization" + "Registry Organization" ], - "summary": "Retrieves all organizations (accessible to Secretariat)", - "description": "

Access Control

User must belong to an organization with the Secretariat role

Expected Behavior

Secretariat: Retrieves information about all organizations

", - "operationId": "orgAll", + "summary": "Retrieves information about the registry organization specified by short name or UUID (accessible to same-organization users or Secretariat)", + "description": "

Access Control

Authenticated users can access this endpoint only for their own organization. Secretariat users can access any organization.

Expected Behavior

Regular, CNA & Admin Users: Retrieves registry organization record for the specified shortname or UUID if it is the user's organization

Secretariat: Retrieves information about any registry organization

", + "operationId": "registryOrgSingle", "parameters": [ { - "$ref": "#/components/parameters/pageQuery" + "name": "identifier", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The shortname or UUID of the registry organization" + }, + { + "name": "expand", + "in": "query", + "description": "Optional expanded related data. Accepted value: users.", + "required": false, + "schema": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "string" + }, + "enum": { + "type": "array", + "example": [ + "users" + ], + "items": { + "type": "string" + } + } + } + } }, { "$ref": "#/components/parameters/apiEntityHeader" @@ -3349,18 +3462,11 @@ ], "responses": { "200": { - "description": "Returns information about all organizations, along with pagination fields if results span multiple pages of data", + "description": "Returns the registry organization information", "content": { "application/json": { "schema": { - "oneOf": [ - { - "$ref": "../schemas/org/list-orgs-response.json" - }, - { - "$ref": "../schemas/registry-org/list-registry-orgs-response.json" - } - ] + "$ref": "../schemas/registry-org/get-registry-org-response.json" } } } @@ -3416,15 +3522,33 @@ } } } - }, - "post": { + } + }, + "/registry/org/{shortname}/user/{username}": { + "get": { "tags": [ - "Organization" + "Registry User" ], - "summary": "Creates an organization as specified in the request body (accessible to Secretariat)", - "description": "

Access Control

User must belong to an organization with the Secretariat role

Expected Behavior

Secretariat: Creates an organization

", - "operationId": "orgCreateSingle", + "summary": "Retrieves information about a user for the specified username and organization short name (accessible to same-organization users or Secretariat)", + "description": "

Access Control

Authenticated users can access this endpoint only for users in their own organization. Secretariat users can access any user.

Expected Behavior

Regular, CNA & Admin Users: Retrieves information about a registry user in the same organization

Secretariat: Retrieves any registry user's information

", + "operationId": "registryUserSingle", "parameters": [ + { + "name": "shortname", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "username", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, { "$ref": "#/components/parameters/apiEntityHeader" }, @@ -3437,18 +3561,11 @@ ], "responses": { "200": { - "description": "Returns information about the organization created", + "description": "Returns information about the specified registry user", "content": { "application/json": { "schema": { - "oneOf": [ - { - "$ref": "../schemas/org/create-org-response.json" - }, - { - "$ref": "../schemas/registry-org/create-registry-org-response.json" - } - ] + "$ref": "../schemas/registry-user/get-registry-user-response.json" } } } @@ -3503,36 +3620,39 @@ } } } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "../schemas/org/create-org-request.json" - } - } - } } - } - }, - "/org/{identifier}": { - "get": { + }, + "put": { "tags": [ - "Organization" + "Registry User" ], - "summary": "Retrieves information about the organization specified by short name or UUID (accessible to same-organization users or Secretariat)", - "description": "

Access Control

Authenticated users can access this endpoint only for their own organization. Secretariat users can access any organization.

Expected Behavior

Regular, CNA & Admin Users: Retrieves organization record for the specified shortname or UUID if it is the user's organization

Secretariat: Retrieves information about any organization

", - "operationId": "orgSingle", + "summary": "Updates information about a user for the specified username and organization shortname (accessible to self, same-organization Admins, or Secretariat)", + "description": "

Access Control

Authenticated users can update their own name fields. Organization admins can update users in their organization. Secretariat users can update users in any organization.

Expected Behavior

Regular User: Updates the user's own information. Only name fields may be changed.

Admin User: Updates information about a user in the Admin's organization. Allowed to change all fields except org_short_name.

Secretariat: Updates information about a user in any organization. Allowed to change all fields.

", + "operationId": "registryUserUpdateSingle", "parameters": [ { - "name": "identifier", + "name": "shortname", "in": "path", "required": true, "schema": { "type": "string" }, - "description": "The shortname or UUID of the organization" + "description": "The shortname of the organization" + }, + { + "name": "username", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The username of the user" + }, + { + "$ref": "#/components/parameters/active" + }, + { + "$ref": "#/components/parameters/orgShortname" }, { "$ref": "#/components/parameters/apiEntityHeader" @@ -3546,11 +3666,25 @@ ], "responses": { "200": { - "description": "Returns the organization information", + "description": "Returns the updated user information", "content": { "application/json": { "schema": { - "$ref": "../schemas/org/get-org-response.json" + "$ref": "../schemas/registry-user/update-registry-user-response.json" + }, + "example": { + "message": "jdoe was successfully updated.", + "updated": { + "UUID": "fe566221-6a2c-4279-8800-4d3795325997", + "username": "jdoe", + "name": { + "first": "John", + "last": "Doe" + }, + "status": "active", + "created": "2021-02-12T17:15:37.382Z", + "last_updated": "2021-02-12T17:15:37.382Z" + } } } } @@ -3606,16 +3740,14 @@ } } } - } - }, - "/org/{shortname}": { - "put": { + }, + "delete": { "tags": [ - "Organization" + "Registry User" ], - "summary": "Updates information about the organization specified by short name (accessible to Secretariat)", - "description": "

Access Control

User must belong to an organization with the Secretariat role

Expected Behavior

Secretariat: Updates any organization's information

", - "operationId": "orgUpdateSingle", + "summary": "Deletes the registry user specified by organization and username (accessible to Secretariat only)", + "description": "

Access Control

User must belong to an organization with the Secretariat role

Expected Behavior

Secretariat: Deletes the specified user from the specified organization

", + "operationId": "registryUserDeleteSingle", "parameters": [ { "name": "shortname", @@ -3627,19 +3759,13 @@ "description": "The shortname of the organization" }, { - "$ref": "#/components/parameters/id_quota" - }, - { - "$ref": "#/components/parameters/name" - }, - { - "$ref": "#/components/parameters/newShortname" - }, - { - "$ref": "#/components/parameters/active_roles_add" - }, - { - "$ref": "#/components/parameters/active_roles_remove" + "name": "username", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The username of the user" }, { "$ref": "#/components/parameters/apiEntityHeader" @@ -3653,11 +3779,11 @@ ], "responses": { "200": { - "description": "Returns information about the organization updated", + "description": "Confirms deletion of the registry user", "content": { "application/json": { "schema": { - "$ref": "../schemas/org/update-org-response.json" + "$ref": "../schemas/registry-user/delete-registry-user-response.json" } } } @@ -3715,14 +3841,14 @@ } } }, - "/org/{shortname}/id_quota": { - "get": { + "/registry/org/{shortname}": { + "put": { "tags": [ - "Organization" - ], - "summary": "Retrieves an organization's CVE ID quota (accessible to same-organization users or Secretariat)", - "description": "

Access Control

Authenticated users can access this endpoint only for their own organization. Secretariat users can access any organization.

Expected Behavior

Regular, CNA & Admin Users: Retrieves the CVE ID quota for the user's organization

Secretariat: Retrieves the CVE ID quota for any organization

", - "operationId": "orgIdQuota", + "Registry Organization" + ], + "summary": "Updates information about the organization specified by short name (accessible to Secretariat or same-organization Admin)", + "description": "

Access Control

User must belong to an organization with the Secretariat role or be an Admin of the requested organization.

With Joint Approval required for the following fields:

Expected Behavior

This endpoint expects a full organization object in the request body.

Secretariat: Updates any organization's information

Organization Admin: Requests changes to its organization's information

  • short_name
  • long_name
  • authority
  • aliases
  • oversees
  • top_level_root
  • charter_or_scope
  • product_list
  • disclosure_policy
  • contact_info.websites
  • contact_info.emails
  • contact_info.phone
  • partner_role_type
  • partner_country
  • advisory_locations
  • advisory_location_require_credentials
  • vulnerability_advisory_location_for_web_scraping
  • industry
  • tl_root_start_date
  • is_cna_discussion_list
", + "operationId": "registryOrgUpdateSingle", "parameters": [ { "name": "shortname", @@ -3745,11 +3871,11 @@ ], "responses": { "200": { - "description": "Returns the CVE ID quota for an organization", + "description": "Returns information about the organization updated", "content": { "application/json": { "schema": { - "$ref": "../schemas/org/get-org-quota-response.json" + "$ref": "../schemas/registry-org/update-registry-org-response.json" } } } @@ -3804,17 +3930,33 @@ } } } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "../schemas/registry-org/update-registry-org-request.json" + }, + "example": { + "short_name": "fake_company", + "long_name": "Fake Company", + "id_quota": 1000, + "authority": [ + "CNA" + ] + } + } + } } - } - }, - "/org/{shortname}/users": { - "get": { + }, + "delete": { "tags": [ - "Users" + "Registry Organization" ], - "summary": "Retrieves all users for the organization with the specified short name (accessible to same-organization users or Secretariat)", - "description": "

Access Control

Authenticated users can access this endpoint only for their own organization. Secretariat users can access any organization.

Expected Behavior

Regular, CNA & Admin Users: Retrieves information about users in the same organization

Secretariat: Retrieves all user information for any organization

", - "operationId": "userOrgAll", + "summary": "Deletes the registry organization specified by short name (accessible to Secretariat only)", + "description": "

Access Control

User must belong to an organization with the Secretariat role

Expected Behavior

Secretariat: Deletes the specified registry organization

", + "operationId": "registryOrgDeleteSingle", "parameters": [ { "name": "shortname", @@ -3823,10 +3965,7 @@ "schema": { "type": "string" }, - "description": "The shortname of the organization" - }, - { - "$ref": "#/components/parameters/pageQuery" + "description": "The shortname of the registry organization" }, { "$ref": "#/components/parameters/apiEntityHeader" @@ -3840,11 +3979,11 @@ ], "responses": { "200": { - "description": "Returns all users for the organization, along with pagination fields if results span multiple pages of data", + "description": "Confirms deletion of the registry organization", "content": { "application/json": { "schema": { - "$ref": "../schemas/user/list-users-response.json" + "$ref": "../schemas/registry-org/delete-registry-org-response.json" } } } @@ -3902,14 +4041,14 @@ } } }, - "/org/{shortname}/user": { + "/registry/org/{shortname}/user": { "post": { "tags": [ - "Users" + "Registry User" ], "summary": "Create a user with the provided short name as the owning organization (accessible to Secretariat or target organization Admin)", "description": "

Access Control

User must belong to an organization with the Secretariat role or be an Admin of the target organization

Expected Behavior

Admin User: Creates a user for the Admin's organization

Secretariat: Creates a user for any organization

", - "operationId": "userCreateSingle", + "operationId": "registryUserCreateSingle", "parameters": [ { "name": "shortname", @@ -3936,7 +4075,22 @@ "content": { "application/json": { "schema": { - "$ref": "../schemas/user/create-user-response.json" + "$ref": "../schemas/registry-user/create-registry-user-response.json" + } + }, + "example": { + "message": "jdoe was successfully created.", + "created": { + "UUID": "fe566221-6a2c-4279-8800-4d3795325997", + "username": "jdoe", + "name": { + "first": "John", + "last": "Doe" + }, + "status": "active", + "secret": "12345-abcde-67890", + "created": "2021-02-12T17:15:37.382Z", + "last_updated": "2021-02-12T17:15:37.382Z" } } } @@ -3997,21 +4151,29 @@ "content": { "application/json": { "schema": { - "$ref": "../schemas/user/create-user-request.json" + "$ref": "../schemas/registry-user/create-registry-user-request.json" + } + }, + "example": { + "username": "jdoe", + "status": "active", + "name": { + "first": "John", + "last": "Doe" } } } } } }, - "/org/{shortname}/user/{username}": { - "get": { + "/registry/org/{shortname}/user/{username}/reset_secret": { + "put": { "tags": [ - "Users" + "Registry User" ], - "summary": "Retrieves information about a user for the specified username and organization short name (accessible to same-organization users or Secretariat)", - "description": "

Access Control

Authenticated users can access this endpoint only for users in their own organization. Secretariat users can access any user.

Expected Behavior

Regular, CNA & Admin Users: Retrieves information about a user in the same organization

Secretariat: Retrieves any user's information

", - "operationId": "userSingle", + "summary": "Reset the API key for a user (accessible to self, same-organization Admins, or Secretariat)", + "description": "

Access Control

Authenticated users can reset their own API secret. Organization admins can reset users in their organization. Secretariat users can reset any user's API secret.

Expected Behavior

Regular User: Resets user's own API secret

Admin User: Resets any user's API secret in the Admin's organization

Secretariat: Resets any user's API secret

", + "operationId": "registryUserResetSecret", "parameters": [ { "name": "shortname", @@ -4043,11 +4205,11 @@ ], "responses": { "200": { - "description": "Returns information about the specified user", + "description": "Returns the new API key", "content": { "application/json": { "schema": { - "$ref": "../schemas/user/get-user-response.json" + "$ref": "../schemas/user/reset-secret-response.json" } } } @@ -4103,14 +4265,16 @@ } } } - }, - "put": { + } + }, + "/registry/org/{shortname}/user/{username}/grant-role": { + "post": { "tags": [ - "Users" + "Registry User" ], - "summary": "Updates information about a user for the specified username and organization shortname (accessible to self, same-organization Admins, or Secretariat)", - "description": "

Access Control

Authenticated users can update their own name fields. Organization admins can update users in their organization. Secretariat users can update users in any organization.

Expected Behavior

Regular User: Updates the user's own information. Only name fields may be changed.

Admin User: Updates information about a user in the Admin's organization. Allowed to change all fields except org_short_name.

Secretariat: Updates information about a user in any organization. Allowed to change all fields.

", - "operationId": "userUpdateSingle", + "summary": "Grants a role to a user (accessible to Secretariat or Org Admin)", + "description": "

Access Control

User must belong to an organization with the Secretariat role or be an Admin of the target organization

Expected Behavior

Admin User: Grants a role to a user in the Admin's organization

Secretariat: Grants a role to a user in any organization

", + "operationId": "registryUserGrantRole", "parameters": [ { "name": "shortname", @@ -4130,33 +4294,6 @@ }, "description": "The username of the user" }, - { - "$ref": "#/components/parameters/active" - }, - { - "$ref": "#/components/parameters/activeUserRolesAdd" - }, - { - "$ref": "#/components/parameters/activeUserRolesRemove" - }, - { - "$ref": "#/components/parameters/nameFirst" - }, - { - "$ref": "#/components/parameters/nameLast" - }, - { - "$ref": "#/components/parameters/nameMiddle" - }, - { - "$ref": "#/components/parameters/nameSuffix" - }, - { - "$ref": "#/components/parameters/newUsername" - }, - { - "$ref": "#/components/parameters/orgShortname" - }, { "$ref": "#/components/parameters/apiEntityHeader" }, @@ -4169,11 +4306,16 @@ ], "responses": { "200": { - "description": "Returns the updated user information", + "description": "Role granted successfully", "content": { "application/json": { "schema": { - "$ref": "../schemas/user/update-user-response.json" + "type": "object", + "properties": { + "message": { + "type": "string" + } + } } } } @@ -4228,17 +4370,38 @@ } } } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "role": { + "type": "string", + "enum": [ + "ADMIN" + ] + } + }, + "required": [ + "role" + ] + } + } + } } } }, - "/org/{shortname}/user/{username}/reset_secret": { - "put": { + "/registry/org/{shortname}/user/{username}/revoke-role": { + "post": { "tags": [ - "Users" + "Registry User" ], - "summary": "Reset the API key for a user (accessible to self, same-organization Admins, or Secretariat)", - "description": "

Access Control

Authenticated users can reset their own API secret. Organization admins can reset users in their organization. Secretariat users can reset any user's API secret.

Expected Behavior

Regular User: Resets user's own API secret

Admin User: Resets any user's API secret in the Admin's organization

Secretariat: Resets any user's API secret

", - "operationId": "userResetSecret", + "summary": "Revokes a role from a user (accessible to Secretariat or Org Admin)", + "description": "

Access Control

User must belong to an organization with the Secretariat role or be an Admin of the target organization

Expected Behavior

Admin User: Revokes a role from a user in the Admin's organization

Secretariat: Revokes a role from a user in any organization

", + "operationId": "registryUserRevokeRole", "parameters": [ { "name": "shortname", @@ -4270,11 +4433,16 @@ ], "responses": { "200": { - "description": "Returns the new API key", + "description": "Role revoked successfully", "content": { "application/json": { "schema": { - "$ref": "../schemas/user/reset-secret-response.json" + "type": "object", + "properties": { + "message": { + "type": "string" + } + } } } } @@ -4329,20 +4497,56 @@ } } } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "role": { + "type": "string", + "enum": [ + "ADMIN" + ] + } + }, + "required": [ + "role" + ] + } + } + } } } }, - "/registry/users": { - "get": { + "/registry/org/{shortname}/conversation/{index}": { + "put": { "tags": [ - "Registry User" + "Registry Organization" ], - "summary": "Retrieves information about all registered users (accessible to Secretariat)", - "description": "

Access Control

User must belong to an organization with the Secretariat role

Expected Behavior

Secretariat: Retrieves information about all users for all organizations

", - "operationId": "userAll", + "summary": "Update the conversation at the given index for the given organization (accessible to Secretariat or original same-organization author)", + "description": "

Access Control

User must belong to an organization with the Secretariat role or be the original author of the conversation in the same organization

Expected Behavior

Original Author: Allowed to update only the message body of a conversation posted by them

Secretariat: Allowed to update the message body and/or visibility of any conversation

", + "operationId": "registryOrgUpdateConversation", "parameters": [ { - "$ref": "#/components/parameters/pageQuery" + "name": "shortname", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The shortname of the organization" + }, + { + "name": "index", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The index of the conversation to update" }, { "$ref": "#/components/parameters/apiEntityHeader" @@ -4356,11 +4560,11 @@ ], "responses": { "200": { - "description": "Returns all users, along with pagination fields if results span multiple pages of data.", + "description": "Returns the updated conversation", "content": { "application/json": { "schema": { - "$ref": "../schemas/registry-user/list-registry-users-response.json" + "$ref": "../schemas/conversation/update-conversation-response.json" } } } @@ -4418,14 +4622,14 @@ } } }, - "/users": { + "/registry/users": { "get": { "tags": [ - "Users" + "Registry User" ], "summary": "Retrieves information about all registered users (accessible to Secretariat)", "description": "

Access Control

User must belong to an organization with the Secretariat role

Expected Behavior

Secretariat: Retrieves information about all users for all organizations

", - "operationId": "userAll", + "operationId": "registryUserAll", "parameters": [ { "$ref": "#/components/parameters/pageQuery" @@ -4446,7 +4650,7 @@ "content": { "application/json": { "schema": { - "$ref": "../schemas/user/list-users-response.json" + "$ref": "../schemas/registry-user/list-registry-users-response.json" } } } @@ -4504,21 +4708,6 @@ } } }, - "/health-check": { - "get": { - "tags": [ - "Utilities" - ], - "summary": "Checks that the system is running (accessible to all users)", - "description": "

Access Control

Endpoint is accessible to all

Expected Behavior

Returns a 200 response code when CVE Services are running

", - "operationId": "healthCheck", - "responses": { - "200": { - "description": "Returns a 200 response code" - } - } - } - }, "/conversation": { "get": { "tags": [ diff --git a/schemas/registry-org/delete-registry-org-response.json b/schemas/registry-org/delete-registry-org-response.json new file mode 100644 index 000000000..f35e807f1 --- /dev/null +++ b/schemas/registry-org/delete-registry-org-response.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://cve.mitre.org/schema/registry-org/delete-registry-org-response.json", + "type": "object", + "title": "CVE Delete Registry Org Response", + "description": "JSON Schema for a successful registry organization deletion response", + "properties": { + "message": { + "type": "string", + "description": "Confirmation that the registry organization was deleted" + } + }, + "required": [ + "message" + ], + "additionalProperties": false +} diff --git a/schemas/registry-user/delete-registry-user-response.json b/schemas/registry-user/delete-registry-user-response.json new file mode 100644 index 000000000..caf60671d --- /dev/null +++ b/schemas/registry-user/delete-registry-user-response.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://cve.mitre.org/schema/registry-user/delete-registry-user-response.json", + "type": "object", + "title": "CVE Delete Registry User Response", + "description": "JSON Schema for a successful registry user deletion response", + "properties": { + "message": { + "type": "string", + "description": "Confirmation that the registry user was deleted" + } + }, + "required": [ + "message" + ], + "additionalProperties": false +} diff --git a/src/controller/org.controller/index.js b/src/controller/org.controller/index.js index d3b472d1b..52e4c4c77 100644 --- a/src/controller/org.controller/index.js +++ b/src/controller/org.controller/index.js @@ -3,8 +3,6 @@ const router = express.Router() const mw = require('../../middleware/middleware') const errorMsgs = require('../../middleware/errorMessages') const controller = require('./org.controller') -const registryOrgController = require('../registry-org.controller/registry-org.controller.js') -const registryUserController = require('../registry-user.controller/registry-user.controller.js') const { body, param, query } = require('express-validator') const { parseGetParams, parsePostParams, parsePutParams, parseError, isUserRole, isValidUsername, isOrgRole, validateUpdateOrgParameters, shortCircuitLegacyCpsMitreOrgParameters } = require('./org.middleware') // Only God and Javascript know swhy its saying it is not used when it is..... @@ -12,1192 +10,6 @@ const { parseGetParams, parsePostParams, parsePutParams, parseError, isUserRole, const { toUpperCaseArray, isFlatStringArray } = require('../../middleware/middleware') const getConstants = require('../../../src/constants').getConstants const CONSTANTS = getConstants() - -router.get('/registry/org', - /* - #swagger.tags = ['Registry Organization'] - #swagger.operationId = 'registryOrgAll' - #swagger.summary = "Retrieves all registry organizations (accessible to Secretariat)" - #swagger.description = " -

Access Control

-

User must belong to an organization with the Secretariat role

-

Expected Behavior

-

Secretariat: Retrieves information about all registry organizations

" - #swagger.parameters['$ref'] = [ - '#/components/parameters/pageQuery', - '#/components/parameters/apiEntityHeader', - '#/components/parameters/apiUserHeader', - '#/components/parameters/apiSecretHeader' - ] - #swagger.responses[200] = { - description: 'Returns information about all registry organizations, along with pagination fields if results span multiple pages of data', - content: { - "application/json": { - schema: { - $ref: '../schemas/registry-org/list-registry-orgs-response.json' - } - } - } - } - #swagger.responses[400] = { - description: 'Bad Request', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/bad-request.json' } - } - } - } - #swagger.responses[401] = { - description: 'Not Authenticated', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[403] = { - description: 'Forbidden', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[404] = { - description: 'Not Found', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[500] = { - description: 'Internal Server Error', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - */ - mw.useRegistry(), - mw.validateUser, - mw.onlySecretariat, - query().custom((query) => { return mw.validateQueryParameterNames(query, ['page']) }), - query(['page']).custom((val) => { return mw.containsNoInvalidCharacters(val) }), - query(['page']).optional().isInt({ min: CONSTANTS.PAGINATOR_PAGE }), - parseError, - parseGetParams, - registryOrgController.ALL_ORGS -) - -router.get('/registry/org/:shortname/users', - /* - #swagger.tags = ['Registry User'] - #swagger.operationId = 'userOrgAll' - #swagger.summary = "Retrieves all users for the organization with the specified short name (accessible to same-organization users or Secretariat)" - #swagger.description = " -

Access Control

-

Authenticated users can access this endpoint only for their own organization. Secretariat users can access any organization.

-

Expected Behavior

-

Regular, CNA & Admin Users: Retrieves information about users in the same organization

-

Secretariat: Retrieves all user information for any organization

" - #swagger.parameters['shortname'] = { description: 'The shortname of the organization' } - #swagger.parameters['$ref'] = [ - '#/components/parameters/pageQuery', - '#/components/parameters/apiEntityHeader', - '#/components/parameters/apiUserHeader', - '#/components/parameters/apiSecretHeader' - ] - #swagger.responses[200] = { - description: 'Returns all users for the organization, along with pagination fields if results span multiple pages of data', - content: { - "application/json": { - schema: { - $ref: '../schemas/registry-user/list-registry-users-response.json' - }, - example: { - "totalCount": 1, - "itemsPerPage": 100, - "pageCount": 1, - "currentPage": 1, - "prevPage": null, - "nextPage": null, - "users": [ - { - "UUID": "fe566221-6a2c-4279-8800-4d3795325997", - "username": "jdoe", - "name": { - "first": "John", - "last": "Doe" - }, - "role": "ADMIN", - "status": "active", - "created": "2021-02-12T17:15:37.382Z", - "last_updated": "2021-02-12T17:15:37.382Z" - } - ] - } - } - } - } - #swagger.responses[400] = { - description: 'Bad Request', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/bad-request.json' } - } - } - } - #swagger.responses[401] = { - description: 'Not Authenticated', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[403] = { - description: 'Forbidden', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[404] = { - description: 'Not Found', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[500] = { - description: 'Internal Server Error', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - */ - mw.useRegistry(), - mw.validateUser, - param(['shortname']).isString().trim().notEmpty().isLength({ min: CONSTANTS.MIN_SHORTNAME_LENGTH, max: CONSTANTS.MAX_SHORTNAME_LENGTH }), - query().custom((query) => { return mw.validateQueryParameterNames(query, ['page']) }), - query(['page']).custom((val) => { return mw.containsNoInvalidCharacters(val) }), - query(['page']).optional().isInt({ min: CONSTANTS.PAGINATOR_PAGE }), - parseError, - parseGetParams, - registryOrgController.USER_ALL) - -router.get('/registry/org/:shortname/id_quota', - /* - #swagger.tags = ['Registry Organization'] - #swagger.operationId = 'orgIdQuota' - #swagger.summary = "Retrieves an organization's CVE ID quota (accessible to same-organization users or Secretariat)" - #swagger.description = " -

Access Control

-

Authenticated users can access this endpoint only for their own organization. Secretariat users can access any organization.

-

Expected Behavior

-

Regular, CNA & Admin Users: Retrieves the CVE ID quota for the user's organization

-

Secretariat: Retrieves the CVE ID quota for any organization

" - #swagger.parameters['shortname'] = { description: 'The shortname of the organization' } - #swagger.parameters['$ref'] = [ - '#/components/parameters/apiEntityHeader', - '#/components/parameters/apiUserHeader', - '#/components/parameters/apiSecretHeader' - ] - #swagger.responses[200] = { - description: 'Returns the CVE ID quota for an organization', - content: { - "application/json": { - schema: { - $ref: '../schemas/registry-org/get-registry-org-quota-response.json' - } - } - } - } - #swagger.responses[400] = { - description: 'Bad Request', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/bad-request.json' } - } - } - } - #swagger.responses[401] = { - description: 'Not Authenticated', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[403] = { - description: 'Forbidden', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[404] = { - description: 'Not Found', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[500] = { - description: 'Internal Server Error', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - */ - mw.useRegistry(), - mw.validateUser, - param(['shortname']).isString().trim().notEmpty().isLength({ min: CONSTANTS.MIN_SHORTNAME_LENGTH, max: CONSTANTS.MAX_SHORTNAME_LENGTH }), - query().custom((query) => { return mw.validateQueryParameterNames(query, ['']) }), - parseError, - parseGetParams, - controller.ORG_ID_QUOTA) - -router.get('/registry/org/:identifier', - /* - #swagger.tags = ['Registry Organization'] - #swagger.operationId = 'registryOrgSingle' - #swagger.summary = "Retrieves information about the registry organization specified by short name or UUID (accessible to same-organization users or Secretariat)" - #swagger.description = " -

Access Control

-

Authenticated users can access this endpoint only for their own organization. Secretariat users can access any organization.

-

Expected Behavior

-

Regular, CNA & Admin Users: Retrieves registry organization record for the specified shortname or UUID if it is the user's organization

-

Secretariat: Retrieves information about any registry organization

" - #swagger.parameters['identifier'] = { description: 'The shortname or UUID of the registry organization' } - #swagger.parameters['expand'] = { - in: 'query', - description: 'Optional expanded related data. Accepted value: users.', - required: false, - schema: { - type: 'string', - enum: ['users'] - } - } - #swagger.parameters['$ref'] = [ - '#/components/parameters/apiEntityHeader', - '#/components/parameters/apiUserHeader', - '#/components/parameters/apiSecretHeader' - ] - #swagger.responses[200] = { - description: 'Returns the registry organization information', - content: { - "application/json": { - schema: { - $ref: '../schemas/registry-org/get-registry-org-response.json' - } - } - } - } - #swagger.responses[400] = { - description: 'Bad Request', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/bad-request.json' } - } - } - } - #swagger.responses[401] = { - description: 'Not Authenticated', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[403] = { - description: 'Forbidden', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[404] = { - description: 'Not Found', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[500] = { - description: 'Internal Server Error', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - */ - mw.useRegistry(), - mw.validateUser, - query().custom((query) => { return mw.validateQueryParameterNames(query, ['expand']) }), - query(['expand']).custom((val) => { return mw.containsNoInvalidCharacters(val) }), - query(['expand']).optional().isIn(['users']), - parseError, - parseGetParams, - registryOrgController.SINGLE_ORG -) - -router.get('/registry/org/:shortname/user/:username', - /* - #swagger.tags = ['Registry User'] - #swagger.operationId = 'registryUserSingle' - #swagger.summary = "Retrieves information about a user for the specified username and organization short name (accessible to same-organization users or Secretariat)" - #swagger.description = " -

Access Control

-

Authenticated users can access this endpoint only for users in their own organization. Secretariat users can access any user.

-

Expected Behavior

-

Regular, CNA & Admin Users: Retrieves information about a registry user in the same organization

-

Secretariat: Retrieves any registry user's information

" - #swagger.parameters['$ref'] = [ - '#/components/parameters/apiEntityHeader', - '#/components/parameters/apiUserHeader', - '#/components/parameters/apiSecretHeader' - ] - #swagger.parameters['shortname'] = { - description: 'The shortname of the organization' - } - #swagger.parameters['username'] = { - description: 'The username of the registry user', - schema: { - type: 'string', - pattern: '^[a-zA-Z0-9._@-]+$' - } - } - #swagger.responses[200] = { - description: 'Returns information about the specified registry user', - content: { - "application/json": { - schema: { $ref: '../schemas/registry-user/get-registry-user-response.json' } - } - } - } - #swagger.responses[400] = { - description: 'Bad Request', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/bad-request.json' } - } - } - } - #swagger.responses[401] = { - description: 'Not Authenticated', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[403] = { - description: 'Forbidden', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[404] = { - description: 'Not Found', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[500] = { - description: 'Internal Server Error', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - */ - mw.useRegistry(), - mw.validateUser, - param(['shortname']).isString().trim().notEmpty().isLength({ min: CONSTANTS.MIN_SHORTNAME_LENGTH, max: CONSTANTS.MAX_SHORTNAME_LENGTH }), - param(['username']).isString().trim().notEmpty().custom(isValidUsername), - query().custom((query) => { return mw.validateQueryParameterNames(query, ['']) }), - parseError, - parseGetParams, - registryUserController.SINGLE_USER -) - -router.post('/registry/org', - /* - #swagger.tags = ['Registry Organization'] - #swagger.operationId = 'orgCreateSingle' - #swagger.summary = "Creates an organization (accessible to Secretariat)" - #swagger.description = " -

Access Control

-

User must belong to an organization with the Secretariat role

-

Expected Behavior

-

Secretariat: Creates a new organization

" - #swagger.parameters['$ref'] = [ - '#/components/parameters/apiEntityHeader', - '#/components/parameters/apiUserHeader', - '#/components/parameters/apiSecretHeader' - ] - #swagger.requestBody = { - required: true, - content: { - 'application/json': { - schema: { - anyOf: [ - { $ref: '../schemas/registry-org/SecretariatOrg.json' }, - { $ref: '../schemas/registry-org/CNAOrg.json' }, - { $ref: '../schemas/registry-org/ADPOrg.json' }, - { $ref: '../schemas/registry-org/BulkDownloadOrg.json' } - ] - }, - example: { - short_name: 'fake_company', - long_name: 'Fake Company', - id_quota: 1000, - authority: ['CNA'] - } - } - } - } - #swagger.responses[200] = { - description: 'Returns information about all organizations, along with pagination fields if results span multiple pages of data', - content: { - "application/json": { - schema: { - $ref: '../schemas/registry-org/list-registry-orgs-response.json' - } - } - } - } - #swagger.responses[400] = { - description: 'Bad Request', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/bad-request.json' } - } - } - } - #swagger.responses[401] = { - description: 'Not Authenticated', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[403] = { - description: 'Forbidden', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[404] = { - description: 'Not Found', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[500] = { - description: 'Internal Server Error', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - */ - mw.useRegistry(), - mw.validateUser, - mw.onlySecretariat, - query().custom((query) => { return mw.validateQueryParameterNames(query, ['']) }), - parsePostParams, - parseError, - registryOrgController.CREATE_ORG -) - -router.put('/registry/org/:shortname', - /* - #swagger.tags = ['Registry Organization'] - #swagger.operationId = 'orgUpdateSingle' - #swagger.summary = "Updates information about the organization specified by short name (accessible to Secretariat or same-organization Admin)" - #swagger.description = " -

Access Control

-

User must belong to an organization with the Secretariat role or be an Admin of the requested organization.

-

With Joint Approval required for the following fields:

-

Expected Behavior

- This endpoint expects a full organization object in the request body. -

Secretariat: Updates any organization's information

-

Organization Admin: Requests changes to its organization's information

-
    -
  • short_name
  • -
  • long_name
  • -
  • authority
  • -
  • aliases
  • -
  • oversees
  • -
  • top_level_root
  • -
  • charter_or_scope
  • -
  • product_list
  • -
  • disclosure_policy
  • -
  • contact_info.websites
  • -
  • contact_info.emails
  • -
  • contact_info.phone
  • -
  • partner_role_type
  • -
  • partner_country
  • -
  • advisory_locations
  • -
  • advisory_location_require_credentials
  • -
  • vulnerability_advisory_location_for_web_scraping
  • -
  • industry
  • -
  • tl_root_start_date
  • -
  • is_cna_discussion_list
  • -
" - #swagger.parameters['shortname'] = { description: 'The shortname of the organization' } - #swagger.parameters['$ref'] = [ - '#/components/parameters/apiEntityHeader', - '#/components/parameters/apiUserHeader', - '#/components/parameters/apiSecretHeader' - ] - #swagger.requestBody = { - required: true, - content: { - 'application/json': { - schema: { - $ref: '../schemas/registry-org/update-registry-org-request.json' - }, - example: { - short_name: 'fake_company', - long_name: 'Fake Company', - id_quota: 1000, - authority: ['CNA'] - } - } - } - } - #swagger.responses[200] = { - description: 'Returns information about the organization updated', - content: { - "application/json": { - schema: { - $ref: '../schemas/registry-org/update-registry-org-response.json' - } - } - } - } - #swagger.responses[400] = { - description: 'Bad Request', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/bad-request.json' } - } - } - } - #swagger.responses[401] = { - description: 'Not Authenticated', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[403] = { - description: 'Forbidden', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[404] = { - description: 'Not Found', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[500] = { - description: 'Internal Server Error', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - */ - mw.useRegistry(), - mw.validateUser, - // mw.onlySecretariat, - parseError, - parsePutParams, - registryOrgController.UPDATE_ORG -) - -router.post('/registry/org/:shortname/user', - /* - #swagger.tags = ['Registry User'] - #swagger.operationId = 'registryUserCreateSingle' - #swagger.summary = "Create a user with the provided short name as the owning organization (accessible to Secretariat or target organization Admin)" - #swagger.description = " -

Access Control

-

User must belong to an organization with the Secretariat role or be an Admin of the target organization

-

Expected Behavior

-

Admin User: Creates a user for the Admin's organization

-

Secretariat: Creates a user for any organization

" - #swagger.parameters['shortname'] = { description: 'The shortname of the organization' } - #swagger.parameters['$ref'] = [ - '#/components/parameters/apiEntityHeader', - '#/components/parameters/apiUserHeader', - '#/components/parameters/apiSecretHeader' - ] - #swagger.requestBody = { - required: true, - content: { - 'application/json': { - schema: - { $ref: '../schemas/registry-user/create-registry-user-request.json' } - }, - example: { - "username": "jdoe", - "status": "active", - "name": { - "first": "John", - "last": "Doe" - } - } - } - } - #swagger.responses[200] = { - description: 'Returns the new user information (with the secret)', - content: { - "application/json": { - schema: - { $ref: '../schemas/registry-user/create-registry-user-response.json' } - }, - example: { - "message": "jdoe was successfully created.", - "created": { - "UUID": "fe566221-6a2c-4279-8800-4d3795325997", - "username": "jdoe", - "name": { - "first": "John", - "last": "Doe" - }, - "status": "active", - "secret": "12345-abcde-67890", - "created": "2021-02-12T17:15:37.382Z", - "last_updated": "2021-02-12T17:15:37.382Z" - } - } - } - } - #swagger.responses[400] = { - description: 'Bad Request', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/bad-request.json' } - } - } - } - #swagger.responses[401] = { - description: 'Not Authenticated', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[403] = { - description: 'Forbidden', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[404] = { - description: 'Not Found', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[500] = { - description: 'Internal Server Error', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - */ - mw.useRegistry(), - mw.validateUser, - mw.onlySecretariatOrAdmin, - mw.onlyOrgWithPartnerRole, - param(['shortname']).isString().trim().notEmpty().isLength({ min: CONSTANTS.MIN_SHORTNAME_LENGTH, max: CONSTANTS.MAX_SHORTNAME_LENGTH }), - body(['org_uuid']).optional().isString().trim(), - body(['uuid']).optional().isString().trim(), - body(['name.first']).optional().isString().trim().isLength({ max: CONSTANTS.MAX_FIRSTNAME_LENGTH }).withMessage(errorMsgs.FIRSTNAME_LENGTH), - body(['name.last']).optional().isString().trim().isLength({ max: CONSTANTS.MAX_LASTNAME_LENGTH }).withMessage(errorMsgs.LASTNAME_LENGTH), - body(['name.middle']).optional().isString().trim().isLength({ max: CONSTANTS.MAX_MIDDLENAME_LENGTH }).withMessage(errorMsgs.MIDDLENAME_LENGTH), - body(['name.suffix']).optional().isString().trim().isLength({ max: CONSTANTS.MAX_SUFFIX_LENGTH }).withMessage(errorMsgs.SUFFIX_LENGTH), - body(['authority.active_roles']).optional() - .custom(mw.isFlatStringArray) - .bail() - .customSanitizer(toUpperCaseArray) - .custom(isUserRole), - parseError, - parsePostParams, - registryOrgController.USER_CREATE_SINGLE -) - -router.put('/registry/org/:shortname/user/:username', - /* - #swagger.tags = ['Registry User'] - #swagger.operationId = 'registryUserUpdateSingle' - #swagger.summary = "Updates information about a user for the specified username and organization shortname (accessible to self, same-organization Admins, or Secretariat)" - #swagger.description = " -

Access Control

-

Authenticated users can update their own name fields. Organization admins can update users in their organization. Secretariat users can update users in any organization.

-

Expected Behavior

-

Regular User: Updates the user's own information. Only name fields may be changed.

-

Admin User: Updates information about a user in the Admin's organization. Allowed to change all fields except org_short_name.

-

Secretariat: Updates information about a user in any organization. Allowed to change all fields.

" - #swagger.parameters['shortname'] = { description: 'The shortname of the organization' } - #swagger.parameters['username'] = { description: 'The username of the user' } - #swagger.parameters['$ref'] = [ - '#/components/parameters/active', - '#/components/parameters/orgShortname', - '#/components/parameters/apiEntityHeader', - '#/components/parameters/apiUserHeader', - '#/components/parameters/apiSecretHeader' - ] - #swagger.responses[200] = { - description: 'Returns the updated user information', - content: { - "application/json": { - schema: { $ref: '../schemas/registry-user/update-registry-user-response.json' }, - example: { - "message": "jdoe was successfully updated.", - "updated": { - "UUID": "fe566221-6a2c-4279-8800-4d3795325997", - "username": "jdoe", - "name": { - "first": "John", - "last": "Doe" - }, - "status": "active", - "created": "2021-02-12T17:15:37.382Z", - "last_updated": "2021-02-12T17:15:37.382Z" - } - } - } - } - } - #swagger.responses[400] = { - description: 'Bad Request', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/bad-request.json' } - } - } - } - #swagger.responses[401] = { - description: 'Not Authenticated', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[403] = { - description: 'Forbidden', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[404] = { - description: 'Not Found', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[500] = { - description: 'Internal Server Error', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - */ - mw.useRegistry(), - mw.validateUser, - mw.onlyOrgWithPartnerRole, - parseError, - parsePutParams, - registryUserController.UPDATE_USER) - -router.put('/registry/org/:shortname/user/:username/reset_secret', - /* - #swagger.tags = ['Registry User'] - #swagger.operationId = 'userResetSecret' - #swagger.summary = "Reset the API key for a user (accessible to self, same-organization Admins, or Secretariat)" - #swagger.description = " -

Access Control

-

Authenticated users can reset their own API secret. Organization admins can reset users in their organization. Secretariat users can reset any user's API secret.

-

Expected Behavior

-

Regular User: Resets user's own API secret

-

Admin User: Resets any user's API secret in the Admin's organization

-

Secretariat: Resets any user's API secret

" - #swagger.parameters['shortname'] = { description: 'The shortname of the organization' } - #swagger.parameters['username'] = { description: 'The username of the user' } - #swagger.parameters['$ref'] = [ - '#/components/parameters/apiEntityHeader', - '#/components/parameters/apiUserHeader', - '#/components/parameters/apiSecretHeader' - ] - #swagger.responses[200] = { - description: 'Returns the new API key', - content: { - "application/json": { - schema: { $ref: '../schemas/user/reset-secret-response.json' } - } - } - } - #swagger.responses[400] = { - description: 'Bad Request', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/bad-request.json' } - } - } - } - #swagger.responses[401] = { - description: 'Not Authenticated', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[403] = { - description: 'Forbidden', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[404] = { - description: 'Not Found', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[500] = { - description: 'Internal Server Error', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - */ - mw.useRegistry(), - mw.validateUser, - mw.onlyOrgWithPartnerRole, - parseError, - parsePostParams, - controller.USER_RESET_SECRET -) - -router.post('/registry/org/:shortname/user/:username/grant-role', - /* - #swagger.tags = ['Registry User'] - #swagger.operationId = 'registryUserGrantRole' - #swagger.summary = "Grants a role to a user (accessible to Secretariat or Org Admin)" - #swagger.description = " -

Access Control

-

User must belong to an organization with the Secretariat role or be an Admin of the target organization

-

Expected Behavior

-

Admin User: Grants a role to a user in the Admin's organization

-

Secretariat: Grants a role to a user in any organization

" - #swagger.parameters['shortname'] = { description: 'The shortname of the organization' } - #swagger.parameters['username'] = { description: 'The username of the user' } - #swagger.parameters['$ref'] = [ - '#/components/parameters/apiEntityHeader', - '#/components/parameters/apiUserHeader', - '#/components/parameters/apiSecretHeader' - ] - #swagger.requestBody = { - required: true, - content: { - 'application/json': { - schema: { - type: 'object', - properties: { - role: { - type: 'string', - enum: ['ADMIN'] - } - }, - required: ['role'] - } - } - } - } - #swagger.responses[200] = { - description: 'Role granted successfully', - content: { - "application/json": { - schema: { type: 'object', properties: { message: { type: 'string' } } } - } - } - } - #swagger.responses[400] = { - description: 'Bad Request', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/bad-request.json' } - } - } - } - #swagger.responses[401] = { - description: 'Not Authenticated', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[403] = { - description: 'Forbidden', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[404] = { - description: 'Not Found', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[500] = { - description: 'Internal Server Error', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - */ - mw.useRegistry(), - mw.validateUser, - // mw.onlyOrgWithPartnerRole, // This might be too restrictive if we want Secretariat to do it for any org type - parseError, - parsePostParams, - registryUserController.GRANT_ROLE -) - -router.post('/registry/org/:shortname/user/:username/revoke-role', - /* - #swagger.tags = ['Registry User'] - #swagger.operationId = 'registryUserRevokeRole' - #swagger.summary = "Revokes a role from a user (accessible to Secretariat or Org Admin)" - #swagger.description = " -

Access Control

-

User must belong to an organization with the Secretariat role or be an Admin of the target organization

-

Expected Behavior

-

Admin User: Revokes a role from a user in the Admin's organization

-

Secretariat: Revokes a role from a user in any organization

" - #swagger.parameters['shortname'] = { description: 'The shortname of the organization' } - #swagger.parameters['username'] = { description: 'The username of the user' } - #swagger.parameters['$ref'] = [ - '#/components/parameters/apiEntityHeader', - '#/components/parameters/apiUserHeader', - '#/components/parameters/apiSecretHeader' - ] - #swagger.requestBody = { - required: true, - content: { - 'application/json': { - schema: { - type: 'object', - properties: { - role: { - type: 'string', - enum: ['ADMIN'] - } - }, - required: ['role'] - } - } - } - } - #swagger.responses[200] = { - description: 'Role revoked successfully', - content: { - "application/json": { - schema: { type: 'object', properties: { message: { type: 'string' } } } - } - } - } - #swagger.responses[400] = { - description: 'Bad Request', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/bad-request.json' } - } - } - } - #swagger.responses[401] = { - description: 'Not Authenticated', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[403] = { - description: 'Forbidden', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[404] = { - description: 'Not Found', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[500] = { - description: 'Internal Server Error', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - */ - mw.useRegistry(), - mw.validateUser, - // mw.onlyOrgWithPartnerRole, - parseError, - parsePostParams, - registryUserController.REVOKE_ROLE -) - -router.put('/registry/org/:shortname/conversation/:index', - /* - #swagger.tags = ['Registry Organization'] - #swagger.operationId = 'registryUserUpdateConversation' - #swagger.summary = "Update the conversation at the given index for the given organization (accessible to Secretariat or original same-organization author)" - #swagger.description = " -

Access Control

-

User must belong to an organization with the Secretariat role or be the original author of the conversation in the same organization

-

Expected Behavior

-

Original Author: Allowed to update only the message body of a conversation posted by them

-

Secretariat: Allowed to update the message body and/or visibility of any conversation

" - #swagger.parameters['shortname'] = { description: 'The shortname of the organization' } - #swagger.parameters['index'] = { description: 'The index of the conversation to update' } - #swagger.parameters['$ref'] = [ - '#/components/parameters/apiEntityHeader', - '#/components/parameters/apiUserHeader', - '#/components/parameters/apiSecretHeader' - ] - #swagger.responses[200] = { - description: 'Returns the updated conversation', - content: { - "application/json": { - schema: { $ref: '../schemas/conversation/update-conversation-response.json' } - } - } - } - #swagger.responses[400] = { - description: 'Bad Request', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/bad-request.json' } - } - } - } - #swagger.responses[401] = { - description: 'Not Authenticated', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[403] = { - description: 'Forbidden', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[404] = { - description: 'Not Found', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[500] = { - description: 'Internal Server Error', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - */ - mw.useRegistry(), - mw.validateUser, - mw.onlyOrgWithPartnerRole, - parseError, - parsePostParams, - registryOrgController.EDIT_CONVERSATION -) - router.get('/org', /* #swagger.tags = ['Organization'] diff --git a/src/controller/org.controller/org.controller.js b/src/controller/org.controller/org.controller.js index 9bff904f0..8fccc6b3b 100644 --- a/src/controller/org.controller/org.controller.js +++ b/src/controller/org.controller/org.controller.js @@ -510,7 +510,7 @@ async function createUser (req, res, next) { /** * Updates a user only if the user exist for the specified username. * If no user exists, it does not create the user. - * Called by PUT /org/{shortname}/user/{username} + * Called by PUT /api/org/{shortname}/user/{username} * * @param {Object} req - The request object * @param {Object} res - The response object @@ -667,7 +667,7 @@ async function updateUser (req, res, next) { /** * Resets API secret for specified user. - * Called by PUT /org/{shortname}/user/{username}/reset_secret, PUT /registry/org/{shortname}/user/{username}/reset_secret + * Called by PUT /api/org/{shortname}/user/{username}/reset_secret, PUT /api/registry/org/{shortname}/user/{username}/reset_secret * * @param {Object} req - The request object * @param {Object} res - The response object diff --git a/src/controller/org.controller/org.middleware.js b/src/controller/org.controller/org.middleware.js index e8f642899..fdff50735 100644 --- a/src/controller/org.controller/org.middleware.js +++ b/src/controller/org.controller/org.middleware.js @@ -168,6 +168,11 @@ function parseGetParams (req, res, next) { next() } +function parseDeleteParams (req, res, next) { + utils.reqCtxMapping(req, 'params', ['shortname', 'username']) + next() +} + function parseError (req, res, next) { const err = validationResult(req).formatWith(({ location, msg, param, value, nestedErrors }) => { return { msg: msg, param: param, location: location } @@ -190,6 +195,7 @@ module.exports = { parsePutParams, parsePostParams, parseGetParams, + parseDeleteParams, parseError, isOrgRole, isUserRole, diff --git a/src/controller/registry-org.controller/index.js b/src/controller/registry-org.controller/index.js deleted file mode 100644 index aa9acc93b..000000000 --- a/src/controller/registry-org.controller/index.js +++ /dev/null @@ -1,552 +0,0 @@ -const express = require('express') -const router = express.Router() -const mw = require('../../middleware/middleware') -const { param, query, body } = require('express-validator') -const controller = require('./registry-org.controller') -const { parseGetParams, parsePostParams, parseDeleteParams, parseError } = require('./registry-org.middleware') -const getConstants = require('../../constants').getConstants -const CONSTANTS = getConstants() - -router.get('/registryOrg', - /* - #swagger.tags = ['Registry Organization'] - #swagger.operationId = 'getAllRegistryOrgs' - #swagger.ignore = true - #swagger.summary = "Retrieves information about all registry organizations (accessible to Secretariat only)" - #swagger.description = " -

Access Control

-

Only users with Secretariat role can access this endpoint

-

Expected Behavior

-

Secretariat: Retrieves a list of all registry organizations

- #swagger.parameters['$ref'] = [ - '#/components/parameters/pageQuery', - '#/components/parameters/apiEntityHeader', - '#/components/parameters/apiUserHeader', - '#/components/parameters/apiSecretHeader' - ] - #swagger.responses[200] = { - description: 'A list of all registry organizations, along with pagination fields if results span multiple pages of data', - content: { - "application/json": { - schema: { $ref: '../schemas/registry-org/list-registry-orgs-response.json' } - } - } - } - #swagger.responses[400] = { - description: 'Bad Request', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/bad-request.json' } - } - } - } - #swagger.responses[401] = { - description: 'Not Authenticated', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[403] = { - description: 'Forbidden', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[500] = { - description: 'Internal Server Error', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - */ - mw.validateUser, - mw.onlySecretariat, - query().custom((query) => { return mw.validateQueryParameterNames(query, ['page']) }), - query(['page']).custom((val) => { return mw.containsNoInvalidCharacters(val) }), - query(['page']).optional().isInt({ min: CONSTANTS.PAGINATOR_PAGE }), - parseError, - parseGetParams, - controller.ALL_ORGS -) - -router.get('/registryOrg/:identifier', - /* - #swagger.tags = ['Registry Organization'] - #swagger.operationId = 'getSingleRegistryOrg' - #swagger.ignore = true - #swagger.summary = "Retrieves information about a specific registry organization (accessible to Secretariat only)" - #swagger.description = " -

Access Control

-

Only users with Secretariat role can access this endpoint

-

Expected Behavior

-

Secretariat: Retrieves information about the specified registry organization

- #swagger.parameters['identifier'] = { - in: 'path', - description: 'The identifier of the registry organization', - required: true, - type: 'string' - } - #swagger.parameters['$ref'] = [ - '#/components/parameters/apiEntityHeader', - '#/components/parameters/apiUserHeader', - '#/components/parameters/apiSecretHeader' - ] - #swagger.responses[200] = { - description: 'The requested registry organization information is returned', - content: { - "application/json": { - schema: { $ref: '../schemas/registry-org/get-registry-org-response.json' } - } - } - } - #swagger.responses[400] = { - description: 'Bad Request', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/bad-request.json' } - } - } - } - #swagger.responses[401] = { - description: 'Not Authenticated', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[404] = { - description: 'Not Found', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[500] = { - description: 'Internal Server Error', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - */ - mw.validateUser, - mw.onlySecretariat, - param(['identifier']).isString().trim(), - parseError, - parseGetParams, - controller.SINGLE_ORG -) - -router.post('/registryOrg', - /* - #swagger.tags = ['Registry Organization'] - #swagger.operationId = 'createRegistryOrg' - #swagger.ignore = true - #swagger.summary = "Creates a new registry organization (accessible to Secretariat only)" - #swagger.description = " -

Access Control

-

Only users with Secretariat role can access this endpoint

-

Expected Behavior

-

Secretariat: Creates a new registry organization

- #swagger.parameters['$ref'] = [ - '#/components/parameters/apiEntityHeader', - '#/components/parameters/apiUserHeader', - '#/components/parameters/apiSecretHeader' - ] - #swagger.requestBody = { - required: true, - content: { - 'application/json': { - schema: { $ref: '../schemas/registry-org/create-registry-org-request.json' } - } - } - } - #swagger.responses[201] = { - description: 'The registry organization was successfully created', - content: { - "application/json": { - schema: { $ref: '../schemas/registry-org/create-registry-org-response.json' } - } - } - } - #swagger.responses[400] = { - description: 'Bad Request', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/bad-request.json' } - } - } - } - #swagger.responses[401] = { - description: 'Not Authenticated', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[403] = { - description: 'Forbidden', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[500] = { - description: 'Internal Server Error', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - */ - mw.useRegistry(), - mw.validateUser, - mw.onlySecretariat, - body(['reports_to']).not().exists().withMessage('reports_to must not be present'), - parseError, - parsePostParams, - controller.CREATE_ORG -) - -router.put('/registryOrg/:shortname', - /* - #swagger.tags = ['Registry Organization'] - #swagger.operationId = 'updateRegistryOrg' - #swagger.ignore = true - #swagger.summary = "Updates an existing registry organization (accessible to Secretariat only)" - #swagger.description = " -

Access Control

-

Only users with Secretariat role can access this endpoint

-

Expected Behavior

-

Secretariat: Updates an existing registry organization

- #swagger.parameters['shortname'] = { - in: 'path', - description: 'The Shortname of the registry organization to update', - required: true, - type: 'string' - } - #swagger.parameters['$ref'] = [ - '#/components/parameters/apiEntityHeader', - '#/components/parameters/apiUserHeader', - '#/components/parameters/apiSecretHeader' - ] - #swagger.requestBody = { - required: true, - content: { - 'application/json': { - schema: { $ref: '../schemas/registry-org/update-registry-org-request.json' } - } - } - } - #swagger.responses[200] = { - description: 'The registry organization was successfully updated', - content: { - "application/json": { - schema: { $ref: '../schemas/registry-org/update-registry-org-response.json' } - } - } - } - #swagger.responses[400] = { - description: 'Bad Request', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/bad-request.json' } - } - } - } - #swagger.responses[401] = { - description: 'Not Authenticated', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[403] = { - description: 'Forbidden', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[404] = { - description: 'Not Found', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[500] = { - description: 'Internal Server Error', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - */ - mw.useRegistry(), - mw.validateUser, - mw.onlySecretariat, - param(['shortname']).isString().trim(), - body(['reports_to']).not().exists().withMessage('reports_to must not be present'), - parseError, - parsePostParams, - controller.UPDATE_ORG -) - -router.delete( - '/registryOrg/:identifier', - /* - #swagger.tags = ['Registry Organization'] - #swagger.operationId = 'deleteRegistryOrg' - #swagger.ignore = true - #swagger.summary = "Deletes an existing registry organization (accessible to Secretariat only)" - #swagger.description = " -

Access Control

-

Only users with Secretariat role can access this endpoint

-

Expected Behavior

-

Secretariat: Deletes an existing registry organization

- #swagger.parameters['identifier'] = { - in: 'path', - description: 'The identifier of the registry organization to delete', - required: true, - type: 'string' - } - #swagger.parameters['$ref'] = [ - '#/components/parameters/apiEntityHeader', - '#/components/parameters/apiUserHeader', - '#/components/parameters/apiSecretHeader' - ] - #swagger.responses[200] = { - description: 'The registry organization was successfully deleted', - content: { - "application/json": { - schema: { - type: 'object', - properties: { - message: { - type: 'string', - description: 'Message describing successful deletion operation' - } - } - } - } - } - } - #swagger.responses[400] = { - description: 'Bad Request', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/bad-request.json' } - } - } - } - #swagger.responses[401] = { - description: 'Not Authenticated', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[403] = { - description: 'Forbidden', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[404] = { - description: 'Not Found', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - */ - mw.useRegistry(), - mw.validateUser, - mw.onlySecretariat, - param(['identifier']).isString().trim(), - parseError, - parseDeleteParams, - controller.DELETE_ORG -) - -router.get('/registryOrg/:shortname/users', - /* - #swagger.tags = ['Registry User'] - #swagger.operationId = 'registryUserOrgAll' - #swagger.ignore = true - #swagger.summary = "Retrieves all users for the organization with the specified short name (accessible to Secretariat only)" - #swagger.description = " -

Access Control

-

Only users with Secretariat role can access this endpoint

-

Expected Behavior

-

Secretariat: Retrieves all user information for any organization

" - #swagger.parameters['shortname'] = { description: 'The shortname of the organization' } - #swagger.parameters['$ref'] = [ - '#/components/parameters/pageQuery', - '#/components/parameters/apiEntityHeader', - '#/components/parameters/apiUserHeader', - '#/components/parameters/apiSecretHeader' - ] - #swagger.responses[200] = { - description: 'Returns all users for the organization, along with pagination fields if results span multiple pages of data', - content: { - "application/json": { - schema: { $ref: '../schemas/registry-user/list-registry-users-response.json' } - } - } - } - #swagger.responses[400] = { - description: 'Bad Request', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/bad-request.json' } - } - } - } - #swagger.responses[401] = { - description: 'Not Authenticated', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[403] = { - description: 'Forbidden', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[404] = { - description: 'Not Found', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[500] = { - description: 'Internal Server Error', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - */ - mw.useRegistry(), - mw.validateUser, - mw.onlySecretariat, - param(['shortname']).isString().trim().notEmpty().isLength({ min: CONSTANTS.MIN_SHORTNAME_LENGTH, max: CONSTANTS.MAX_SHORTNAME_LENGTH }), - query(['page']).optional().isInt({ min: CONSTANTS.PAGINATOR_PAGE }), - parseError, - parseGetParams, - controller.USER_ALL) - -router.post('/registryOrg/:shortname/user', - /* - #swagger.tags = ['Registry User'] - #swagger.operationId = 'RegistryUserCreateSingle' - #swagger.ignore = true - #swagger.summary = "Create a user with the provided short name as the owning organization (accessible to Secretariat only)" - #swagger.description = " -

Access Control

-

Only users with Secretariat role can access this endpoint

-

Expected Behavior

-

Secretariat: Creates a user for any organization

" - #swagger.parameters['shortname'] = { description: 'The shortname of the organization' } - #swagger.parameters['$ref'] = [ - '#/components/parameters/apiEntityHeader', - '#/components/parameters/apiUserHeader', - '#/components/parameters/apiSecretHeader' - ] - #swagger.requestBody = { - required: true, - content: { - 'application/json': { - schema: { $ref: '../schemas/registry-user/create-registry-user-request.json' }, - } - } - } - #swagger.responses[200] = { - description: 'Returns the new user information (with the secret)', - content: { - "application/json": { - schema: { $ref: '../schemas/registry-user/create-registry-user-response.json' }, - } - } - } - #swagger.responses[400] = { - description: 'Bad Request', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/bad-request.json' } - } - } - } - #swagger.responses[401] = { - description: 'Not Authenticated', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[403] = { - description: 'Forbidden', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[404] = { - description: 'Not Found', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[500] = { - description: 'Internal Server Error', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - */ - mw.useRegistry(), - mw.validateUser, - mw.onlySecretariat, - param(['shortname']).isString().trim().notEmpty().isLength({ min: CONSTANTS.MIN_SHORTNAME_LENGTH, max: CONSTANTS.MAX_SHORTNAME_LENGTH }), - - parseError, - parsePostParams, - controller.USER_CREATE_SINGLE) - -module.exports = router diff --git a/src/controller/registry-org.controller/registry-org.middleware.js b/src/controller/registry-org.controller/registry-org.middleware.js deleted file mode 100644 index 2d79faf6a..000000000 --- a/src/controller/registry-org.controller/registry-org.middleware.js +++ /dev/null @@ -1,74 +0,0 @@ -const utils = require('../../utils/utils') -const getConstants = require('../../constants').getConstants -const { validationResult } = require('express-validator') -const errors = require('./error') -const error = new errors.RegistryOrgControllerError() - -function parsePostParams (req, res, next) { - req.body = utils.deepRemoveEmpty(req.body) - utils.reqCtxMapping(req, 'body', []) - utils.reqCtxMapping(req, 'params', ['identifier', 'shortname']) - utils.reqCtxMapping(req, 'query', [ - 'long_name', 'short_name', 'aliases', - 'cve_program_org_function', 'authority.active_roles', - 'oversees', - 'top_level_root', 'users', - 'charter_or_scope', 'disclosure_policy', 'product_list', - 'id_quota', - 'private_contacts', 'contact_info.websites', 'contact_info.emails', 'contact_info.phone', - 'partner_role_type', - 'partner_number', - 'partner_country', - 'program_data.cve_website_update_date', - 'program_data.cve_website_update_needed', - 'program_data.status', - 'advisory_locations', - 'advisory_location_require_credentials', - 'vulnerability_advisory_location_for_web_scraping', - 'industry', - 'tl_root_start_date', - 'is_cna_discussion_list' - ]) - next() -} - -function parseGetParams (req, res, next) { - utils.reqCtxMapping(req, 'params', ['identifier', 'shortname']) - utils.reqCtxMapping(req, 'query', ['page']) - next() -} - -function parseDeleteParams (req, res, next) { - utils.reqCtxMapping(req, 'params', ['identifier']) - next() -} - -function isOrgRole (val) { - const CONSTANTS = getConstants() - - val.forEach(role => { - if (!CONSTANTS.ORG_ROLES.includes(role)) { - throw new Error('Organization role does not exist.') - } - }) - - return true -} - -function parseError (req, res, next) { - const err = validationResult(req).formatWith(({ location, msg, param, value, nestedErrors }) => { - return { msg: msg, param: param, location: location } - }) - if (!err.isEmpty()) { - return res.status(400).json(error.badInput(err.array())) - } - next() -} - -module.exports = { - parsePostParams, - parseGetParams, - parseError, - parseDeleteParams, - isOrgRole -} diff --git a/src/controller/registry-user.controller/index.js b/src/controller/registry-user.controller/index.js deleted file mode 100644 index 179987a83..000000000 --- a/src/controller/registry-user.controller/index.js +++ /dev/null @@ -1,397 +0,0 @@ -const express = require('express') -const router = express.Router() -const mw = require('../../middleware/middleware') -const { param, query } = require('express-validator') -const controller = require('./registry-user.controller') -const { parseGetParams, parsePostParams, parseDeleteParams, parseError } = require('./registry-user.middleware') -const getConstants = require('../../constants').getConstants -const CONSTANTS = getConstants() - -router.get('/registryUser', - /* - #swagger.tags = ['Secretariat Only Utility Endpoints'] - #swagger.operationId = 'getAllRegistryUsers' - #swagger.ignore = true - #swagger.summary = "Retrieves information about all registry users (accessible to Secretariat only)" - #swagger.description = " -

Access Control

-

Only users with Secretariat role can access this endpoint

-

Expected Behavior

-

Secretariat: Retrieves a list of all registry users

- #swagger.parameters['$ref'] = [ - '#/components/parameters/pageQuery', - '#/components/parameters/apiEntityHeader', - '#/components/parameters/apiUserHeader', - '#/components/parameters/apiSecretHeader' - ] - #swagger.responses[200] = { - description: 'A list of all registry users, along with pagination fields if results span multiple pages of data', - content: { - "application/json": { - schema: { $ref: '../schemas/registry-user/list-registry-users-response.json' } - } - } - } - #swagger.responses[400] = { - description: 'Bad Request', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/bad-request.json' } - } - } - } - #swagger.responses[401] = { - description: 'Not Authenticated', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[403] = { - description: 'Forbidden', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[500] = { - description: 'Internal Server Error', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - */ - mw.validateUser, - mw.onlySecretariat, - query(['page']).optional().isInt({ min: CONSTANTS.PAGINATOR_PAGE }), - query(['page']).custom((val) => { return mw.containsNoInvalidCharacters(val) }), - parseError, - parseGetParams, - controller.ALL_USERS -) - -router.get('/registryUser/:identifier', -/* - #swagger.tags = ['Secretariat Only Utility Endpoints'] - #swagger.operationId = 'getSingleRegistryUser' - #swagger.ignore = true - #swagger.summary = "Retrieves information about a specific registry user (accessible to Secretariat only)" - #swagger.description = " -

Access Control

-

Only users with Secretariat role can access this endpoint

-

Expected Behavior

-

Secretariat: Retrieves information about the specified registry user

- #swagger.parameters['identifier'] = { - in: 'path', - description: 'The identifier of the registry user', - required: true, - type: 'string' - } - #swagger.parameters['$ref'] = [ - '#/components/parameters/apiEntityHeader', - '#/components/parameters/apiUserHeader', - '#/components/parameters/apiSecretHeader' - ] - #swagger.responses[200] = { - description: 'The requested registry user information is returned', - content: { - "application/json": { - schema: { $ref: '../schemas/registry-user/get-registry-user-response.json' } - } - } - } - #swagger.responses[400] = { - description: 'Bad Request', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/bad-request.json' } - } - } - } - #swagger.responses[401] = { - description: 'Not Authenticated', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[404] = { - description: 'Not Found', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[500] = { - description: 'Internal Server Error', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - */ - mw.validateUser, - mw.onlySecretariat, - param(['identifier']).isString().trim(), - parseError, - parseGetParams, - controller.SINGLE_USER -) - -router.post('/registryUser/:shortname', - /* - #swagger.tags = ['Secretariat Only Utility Endpoints'] - #swagger.operationId = 'createRegistryUser' - #swagger.ignore = true - #swagger.summary = "Creates a new registry user (accessible to Secretariat only)" - #swagger.description = " -

Access Control

-

Only users with Secretariat role can access this endpoint

-

Expected Behavior

-

Secretariat: Creates a new registry user

- #swagger.parameters['$ref'] = [ - '#/components/parameters/apiEntityHeader', - '#/components/parameters/apiUserHeader', - '#/components/parameters/apiSecretHeader' - ] - #swagger.requestBody = { - required: true, - content: { - 'application/json': { - schema: { $ref: '../schemas/registry-user/create-registry-user-request.json' } - } - } - } - #swagger.responses[201] = { - description: 'The registry user was successfully created', - content: { - "application/json": { - schema: { $ref: '../schemas/registry-user/create-registry-user-response.json' } - } - } - } - #swagger.responses[400] = { - description: 'Bad Request', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/bad-request.json' } - } - } - } - #swagger.responses[401] = { - description: 'Not Authenticated', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[403] = { - description: 'Forbidden', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[500] = { - description: 'Internal Server Error', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - */ - mw.validateUser, - mw.onlySecretariat, - param(['shortname']).isString().trim(), - parseError, - parsePostParams, - controller.CREATE_USER -) - -router.put('/registryUser/:identifier', - /* - #swagger.tags = ['Secretariat Only Utility Endpoints'] - #swagger.operationId = 'updateRegistryUser' - #swagger.ignore = true - #swagger.summary = "Updates an existing registry user (accessible to Secretariat only)" - #swagger.description = " -

Access Control

-

Only users with Secretariat role can access this endpoint

-

Expected Behavior

-

Secretariat: Updates an existing registry user

- #swagger.parameters['identifier'] = { - in: 'path', - description: 'The identifier of the registry user to update', - required: true, - type: 'string' - } - #swagger.parameters['$ref'] = [ - '#/components/parameters/apiEntityHeader', - '#/components/parameters/apiUserHeader', - '#/components/parameters/apiSecretHeader' - ] - #swagger.requestBody = { - required: true, - content: { - 'application/json': { - schema: { $ref: '../schemas/registry-user/update-registry-user-request.json' } - } - } - } - #swagger.responses[200] = { - description: 'The registry user was successfully updated', - content: { - "application/json": { - schema: { $ref: '../schemas/registry-user/update-registry-user-response.json' } - } - } - } - #swagger.responses[400] = { - description: 'Bad Request', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/bad-request.json' } - } - } - } - #swagger.responses[401] = { - description: 'Not Authenticated', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[403] = { - description: 'Forbidden', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[404] = { - description: 'Not Found', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[500] = { - description: 'Internal Server Error', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - */ - mw.validateUser, - mw.onlySecretariat, - param(['identifier']).isString().trim(), - // TODO: do more validation here - parseError, - parsePostParams, - controller.UPDATE_USER -) - -router.delete( - '/registryUser/:identifier', - /* - #swagger.tags = ['Secretariat Only Utility Endpoints'] - #swagger.operationId = 'deleteRegistryUser' - #swagger.ignore = true - #swagger.summary = "Deletes an existing registry user (accessible to Secretariat only)" - #swagger.description = " -

Access Control

-

Only users with Secretariat role can access this endpoint

-

Expected Behavior

-

Secretariat: Deletes an existing registry user

- #swagger.parameters['identifier'] = { - in: 'path', - description: 'The identifier of the registry user to delete', - required: true, - type: 'string' - } - #swagger.parameters['$ref'] = [ - '#/components/parameters/apiEntityHeader', - '#/components/parameters/apiUserHeader', - '#/components/parameters/apiSecretHeader' - ] - #swagger.responses[200] = { - description: 'The registry user was successfully deleted', - content: { - "application/json": { - schema: { - type: 'object', - properties: { - message: { - type: 'string', - description: 'Message describing successful deletion operation' - } - } - } - } - } - } - #swagger.responses[400] = { - description: 'Bad Request', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/bad-request.json' } - } - } - } - #swagger.responses[401] = { - description: 'Not Authenticated', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[403] = { - description: 'Forbidden', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[404] = { - description: 'Not Found', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[500] = { - description: 'Internal Server Error', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - */ - mw.validateUser, - mw.onlySecretariat, - param(['identifier']).isString().trim(), - parseError, - parseDeleteParams, - controller.DELETE_USER -) - -module.exports = router diff --git a/src/controller/registry-user.controller/registry-user.middleware.js b/src/controller/registry-user.controller/registry-user.middleware.js deleted file mode 100644 index cffcb7e6f..000000000 --- a/src/controller/registry-user.controller/registry-user.middleware.js +++ /dev/null @@ -1,42 +0,0 @@ -const utils = require('../../utils/utils') -const { validationResult } = require('express-validator') -const errors = require('../registry-org.controller/error') -const error = new errors.RegistryOrgControllerError() - -function parsePostParams (req, res, next) { - utils.reqCtxMapping(req, 'body', []) - utils.reqCtxMapping(req, 'params', ['identifier', 'shortname']) - utils.reqCtxMapping(req, 'query', [ - 'new_username', - 'name.first', 'name.last', 'name.middle', 'name.suffix' - ]) - next() -} - -function parseGetParams (req, res, next) { - utils.reqCtxMapping(req, 'params', ['identifier']) - utils.reqCtxMapping(req, 'query', ['page']) - next() -} - -function parseDeleteParams (req, res, next) { - utils.reqCtxMapping(req, 'params', ['identifier']) - next() -} - -function parseError (req, res, next) { - const err = validationResult(req).formatWith(({ location, msg, param, value, nestedErrors }) => { - return { msg: msg, param: param, location: location } - }) - if (!err.isEmpty()) { - return res.status(400).json(error.badInput(err.array())) - } - next() -} - -module.exports = { - parsePostParams, - parseGetParams, - parseDeleteParams, - parseError -} diff --git a/src/controller/registry.controller/index.js b/src/controller/registry.controller/index.js new file mode 100644 index 000000000..36874272e --- /dev/null +++ b/src/controller/registry.controller/index.js @@ -0,0 +1,1430 @@ +const express = require('express') +const router = express.Router() +const mw = require('../../middleware/middleware') +const errorMsgs = require('../../middleware/errorMessages') +const controller = require('../org.controller/org.controller') +const registryOrgController = require('./org.registry.controller') +const registryUserController = require('./user.registry.controller') +const { body, param, query } = require('express-validator') +const { parseGetParams, parsePostParams, parsePutParams, parseDeleteParams, parseError, isUserRole, isValidUsername } = require('../org.controller/org.middleware') +// Only God and Javascript know swhy its saying it is not used when it is..... +// eslint-disable-next-line no-unused-vars +const { toUpperCaseArray, isFlatStringArray } = require('../../middleware/middleware') +const getConstants = require('../../../src/constants').getConstants +const CONSTANTS = getConstants() +const { parseGetParams: parseUserGetParams, parseError: parseUserError } = require('../user.controller/user.middleware') + +router.get('/registry/org', + /* + #swagger.tags = ['Registry Organization'] + #swagger.operationId = 'registryOrgAll' + #swagger.summary = "Retrieves all registry organizations (accessible to Secretariat)" + #swagger.description = " +

Access Control

+

User must belong to an organization with the Secretariat role

+

Expected Behavior

+

Secretariat: Retrieves information about all registry organizations

" + #swagger.parameters['$ref'] = [ + '#/components/parameters/pageQuery', + '#/components/parameters/apiEntityHeader', + '#/components/parameters/apiUserHeader', + '#/components/parameters/apiSecretHeader' + ] + #swagger.responses[200] = { + description: 'Returns information about all registry organizations, along with pagination fields if results span multiple pages of data', + content: { + "application/json": { + schema: { + $ref: '../schemas/registry-org/list-registry-orgs-response.json' + } + } + } + } + #swagger.responses[400] = { + description: 'Bad Request', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/bad-request.json' } + } + } + } + #swagger.responses[401] = { + description: 'Not Authenticated', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[403] = { + description: 'Forbidden', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[404] = { + description: 'Not Found', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[500] = { + description: 'Internal Server Error', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + */ + mw.useRegistry(), + mw.validateUser, + mw.onlySecretariat, + query().custom((query) => { return mw.validateQueryParameterNames(query, ['page']) }), + query(['page']).custom((val) => { return mw.containsNoInvalidCharacters(val) }), + query(['page']).optional().isInt({ min: CONSTANTS.PAGINATOR_PAGE }), + parseError, + parseGetParams, + registryOrgController.ALL_ORGS +) + +router.get('/registry/org/:shortname/users', + /* + #swagger.tags = ['Registry User'] + #swagger.operationId = 'registryOrgUsersAll' + #swagger.summary = "Retrieves all users for the organization with the specified short name (accessible to same-organization users or Secretariat)" + #swagger.description = " +

Access Control

+

Authenticated users can access this endpoint only for their own organization. Secretariat users can access any organization.

+

Expected Behavior

+

Regular, CNA & Admin Users: Retrieves information about users in the same organization

+

Secretariat: Retrieves all user information for any organization

" + #swagger.parameters['shortname'] = { description: 'The shortname of the organization' } + #swagger.parameters['$ref'] = [ + '#/components/parameters/pageQuery', + '#/components/parameters/apiEntityHeader', + '#/components/parameters/apiUserHeader', + '#/components/parameters/apiSecretHeader' + ] + #swagger.responses[200] = { + description: 'Returns all users for the organization, along with pagination fields if results span multiple pages of data', + content: { + "application/json": { + schema: { + $ref: '../schemas/registry-user/list-registry-users-response.json' + }, + example: { + "totalCount": 1, + "itemsPerPage": 100, + "pageCount": 1, + "currentPage": 1, + "prevPage": null, + "nextPage": null, + "users": [ + { + "UUID": "fe566221-6a2c-4279-8800-4d3795325997", + "username": "jdoe", + "name": { + "first": "John", + "last": "Doe" + }, + "role": "ADMIN", + "status": "active", + "created": "2021-02-12T17:15:37.382Z", + "last_updated": "2021-02-12T17:15:37.382Z" + } + ] + } + } + } + } + #swagger.responses[400] = { + description: 'Bad Request', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/bad-request.json' } + } + } + } + #swagger.responses[401] = { + description: 'Not Authenticated', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[403] = { + description: 'Forbidden', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[404] = { + description: 'Not Found', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[500] = { + description: 'Internal Server Error', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + */ + mw.useRegistry(), + mw.validateUser, + param(['shortname']).isString().trim().notEmpty().isLength({ min: CONSTANTS.MIN_SHORTNAME_LENGTH, max: CONSTANTS.MAX_SHORTNAME_LENGTH }), + query().custom((query) => { return mw.validateQueryParameterNames(query, ['page']) }), + query(['page']).custom((val) => { return mw.containsNoInvalidCharacters(val) }), + query(['page']).optional().isInt({ min: CONSTANTS.PAGINATOR_PAGE }), + parseError, + parseGetParams, + registryOrgController.USER_ALL) + +router.get('/registry/org/:shortname/id_quota', + /* + #swagger.tags = ['Registry Organization'] + #swagger.operationId = 'registryOrgIdQuota' + #swagger.summary = "Retrieves an organization's CVE ID quota (accessible to same-organization users or Secretariat)" + #swagger.description = " +

Access Control

+

Authenticated users can access this endpoint only for their own organization. Secretariat users can access any organization.

+

Expected Behavior

+

Regular, CNA & Admin Users: Retrieves the CVE ID quota for the user's organization

+

Secretariat: Retrieves the CVE ID quota for any organization

" + #swagger.parameters['shortname'] = { description: 'The shortname of the organization' } + #swagger.parameters['$ref'] = [ + '#/components/parameters/apiEntityHeader', + '#/components/parameters/apiUserHeader', + '#/components/parameters/apiSecretHeader' + ] + #swagger.responses[200] = { + description: 'Returns the CVE ID quota for an organization', + content: { + "application/json": { + schema: { + $ref: '../schemas/registry-org/get-registry-org-quota-response.json' + } + } + } + } + #swagger.responses[400] = { + description: 'Bad Request', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/bad-request.json' } + } + } + } + #swagger.responses[401] = { + description: 'Not Authenticated', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[403] = { + description: 'Forbidden', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[404] = { + description: 'Not Found', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[500] = { + description: 'Internal Server Error', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + */ + mw.useRegistry(), + mw.validateUser, + param(['shortname']).isString().trim().notEmpty().isLength({ min: CONSTANTS.MIN_SHORTNAME_LENGTH, max: CONSTANTS.MAX_SHORTNAME_LENGTH }), + query().custom((query) => { return mw.validateQueryParameterNames(query, ['']) }), + parseError, + parseGetParams, + controller.ORG_ID_QUOTA) + +router.get('/registry/org/:identifier', + /* + #swagger.tags = ['Registry Organization'] + #swagger.operationId = 'registryOrgSingle' + #swagger.summary = "Retrieves information about the registry organization specified by short name or UUID (accessible to same-organization users or Secretariat)" + #swagger.description = " +

Access Control

+

Authenticated users can access this endpoint only for their own organization. Secretariat users can access any organization.

+

Expected Behavior

+

Regular, CNA & Admin Users: Retrieves registry organization record for the specified shortname or UUID if it is the user's organization

+

Secretariat: Retrieves information about any registry organization

" + #swagger.parameters['identifier'] = { description: 'The shortname or UUID of the registry organization' } + #swagger.parameters['expand'] = { + in: 'query', + description: 'Optional expanded related data. Accepted value: users.', + required: false, + schema: { + type: 'string', + enum: ['users'] + } + } + #swagger.parameters['$ref'] = [ + '#/components/parameters/apiEntityHeader', + '#/components/parameters/apiUserHeader', + '#/components/parameters/apiSecretHeader' + ] + #swagger.responses[200] = { + description: 'Returns the registry organization information', + content: { + "application/json": { + schema: { + $ref: '../schemas/registry-org/get-registry-org-response.json' + } + } + } + } + #swagger.responses[400] = { + description: 'Bad Request', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/bad-request.json' } + } + } + } + #swagger.responses[401] = { + description: 'Not Authenticated', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[403] = { + description: 'Forbidden', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[404] = { + description: 'Not Found', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[500] = { + description: 'Internal Server Error', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + */ + mw.useRegistry(), + mw.validateUser, + query().custom((query) => { return mw.validateQueryParameterNames(query, ['expand']) }), + query(['expand']).custom((val) => { return mw.containsNoInvalidCharacters(val) }), + query(['expand']).optional().isIn(['users']), + parseError, + parseGetParams, + registryOrgController.SINGLE_ORG +) + +router.get('/registry/org/:shortname/user/:username', + /* + #swagger.tags = ['Registry User'] + #swagger.operationId = 'registryUserSingle' + #swagger.summary = "Retrieves information about a user for the specified username and organization short name (accessible to same-organization users or Secretariat)" + #swagger.description = " +

Access Control

+

Authenticated users can access this endpoint only for users in their own organization. Secretariat users can access any user.

+

Expected Behavior

+

Regular, CNA & Admin Users: Retrieves information about a registry user in the same organization

+

Secretariat: Retrieves any registry user's information

" + #swagger.parameters['$ref'] = [ + '#/components/parameters/apiEntityHeader', + '#/components/parameters/apiUserHeader', + '#/components/parameters/apiSecretHeader' + ] + #swagger.parameters['shortname'] = { + description: 'The shortname of the organization' + } + #swagger.parameters['username'] = { + description: 'The username of the registry user', + schema: { + type: 'string', + pattern: '^[a-zA-Z0-9._@-]+$' + } + } + #swagger.responses[200] = { + description: 'Returns information about the specified registry user', + content: { + "application/json": { + schema: { $ref: '../schemas/registry-user/get-registry-user-response.json' } + } + } + } + #swagger.responses[400] = { + description: 'Bad Request', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/bad-request.json' } + } + } + } + #swagger.responses[401] = { + description: 'Not Authenticated', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[403] = { + description: 'Forbidden', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[404] = { + description: 'Not Found', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[500] = { + description: 'Internal Server Error', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + */ + mw.useRegistry(), + mw.validateUser, + param(['shortname']).isString().trim().notEmpty().isLength({ min: CONSTANTS.MIN_SHORTNAME_LENGTH, max: CONSTANTS.MAX_SHORTNAME_LENGTH }), + param(['username']).isString().trim().notEmpty().custom(isValidUsername), + query().custom((query) => { return mw.validateQueryParameterNames(query, ['']) }), + parseError, + parseGetParams, + registryUserController.SINGLE_USER +) + +router.post('/registry/org', + /* + #swagger.tags = ['Registry Organization'] + #swagger.operationId = 'registryOrgCreateSingle' + #swagger.summary = "Creates an organization (accessible to Secretariat)" + #swagger.description = " +

Access Control

+

User must belong to an organization with the Secretariat role

+

Expected Behavior

+

Secretariat: Creates a new organization

" + #swagger.parameters['$ref'] = [ + '#/components/parameters/apiEntityHeader', + '#/components/parameters/apiUserHeader', + '#/components/parameters/apiSecretHeader' + ] + #swagger.requestBody = { + required: true, + content: { + 'application/json': { + schema: { + anyOf: [ + { $ref: '../schemas/registry-org/SecretariatOrg.json' }, + { $ref: '../schemas/registry-org/CNAOrg.json' }, + { $ref: '../schemas/registry-org/ADPOrg.json' }, + { $ref: '../schemas/registry-org/BulkDownloadOrg.json' } + ] + }, + example: { + short_name: 'fake_company', + long_name: 'Fake Company', + id_quota: 1000, + authority: ['CNA'] + } + } + } + } + #swagger.responses[200] = { + description: 'Returns information about all organizations, along with pagination fields if results span multiple pages of data', + content: { + "application/json": { + schema: { + $ref: '../schemas/registry-org/list-registry-orgs-response.json' + } + } + } + } + #swagger.responses[400] = { + description: 'Bad Request', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/bad-request.json' } + } + } + } + #swagger.responses[401] = { + description: 'Not Authenticated', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[403] = { + description: 'Forbidden', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[404] = { + description: 'Not Found', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[500] = { + description: 'Internal Server Error', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + */ + mw.useRegistry(), + mw.validateUser, + mw.onlySecretariat, + query().custom((query) => { return mw.validateQueryParameterNames(query, ['']) }), + parsePostParams, + parseError, + registryOrgController.CREATE_ORG +) + +router.put('/registry/org/:shortname', + /* + #swagger.tags = ['Registry Organization'] + #swagger.operationId = 'registryOrgUpdateSingle' + #swagger.summary = "Updates information about the organization specified by short name (accessible to Secretariat or same-organization Admin)" + #swagger.description = " +

Access Control

+

User must belong to an organization with the Secretariat role or be an Admin of the requested organization.

+

With Joint Approval required for the following fields:

+

Expected Behavior

+ This endpoint expects a full organization object in the request body. +

Secretariat: Updates any organization's information

+

Organization Admin: Requests changes to its organization's information

+
    +
  • short_name
  • +
  • long_name
  • +
  • authority
  • +
  • aliases
  • +
  • oversees
  • +
  • top_level_root
  • +
  • charter_or_scope
  • +
  • product_list
  • +
  • disclosure_policy
  • +
  • contact_info.websites
  • +
  • contact_info.emails
  • +
  • contact_info.phone
  • +
  • partner_role_type
  • +
  • partner_country
  • +
  • advisory_locations
  • +
  • advisory_location_require_credentials
  • +
  • vulnerability_advisory_location_for_web_scraping
  • +
  • industry
  • +
  • tl_root_start_date
  • +
  • is_cna_discussion_list
  • +
" + #swagger.parameters['shortname'] = { description: 'The shortname of the organization' } + #swagger.parameters['$ref'] = [ + '#/components/parameters/apiEntityHeader', + '#/components/parameters/apiUserHeader', + '#/components/parameters/apiSecretHeader' + ] + #swagger.requestBody = { + required: true, + content: { + 'application/json': { + schema: { + $ref: '../schemas/registry-org/update-registry-org-request.json' + }, + example: { + short_name: 'fake_company', + long_name: 'Fake Company', + id_quota: 1000, + authority: ['CNA'] + } + } + } + } + #swagger.responses[200] = { + description: 'Returns information about the organization updated', + content: { + "application/json": { + schema: { + $ref: '../schemas/registry-org/update-registry-org-response.json' + } + } + } + } + #swagger.responses[400] = { + description: 'Bad Request', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/bad-request.json' } + } + } + } + #swagger.responses[401] = { + description: 'Not Authenticated', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[403] = { + description: 'Forbidden', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[404] = { + description: 'Not Found', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[500] = { + description: 'Internal Server Error', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + */ + mw.useRegistry(), + mw.validateUser, + // mw.onlySecretariat, + parseError, + parsePutParams, + registryOrgController.UPDATE_ORG +) + +router.delete('/registry/org/:shortname', + /* + #swagger.tags = ['Registry Organization'] + #swagger.operationId = 'registryOrgDeleteSingle' + #swagger.summary = "Deletes the registry organization specified by short name (accessible to Secretariat only)" + #swagger.description = " +

Access Control

+

User must belong to an organization with the Secretariat role

+

Expected Behavior

+

Secretariat: Deletes the specified registry organization

" + #swagger.parameters['shortname'] = { description: 'The shortname of the registry organization' } + #swagger.parameters['$ref'] = [ + '#/components/parameters/apiEntityHeader', + '#/components/parameters/apiUserHeader', + '#/components/parameters/apiSecretHeader' + ] + #swagger.responses[200] = { + description: 'Confirms deletion of the registry organization', + content: { + "application/json": { + schema: { $ref: '../schemas/registry-org/delete-registry-org-response.json' } + } + } + } + #swagger.responses[400] = { + description: 'Bad Request', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/bad-request.json' } + } + } + } + #swagger.responses[401] = { + description: 'Not Authenticated', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[403] = { + description: 'Forbidden', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[404] = { + description: 'Not Found', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[500] = { + description: 'Internal Server Error', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + */ + mw.useRegistry(), + mw.validateUser, + mw.onlySecretariat, + param(['shortname']).isString().trim().notEmpty().isLength({ min: CONSTANTS.MIN_SHORTNAME_LENGTH, max: CONSTANTS.MAX_SHORTNAME_LENGTH }), + parseError, + parseDeleteParams, + registryOrgController.DELETE_ORG +) + +router.post('/registry/org/:shortname/user', + /* + #swagger.tags = ['Registry User'] + #swagger.operationId = 'registryUserCreateSingle' + #swagger.summary = "Create a user with the provided short name as the owning organization (accessible to Secretariat or target organization Admin)" + #swagger.description = " +

Access Control

+

User must belong to an organization with the Secretariat role or be an Admin of the target organization

+

Expected Behavior

+

Admin User: Creates a user for the Admin's organization

+

Secretariat: Creates a user for any organization

" + #swagger.parameters['shortname'] = { description: 'The shortname of the organization' } + #swagger.parameters['$ref'] = [ + '#/components/parameters/apiEntityHeader', + '#/components/parameters/apiUserHeader', + '#/components/parameters/apiSecretHeader' + ] + #swagger.requestBody = { + required: true, + content: { + 'application/json': { + schema: + { $ref: '../schemas/registry-user/create-registry-user-request.json' } + }, + example: { + "username": "jdoe", + "status": "active", + "name": { + "first": "John", + "last": "Doe" + } + } + } + } + #swagger.responses[200] = { + description: 'Returns the new user information (with the secret)', + content: { + "application/json": { + schema: + { $ref: '../schemas/registry-user/create-registry-user-response.json' } + }, + example: { + "message": "jdoe was successfully created.", + "created": { + "UUID": "fe566221-6a2c-4279-8800-4d3795325997", + "username": "jdoe", + "name": { + "first": "John", + "last": "Doe" + }, + "status": "active", + "secret": "12345-abcde-67890", + "created": "2021-02-12T17:15:37.382Z", + "last_updated": "2021-02-12T17:15:37.382Z" + } + } + } + } + #swagger.responses[400] = { + description: 'Bad Request', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/bad-request.json' } + } + } + } + #swagger.responses[401] = { + description: 'Not Authenticated', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[403] = { + description: 'Forbidden', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[404] = { + description: 'Not Found', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[500] = { + description: 'Internal Server Error', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + */ + mw.useRegistry(), + mw.validateUser, + mw.onlySecretariatOrAdmin, + mw.onlyOrgWithPartnerRole, + param(['shortname']).isString().trim().notEmpty().isLength({ min: CONSTANTS.MIN_SHORTNAME_LENGTH, max: CONSTANTS.MAX_SHORTNAME_LENGTH }), + body(['org_uuid']).optional().isString().trim(), + body(['uuid']).optional().isString().trim(), + body(['name.first']).optional().isString().trim().isLength({ max: CONSTANTS.MAX_FIRSTNAME_LENGTH }).withMessage(errorMsgs.FIRSTNAME_LENGTH), + body(['name.last']).optional().isString().trim().isLength({ max: CONSTANTS.MAX_LASTNAME_LENGTH }).withMessage(errorMsgs.LASTNAME_LENGTH), + body(['name.middle']).optional().isString().trim().isLength({ max: CONSTANTS.MAX_MIDDLENAME_LENGTH }).withMessage(errorMsgs.MIDDLENAME_LENGTH), + body(['name.suffix']).optional().isString().trim().isLength({ max: CONSTANTS.MAX_SUFFIX_LENGTH }).withMessage(errorMsgs.SUFFIX_LENGTH), + body(['authority.active_roles']).optional() + .custom(mw.isFlatStringArray) + .bail() + .customSanitizer(toUpperCaseArray) + .custom(isUserRole), + parseError, + parsePostParams, + registryOrgController.USER_CREATE_SINGLE +) + +router.put('/registry/org/:shortname/user/:username', + /* + #swagger.tags = ['Registry User'] + #swagger.operationId = 'registryUserUpdateSingle' + #swagger.summary = "Updates information about a user for the specified username and organization shortname (accessible to self, same-organization Admins, or Secretariat)" + #swagger.description = " +

Access Control

+

Authenticated users can update their own name fields. Organization admins can update users in their organization. Secretariat users can update users in any organization.

+

Expected Behavior

+

Regular User: Updates the user's own information. Only name fields may be changed.

+

Admin User: Updates information about a user in the Admin's organization. Allowed to change all fields except org_short_name.

+

Secretariat: Updates information about a user in any organization. Allowed to change all fields.

" + #swagger.parameters['shortname'] = { description: 'The shortname of the organization' } + #swagger.parameters['username'] = { description: 'The username of the user' } + #swagger.parameters['$ref'] = [ + '#/components/parameters/active', + '#/components/parameters/orgShortname', + '#/components/parameters/apiEntityHeader', + '#/components/parameters/apiUserHeader', + '#/components/parameters/apiSecretHeader' + ] + #swagger.responses[200] = { + description: 'Returns the updated user information', + content: { + "application/json": { + schema: { $ref: '../schemas/registry-user/update-registry-user-response.json' }, + example: { + "message": "jdoe was successfully updated.", + "updated": { + "UUID": "fe566221-6a2c-4279-8800-4d3795325997", + "username": "jdoe", + "name": { + "first": "John", + "last": "Doe" + }, + "status": "active", + "created": "2021-02-12T17:15:37.382Z", + "last_updated": "2021-02-12T17:15:37.382Z" + } + } + } + } + } + #swagger.responses[400] = { + description: 'Bad Request', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/bad-request.json' } + } + } + } + #swagger.responses[401] = { + description: 'Not Authenticated', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[403] = { + description: 'Forbidden', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[404] = { + description: 'Not Found', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[500] = { + description: 'Internal Server Error', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + */ + mw.useRegistry(), + mw.validateUser, + mw.onlyOrgWithPartnerRole, + parseError, + parsePutParams, + registryUserController.UPDATE_USER) + +router.delete('/registry/org/:shortname/user/:username', + /* + #swagger.tags = ['Registry User'] + #swagger.operationId = 'registryUserDeleteSingle' + #swagger.summary = "Deletes the registry user specified by organization and username (accessible to Secretariat only)" + #swagger.description = " +

Access Control

+

User must belong to an organization with the Secretariat role

+

Expected Behavior

+

Secretariat: Deletes the specified user from the specified organization

" + #swagger.parameters['shortname'] = { description: 'The shortname of the organization' } + #swagger.parameters['username'] = { description: 'The username of the user' } + #swagger.parameters['$ref'] = [ + '#/components/parameters/apiEntityHeader', + '#/components/parameters/apiUserHeader', + '#/components/parameters/apiSecretHeader' + ] + #swagger.responses[200] = { + description: 'Confirms deletion of the registry user', + content: { + "application/json": { + schema: { $ref: '../schemas/registry-user/delete-registry-user-response.json' } + } + } + } + #swagger.responses[400] = { + description: 'Bad Request', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/bad-request.json' } + } + } + } + #swagger.responses[401] = { + description: 'Not Authenticated', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[403] = { + description: 'Forbidden', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[404] = { + description: 'Not Found', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[500] = { + description: 'Internal Server Error', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + */ + mw.useRegistry(), + mw.validateUser, + mw.onlySecretariat, + param(['shortname']).isString().trim().notEmpty().isLength({ min: CONSTANTS.MIN_SHORTNAME_LENGTH, max: CONSTANTS.MAX_SHORTNAME_LENGTH }), + param(['username']).isString().trim().notEmpty().custom(isValidUsername), + parseError, + parseDeleteParams, + registryUserController.DELETE_USER +) + +router.put('/registry/org/:shortname/user/:username/reset_secret', + /* + #swagger.tags = ['Registry User'] + #swagger.operationId = 'registryUserResetSecret' + #swagger.summary = "Reset the API key for a user (accessible to self, same-organization Admins, or Secretariat)" + #swagger.description = " +

Access Control

+

Authenticated users can reset their own API secret. Organization admins can reset users in their organization. Secretariat users can reset any user's API secret.

+

Expected Behavior

+

Regular User: Resets user's own API secret

+

Admin User: Resets any user's API secret in the Admin's organization

+

Secretariat: Resets any user's API secret

" + #swagger.parameters['shortname'] = { description: 'The shortname of the organization' } + #swagger.parameters['username'] = { description: 'The username of the user' } + #swagger.parameters['$ref'] = [ + '#/components/parameters/apiEntityHeader', + '#/components/parameters/apiUserHeader', + '#/components/parameters/apiSecretHeader' + ] + #swagger.responses[200] = { + description: 'Returns the new API key', + content: { + "application/json": { + schema: { $ref: '../schemas/user/reset-secret-response.json' } + } + } + } + #swagger.responses[400] = { + description: 'Bad Request', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/bad-request.json' } + } + } + } + #swagger.responses[401] = { + description: 'Not Authenticated', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[403] = { + description: 'Forbidden', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[404] = { + description: 'Not Found', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[500] = { + description: 'Internal Server Error', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + */ + mw.useRegistry(), + mw.validateUser, + mw.onlyOrgWithPartnerRole, + parseError, + parsePostParams, + controller.USER_RESET_SECRET +) + +router.post('/registry/org/:shortname/user/:username/grant-role', + /* + #swagger.tags = ['Registry User'] + #swagger.operationId = 'registryUserGrantRole' + #swagger.summary = "Grants a role to a user (accessible to Secretariat or Org Admin)" + #swagger.description = " +

Access Control

+

User must belong to an organization with the Secretariat role or be an Admin of the target organization

+

Expected Behavior

+

Admin User: Grants a role to a user in the Admin's organization

+

Secretariat: Grants a role to a user in any organization

" + #swagger.parameters['shortname'] = { description: 'The shortname of the organization' } + #swagger.parameters['username'] = { description: 'The username of the user' } + #swagger.parameters['$ref'] = [ + '#/components/parameters/apiEntityHeader', + '#/components/parameters/apiUserHeader', + '#/components/parameters/apiSecretHeader' + ] + #swagger.requestBody = { + required: true, + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + role: { + type: 'string', + enum: ['ADMIN'] + } + }, + required: ['role'] + } + } + } + } + #swagger.responses[200] = { + description: 'Role granted successfully', + content: { + "application/json": { + schema: { type: 'object', properties: { message: { type: 'string' } } } + } + } + } + #swagger.responses[400] = { + description: 'Bad Request', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/bad-request.json' } + } + } + } + #swagger.responses[401] = { + description: 'Not Authenticated', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[403] = { + description: 'Forbidden', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[404] = { + description: 'Not Found', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[500] = { + description: 'Internal Server Error', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + */ + mw.useRegistry(), + mw.validateUser, + // mw.onlyOrgWithPartnerRole, // This might be too restrictive if we want Secretariat to do it for any org type + parseError, + parsePostParams, + registryUserController.GRANT_ROLE +) + +router.post('/registry/org/:shortname/user/:username/revoke-role', + /* + #swagger.tags = ['Registry User'] + #swagger.operationId = 'registryUserRevokeRole' + #swagger.summary = "Revokes a role from a user (accessible to Secretariat or Org Admin)" + #swagger.description = " +

Access Control

+

User must belong to an organization with the Secretariat role or be an Admin of the target organization

+

Expected Behavior

+

Admin User: Revokes a role from a user in the Admin's organization

+

Secretariat: Revokes a role from a user in any organization

" + #swagger.parameters['shortname'] = { description: 'The shortname of the organization' } + #swagger.parameters['username'] = { description: 'The username of the user' } + #swagger.parameters['$ref'] = [ + '#/components/parameters/apiEntityHeader', + '#/components/parameters/apiUserHeader', + '#/components/parameters/apiSecretHeader' + ] + #swagger.requestBody = { + required: true, + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + role: { + type: 'string', + enum: ['ADMIN'] + } + }, + required: ['role'] + } + } + } + } + #swagger.responses[200] = { + description: 'Role revoked successfully', + content: { + "application/json": { + schema: { type: 'object', properties: { message: { type: 'string' } } } + } + } + } + #swagger.responses[400] = { + description: 'Bad Request', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/bad-request.json' } + } + } + } + #swagger.responses[401] = { + description: 'Not Authenticated', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[403] = { + description: 'Forbidden', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[404] = { + description: 'Not Found', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[500] = { + description: 'Internal Server Error', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + */ + mw.useRegistry(), + mw.validateUser, + // mw.onlyOrgWithPartnerRole, + parseError, + parsePostParams, + registryUserController.REVOKE_ROLE +) + +router.put('/registry/org/:shortname/conversation/:index', + /* + #swagger.tags = ['Registry Organization'] + #swagger.operationId = 'registryOrgUpdateConversation' + #swagger.summary = "Update the conversation at the given index for the given organization (accessible to Secretariat or original same-organization author)" + #swagger.description = " +

Access Control

+

User must belong to an organization with the Secretariat role or be the original author of the conversation in the same organization

+

Expected Behavior

+

Original Author: Allowed to update only the message body of a conversation posted by them

+

Secretariat: Allowed to update the message body and/or visibility of any conversation

" + #swagger.parameters['shortname'] = { description: 'The shortname of the organization' } + #swagger.parameters['index'] = { description: 'The index of the conversation to update' } + #swagger.parameters['$ref'] = [ + '#/components/parameters/apiEntityHeader', + '#/components/parameters/apiUserHeader', + '#/components/parameters/apiSecretHeader' + ] + #swagger.responses[200] = { + description: 'Returns the updated conversation', + content: { + "application/json": { + schema: { $ref: '../schemas/conversation/update-conversation-response.json' } + } + } + } + #swagger.responses[400] = { + description: 'Bad Request', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/bad-request.json' } + } + } + } + #swagger.responses[401] = { + description: 'Not Authenticated', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[403] = { + description: 'Forbidden', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[404] = { + description: 'Not Found', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[500] = { + description: 'Internal Server Error', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + */ + mw.useRegistry(), + mw.validateUser, + mw.onlyOrgWithPartnerRole, + parseError, + parsePostParams, + registryOrgController.EDIT_CONVERSATION +) + +router.get('/registry/users', + /* + #swagger.tags = ['Registry User'] + #swagger.operationId = 'registryUserAll' + #swagger.summary = "Retrieves information about all registered users (accessible to Secretariat)" + #swagger.description = " +

Access Control

+

User must belong to an organization with the Secretariat role

+

Expected Behavior

+

Secretariat: Retrieves information about all users for all organizations

" + #swagger.parameters['$ref'] = [ + '#/components/parameters/pageQuery', + '#/components/parameters/apiEntityHeader', + '#/components/parameters/apiUserHeader', + '#/components/parameters/apiSecretHeader' + ] + #swagger.responses[200] = { + description: 'Returns all users, along with pagination fields if results span multiple pages of data.', + content:{ + "application/json":{ + schema: { + $ref: '../schemas/registry-user/list-registry-users-response.json' + } + } + } + } + #swagger.responses[400] = { + description: 'Bad Request', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/bad-request.json' } + } + } + } + #swagger.responses[401] = { + description: 'Not Authenticated', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' }, + } + } + } + #swagger.responses[403] = { + description: 'Forbidden', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[404] = { + description: 'Not Found', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[500] = { + description: 'Internal Server Error', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + */ + mw.useRegistry(), + mw.validateUser, + mw.onlySecretariat, + query().custom((query) => { return mw.validateQueryParameterNames(query, ['page']) }), + query(['page']).optional().isInt({ min: CONSTANTS.PAGINATOR_PAGE }), + query(['page']).custom((val) => { return mw.containsNoInvalidCharacters(val) }), + parseUserError, + parseUserGetParams, + registryUserController.ALL_USERS +) + +module.exports = router diff --git a/src/controller/registry-org.controller/error.js b/src/controller/registry.controller/org.error.js similarity index 98% rename from src/controller/registry-org.controller/error.js rename to src/controller/registry.controller/org.error.js index 1b350b417..c47c26a9f 100644 --- a/src/controller/registry-org.controller/error.js +++ b/src/controller/registry.controller/org.error.js @@ -1,5 +1,6 @@ const idrErr = require('../../utils/error') +/** Registry organization controller errors. */ class RegistryOrgControllerError extends idrErr.IDRError { orgDnePathParam (shortname) { // org const err = {} diff --git a/src/controller/registry-org.controller/registry-org.controller.js b/src/controller/registry.controller/org.registry.controller.js similarity index 98% rename from src/controller/registry-org.controller/registry-org.controller.js rename to src/controller/registry.controller/org.registry.controller.js index cf739a819..deb83fc3c 100644 --- a/src/controller/registry-org.controller/registry-org.controller.js +++ b/src/controller/registry.controller/org.registry.controller.js @@ -1,8 +1,9 @@ +/** Registry organization route handlers. */ const mongoose = require('mongoose') const logger = require('../../middleware/logger') const { getConstants } = require('../../constants') const _ = require('lodash') -const errors = require('./error') +const errors = require('./org.error') const error = new errors.RegistryOrgControllerError() const conversationErrors = require('../conversation.controller/error') const convoError = new conversationErrors.ConversationControllerError() @@ -106,7 +107,7 @@ function removeAdditionalContactUUIDFields (org) { * @param {function} next - The next middleware function. * @returns {Promise} - A promise that resolves when the response is sent. * @description This endpoint is accessible to Secretariat only. It retrieves a list of all registry organizations. - * Called by GET /api/registryOrg + * Called by GET /api/registry/org */ async function getAllOrgs (req, res, next) { try { @@ -158,7 +159,7 @@ async function getAllOrgs (req, res, next) { * @param {function} next - The next middleware function. * @returns {Promise} - A promise that resolves when the response is sent. * @description This endpoint is accessible to Secretariat only. It retrieves information about the specified registry organization. - * Called by GET /api/registryOrg/:identifier + * Called by GET /api/registry/org/:identifier */ async function getOrg (req, res, next) { try { @@ -243,7 +244,7 @@ async function getOrg (req, res, next) { * @param {function} next - The next middleware function. * @returns {Promise} - A promise that resolves when the response is sent. * @description This endpoint is accessible to Secretariat only. It creates a new registry organization. - * Called by POST /api/registryOrg + * Called by POST /api/registry/org */ async function createOrg (req, res, next) { try { @@ -409,7 +410,7 @@ async function validateRequestedShortName (req, repo, body, shortName, session) * @param {function} next - The next middleware function. * @returns {Promise} - A promise that resolves when the response is sent. * @description This endpoint is accessible to Secretariat only. It updates an existing registry organization. - * Called by PUT /api/registryOrg/:shortname + * Called by PUT /api/registry/org/:shortname */ async function updateOrg (req, res, next) { try { @@ -608,18 +609,18 @@ async function updateOrg (req, res, next) { * * @async * @function deleteOrg - * @param {object} req - The Express request object, containing the organization identifier in `req.ctx.params.identifier`. + * @param {object} req - The Express request object, containing the organization short name 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. * @description This endpoint is accessible to Secretariat only. It deletes an existing registry organization. - * Called by DELETE /api/registryOrg/:identifier + * Called by DELETE /api/registry/org/:shortname */ async function deleteOrg (req, res, next) { try { const session = await mongoose.startSession({ causalConsistency: false }) const repo = req.ctx.repositories.getBaseOrgRepository() - const shortName = req.ctx.params.identifier + const shortName = req.ctx.params.shortname let targetOrgUUID try { @@ -668,7 +669,7 @@ async function deleteOrg (req, res, next) { * @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. * @description This endpoint is accessible to Secretariat only. It retrieves user information for any organization. - * Called by GET /api/registryOrg/:shortname/users + * Called by GET /api/registry/org/:shortname/users */ async function getUsers (req, res, next) { try { @@ -726,7 +727,7 @@ async function getUsers (req, res, next) { * @param {function} next - The next middleware function. * @returns {Promise} - A promise that resolves when the response is sent. * @description This endpoint is accessible to Secretariat only. It creates a user for any organization. - * Called by POST /api/registryOrg/:shortname/user + * Called by POST /api/registry/org/:shortname/user */ async function createUserByOrg (req, res, next) { try { diff --git a/src/controller/registry-user.controller/registry-user.controller.js b/src/controller/registry.controller/user.registry.controller.js similarity index 87% rename from src/controller/registry-user.controller/registry-user.controller.js rename to src/controller/registry.controller/user.registry.controller.js index 7502322f0..788e7c8c6 100644 --- a/src/controller/registry-user.controller/registry-user.controller.js +++ b/src/controller/registry.controller/user.registry.controller.js @@ -1,3 +1,4 @@ +/** Registry user controller route handlers. */ const mongoose = require('mongoose') const logger = require('../../middleware/logger') const { getConstants } = require('../../constants') @@ -25,7 +26,7 @@ function removeImmutableUpdateFields (body) { * @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. * @description This endpoint is accessible to Secretariat only. It retrieves a list of all registry users. - * Called by GET /api/registryUser + * Called by GET /api/registry/users */ async function getAllUsers (req, res, next) { try { @@ -85,7 +86,7 @@ async function getAllUsers (req, res, next) { * @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. * @description This endpoint is accessible to Secretariat only. It retrieves information about the specified registry user. - * Called by GET /api/registryUser/:identifier + * Called by GET /api/registry/org/:shortname/user/:username */ async function getUser (req, res, next) { /* @@ -174,79 +175,6 @@ async function getUser (req, res, next) { } } -async function createUser (req, res, next) { - try { - const orgRepo = req.ctx.repositories.getBaseOrgRepository() - const userRepo = req.ctx.repositories.getBaseUserRepository() - const body = req.ctx.body - const orgShortName = req.ctx.params.shortname - let returnValue - - const orgUUID = await orgRepo.getOrgUUID(orgShortName) - if (!orgUUID) { - logger.info({ uuid: req.ctx.uuid, message: 'The user could not be created because ' + orgShortName + ' organization does not exist.' }) - return res.status(404).json(error.orgDnePathParam(orgShortName)) - } - - // Do not allow the user to pass in a UUID - if ((body?.UUID ?? null) || (body?.uuid ?? null)) { - return res.status(400).json(error.uuidProvided('user')) - } - - if ((body?.org_UUID ?? null) || (body?.org_uuid ?? null)) { - return res.status(400).json(error.uuidProvided('org')) - } - - const session = await mongoose.startSession({ causalConsistency: false }) - - try { - session.startTransaction({ readPreference: 'primary' }) - - const result = await userRepo.validateUser(body) - if (body?.role && typeof body?.role !== 'string') { - await session.abortTransaction() - return res.status(400).json({ message: 'Parameters were invalid', details: [{ param: 'role', msg: 'Parameter must be a string' }] }) - } - if (!result.isValid) { - logger.error(JSON.stringify({ uuid: req.ctx.uuid, message: 'User JSON schema validation FAILED.' })) - await session.abortTransaction() - return res.status(400).json({ message: 'Parameters were invalid', errors: result.errors }) - } - - // Ask repo if user already exists - if (await userRepo.orgHasUser(orgShortName, body?.username, { session })) { - logger.info({ uuid: req.ctx.uuid, message: `${body?.username} user was not created because it already exists.` }) - await session.abortTransaction() - return res.status(400).json(error.userExists(body?.username)) - } - - const users = await userRepo.findUsersByOrgShortname(orgShortName, { session }) - if (users.length >= 100) { - await session.abortTransaction() - return res.status(400).json(error.userLimitReached()) - } - - const requestingUserUUID = await authContext.getRequesterUserUUID(req, userRepo, orgRepo, { session }) - returnValue = await userRepo.createUser(orgShortName, body, { session, upsert: true }, true, requestingUserUUID) - await session.commitTransaction() - } catch (error) { - await session.abortTransaction() - throw error - } finally { - await session.endSession() - } - - const responseMessage = { - message: `${body?.username} was successfully created.`, - created: returnValue - } - - return res.status(200).json(responseMessage) - } catch (err) { - next(err) - } -} - async function updateUser (req, res, next) { /* This function is a little bit overloaded ATM until future releases of CVE-Services @@ -454,19 +382,36 @@ async function updateUser (req, res, next) { } } +/** + * Deletes a registry user from the specified organization. + * Called by DELETE /api/registry/org/{shortname}/user/{username} + * + * @param {Object} req - The request object + * @param {Object} res - The response object + * @param {Function} next - The next middleware function + * @returns {Promise} + */ async function deleteUser (req, res, next) { try { const userRepo = req.ctx.repositories.getBaseUserRepository() const orgRepo = req.ctx.repositories.getBaseOrgRepository() - const userUUID = req.ctx.params.identifier + const orgShortName = req.ctx.params.shortname + const username = req.ctx.params.username + const org = await orgRepo.findOneByShortName(orgShortName) - const user = await userRepo.findUserByUUID(userUUID) + if (!org) { + logger.info({ uuid: req.ctx.uuid, message: 'Org DNE' }) + return res.status(404).json(error.orgDnePathParam(orgShortName)) + } + + const user = await userRepo.findOneByUsernameAndOrgShortname(username, orgShortName) if (!user) { logger.info({ uuid: req.ctx.uuid, message: 'User DNE' }) - return res.status(404).json(error.userDne(userUUID)) + return res.status(404).json(error.userDne(username)) } + const userUUID = user.UUID const requestingUserUUID = await authContext.getRequesterUserUUID(req, userRepo, orgRepo) await userRepo.deleteUserByUUID(userUUID, {}, requestingUserUUID) @@ -628,7 +573,6 @@ async function revokeRole (req, res, next) { module.exports = { ALL_USERS: getAllUsers, SINGLE_USER: getUser, - CREATE_USER: createUser, UPDATE_USER: updateUser, DELETE_USER: deleteUser, GRANT_ROLE: grantRole, diff --git a/src/controller/user.controller/index.js b/src/controller/user.controller/index.js index ddbb44823..c118669c0 100644 --- a/src/controller/user.controller/index.js +++ b/src/controller/user.controller/index.js @@ -3,89 +3,10 @@ const router = express.Router() const mw = require('../../middleware/middleware') const { query } = require('express-validator') const controller = require('./user.controller') -const registryUserController = require('../registry-user.controller/registry-user.controller.js') const { parseGetParams, parseError } = require('./user.middleware') const getConstants = require('../../constants').getConstants const CONSTANTS = getConstants() -router.get('/registry/users', - /* - #swagger.tags = ['Registry User'] - #swagger.operationId = 'userAll' - #swagger.summary = "Retrieves information about all registered users (accessible to Secretariat)" - #swagger.description = " -

Access Control

-

User must belong to an organization with the Secretariat role

-

Expected Behavior

-

Secretariat: Retrieves information about all users for all organizations

" - #swagger.parameters['$ref'] = [ - '#/components/parameters/pageQuery', - '#/components/parameters/apiEntityHeader', - '#/components/parameters/apiUserHeader', - '#/components/parameters/apiSecretHeader' - ] - #swagger.responses[200] = { - description: 'Returns all users, along with pagination fields if results span multiple pages of data.', - content:{ - "application/json":{ - schema: { - $ref: '../schemas/registry-user/list-registry-users-response.json' - } - } - } - } - #swagger.responses[400] = { - description: 'Bad Request', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/bad-request.json' } - } - } - } - #swagger.responses[401] = { - description: 'Not Authenticated', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' }, - } - } - } - #swagger.responses[403] = { - description: 'Forbidden', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[404] = { - description: 'Not Found', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - #swagger.responses[500] = { - description: 'Internal Server Error', - content: { - "application/json": { - schema: { $ref: '../schemas/errors/generic.json' } - } - } - } - */ - mw.useRegistry(), - mw.validateUser, - mw.onlySecretariat, - query().custom((query) => { return mw.validateQueryParameterNames(query, ['page']) }), - query(['page']).optional().isInt({ min: CONSTANTS.PAGINATOR_PAGE }), - query(['page']).custom((val) => { return mw.containsNoInvalidCharacters(val) }), - parseError, - parseGetParams, - registryUserController.ALL_USERS -) - router.get('/users', /* #swagger.tags = ['Users'] diff --git a/src/routes.config.js b/src/routes.config.js index 9cf95cdc3..b914e243c 100644 --- a/src/routes.config.js +++ b/src/routes.config.js @@ -7,8 +7,7 @@ const CveIdController = require('./controller/cve-id.controller') const SchemasController = require('./controller/schemas.controller') const SystemController = require('./controller/system.controller') const UserController = require('./controller/user.controller') -const RegistryUserController = require('./controller/registry-user.controller') -const RegistryOrgController = require('./controller/registry-org.controller') +const RegistryController = require('./controller/registry.controller') const AuditController = require('./controller/audit.controller') const ConversationController = require('./controller/conversation.controller') const ReviewObjectController = require('./controller/review-object.controller') @@ -36,9 +35,7 @@ module.exports = async function configureRoutes (app) { app.use('/api/', CveIdController) app.use('/api/', SystemController) app.use('/api/', UserController) - // At this time, we have moved the crud operations to mirror the cve legacy endpoint just with /registry/ in them. In the future we may want these. - app.use('/api/', RegistryUserController) - app.use('/api/', RegistryOrgController) + app.use('/api/', RegistryController) app.use('/api/', ConversationController) app.use('/api/', ReviewObjectController) app.use('/api/', GlossaryController) diff --git a/src/swagger.js b/src/swagger.js index 4b18a646f..f038c1a6b 100644 --- a/src/swagger.js +++ b/src/swagger.js @@ -6,8 +6,7 @@ const endpointsFiles = [ 'src/controller/org.controller/index.js', 'src/controller/user.controller/index.js', 'src/controller/system.controller/index.js', - 'src/controller/registry-org.controller/index.js', - 'src/controller/registry-user.controller/index.js', + 'src/controller/registry.controller/index.js', 'src/controller/conversation.controller/index.js', 'src/controller/review-object.controller/index.js' ] diff --git a/test/integration-tests/registry-org/createUserByOrgTest.js b/test/integration-tests/registry-org/createUserByOrgTest.js index 14f3a64cf..be9a94d9c 100644 --- a/test/integration-tests/registry-org/createUserByOrgTest.js +++ b/test/integration-tests/registry-org/createUserByOrgTest.js @@ -8,7 +8,7 @@ const expect = chai.expect const constants = require('../constants.js') const app = require('../../../src/index.js') -describe('Testing POST /api/registryOrg/:shortname/user endpoint', () => { +describe('Testing POST /api/registry/org/:shortname/user endpoint', () => { context('Positive Tests', () => { it('Should create a new user in an organization', (done) => { const orgShortName = 'mitre' @@ -22,7 +22,7 @@ describe('Testing POST /api/registryOrg/:shortname/user endpoint', () => { role: 'ADMIN' } chai.request(app) - .post(`/api/registryOrg/${orgShortName}/user`) + .post(`/api/registry/org/${orgShortName}/user`) .set(constants.headers) .send(newUser) .end((err, res) => { @@ -48,7 +48,7 @@ describe('Testing POST /api/registryOrg/:shortname/user endpoint', () => { status: 'active' } chai.request(app) - .post(`/api/registryOrg/${orgShortName}/user`) + .post(`/api/registry/org/${orgShortName}/user`) .set(constants.headers) .send(newUser) .end((err, res) => { @@ -69,7 +69,7 @@ describe('Testing POST /api/registryOrg/:shortname/user endpoint', () => { status: 'active' } chai.request(app) - .post(`/api/registryOrg/${orgShortName}/user`) + .post(`/api/registry/org/${orgShortName}/user`) .set(constants.headers) .send(existingUser) .end((err, res) => { @@ -89,7 +89,7 @@ describe('Testing POST /api/registryOrg/:shortname/user endpoint', () => { } } chai.request(app) - .post(`/api/registryOrg/${orgShortName}/user`) + .post(`/api/registry/org/${orgShortName}/user`) .set(constants.headers) .send(invalidUser) .end((err, res) => { @@ -111,7 +111,7 @@ describe('Testing POST /api/registryOrg/:shortname/user endpoint', () => { test: 'additional key not in schema' } chai.request(app) - .post(`/api/registryOrg/${orgShortName}/user`) + .post(`/api/registry/org/${orgShortName}/user`) .set(constants.headers) .send(existingUser) .then((res) => { diff --git a/test/integration-tests/registry-org/registryOrgCRUDTest.js b/test/integration-tests/registry-org/registryOrgCRUDTest.js index 1ebd05f80..46fb6286c 100644 --- a/test/integration-tests/registry-org/registryOrgCRUDTest.js +++ b/test/integration-tests/registry-org/registryOrgCRUDTest.js @@ -36,8 +36,8 @@ function expectConversationWithoutAuthorId (conversations, body) { expect(convo).to.not.have.property('author_id') } -describe('Testing /registryOrg endpoints', () => { - context('Testing POST /registryOrg endpoint', () => { +describe('Testing /registry/org endpoints', () => { + context('Testing POST /registry/org endpoint', () => { context('Positive Tests', () => { it('Creates a new registry org', async () => { await chai.request(app) @@ -340,7 +340,7 @@ describe('Testing /registryOrg endpoints', () => { }) }) }) - context('Testing GET /registryOrg endpoints', () => { + context('Testing GET /registry/org endpoints', () => { context('Positive Tests', () => { it('Gets a list of all registry organizations', async () => { await chai.request(app) @@ -659,7 +659,7 @@ describe('Testing /registryOrg endpoints', () => { }) }) }) - context('Testing PUT /registryOrg endpoint', () => { + context('Testing PUT /registry/org endpoint', () => { context('Positive Tests', () => { it('Updates a registry organization providing a full organization object', async () => { await chai.request(app) @@ -933,7 +933,7 @@ describe('Testing /registryOrg endpoints', () => { // Cleanup sub org await chai.request(app) - .delete(`/api/registryOrg/${subOrg.short_name}`) + .delete(`/api/registry/org/${subOrg.short_name}`) .set(secretariatHeaders) }) it('Preserves inUse and in_use properties across updates', async () => { @@ -975,7 +975,7 @@ describe('Testing /registryOrg endpoints', () => { // Cleanup await chai.request(app) - .delete(`/api/registryOrg/${tempOrg.short_name}`) + .delete(`/api/registry/org/${tempOrg.short_name}`) .set(secretariatHeaders) }) }) @@ -1152,11 +1152,11 @@ describe('Testing /registryOrg endpoints', () => { }) }) }) - context('Testing DELETE /registryOrg endpoint', () => { + context('Testing DELETE /registry/org endpoint', () => { context('Positive Tests', () => { it('Deletes a registry organization with the provided short name', async () => { await chai.request(app) - .delete('/api/registryOrg/registry_org_test') + .delete('/api/registry/org/registry_org_test') .set(secretariatHeaders) .then((res) => { expect(res).to.have.status(200) @@ -1167,7 +1167,7 @@ describe('Testing /registryOrg endpoints', () => { context('Negative Tests', () => { it('Fails to delete a registry organization that does not exist', async () => { await chai.request(app) - .delete('/api/registryOrg/registry_org_test2') + .delete('/api/registry/org/registry_org_test2') .set(secretariatHeaders) .then((res) => { expect(res).to.have.status(404) diff --git a/test/integration-tests/registry-org/registryOrgDiscriminatorAuthorityTest.js b/test/integration-tests/registry-org/registryOrgDiscriminatorAuthorityTest.js index 7fa072f43..ed3ea8b70 100644 --- a/test/integration-tests/registry-org/registryOrgDiscriminatorAuthorityTest.js +++ b/test/integration-tests/registry-org/registryOrgDiscriminatorAuthorityTest.js @@ -16,7 +16,7 @@ describe('Testing Registry Org Discriminator Authority inheritance', () => { const shortName = orgsToCleanup.pop() try { await chai.request(app) - .delete(`/api/registryOrg/${shortName}`) + .delete(`/api/registry/org/${shortName}`) .set(secretariatHeaders) } catch (err) { // ignore errors during cleanup diff --git a/test/integration-tests/registry-org/registryOrgWithJointReviewTest.js b/test/integration-tests/registry-org/registryOrgWithJointReviewTest.js index d891e91c4..28cd37220 100644 --- a/test/integration-tests/registry-org/registryOrgWithJointReviewTest.js +++ b/test/integration-tests/registry-org/registryOrgWithJointReviewTest.js @@ -197,7 +197,7 @@ describe('Testing Joint approval', () => { }) it('Check to see if the org was partially updated', async () => { await chai.request(app) - .get(`/api/registryOrg/${orgUUID}`) + .get(`/api/registry/org/${orgUUID}`) .set(secretariatHeaders) .then((res, err) => { expect(err).to.be.undefined @@ -217,7 +217,7 @@ describe('Testing Joint approval', () => { }) // Verify that the org was updated with the new body values await chai.request(app) - .get(`/api/registryOrg/${orgUUID}`) + .get(`/api/registry/org/${orgUUID}`) .set(secretariatHeaders) .then((res, err) => { expect(err).to.be.undefined @@ -233,7 +233,7 @@ describe('Testing Joint approval', () => { let reviewUUID it('Create an org to use for testing', async () => { await chai.request(app) - .post('/api/registryOrg') + .post('/api/registry/org') .set(secretariatHeaders) .send(testRegistryOrgForReviewWithComments) .then((res, err) => { @@ -300,7 +300,7 @@ describe('Testing Joint approval', () => { }) it('Check to see if the org was partially updated', async () => { await chai.request(app) - .get(`/api/registryOrg/${orgUUID}`) + .get(`/api/registry/org/${orgUUID}`) .set(secretariatHeaders) .then((res, err) => { expect(err).to.be.undefined @@ -373,7 +373,7 @@ describe('Testing Joint approval', () => { }) it('Check to see if the org was fully updated', async () => { await chai.request(app) - .get(`/api/registryOrg/${orgUUID}`) + .get(`/api/registry/org/${orgUUID}`) .set(secretariatHeaders) .then((res, err) => { expect(err).to.be.undefined diff --git a/test/integration-tests/registry-org/verifyDeepRemoveEmpty.js b/test/integration-tests/registry-org/verifyDeepRemoveEmpty.js index c6abde3a3..6884a6784 100644 --- a/test/integration-tests/registry-org/verifyDeepRemoveEmpty.js +++ b/test/integration-tests/registry-org/verifyDeepRemoveEmpty.js @@ -22,7 +22,7 @@ describe('Testing Deep Remove Empty in Create Org', () => { context('Positive Tests', () => { it('Creates a registry org and verifies null values are removed', async () => { await chai.request(app) - .post('/api/registryOrg') + .post('/api/registry/org') .set(secretariatHeaders) .send(testNullRemovalOrg) .then((res, err) => { @@ -56,7 +56,7 @@ describe('Testing Deep Remove Empty in Create Org', () => { after(async () => { // Cleanup: Delete the created org await chai.request(app) - .delete('/api/registryOrg/test_null_removal') + .delete('/api/registry/org/test_null_removal') .set(secretariatHeaders) }) }) diff --git a/test/integration-tests/registry-user/registryUserCRUDTest.js b/test/integration-tests/registry-user/registryUserCRUDTest.js index 01c089c98..b696e01d2 100644 --- a/test/integration-tests/registry-user/registryUserCRUDTest.js +++ b/test/integration-tests/registry-user/registryUserCRUDTest.js @@ -40,7 +40,7 @@ const postNewOrg = async (shortName) => { const postNewUser = async (orgShortName, username) => { return chai.request(app) - .post(`/api/registryUser/${orgShortName}`) + .post(`/api/registry/org/${orgShortName}/user`) .set(secretariatHeaders) .send({ username, @@ -71,11 +71,11 @@ const createRegistryUser = async () => { return { orgShortName, createdUser } } -describe('Testing /registryUser endpoints', () => { +describe('Testing canonical registry user endpoints', () => { context('Positive Tests', () => { it('Gets a list of all registry users', async () => { await chai.request(app) - .get('/api/registryUser') + .get('/api/registry/users') .set(secretariatHeaders) .then((res) => { expect(res).to.have.status(200) @@ -84,7 +84,7 @@ describe('Testing /registryUser endpoints', () => { }) }) - it('Gets a registry user by UUID', async () => { + it('Gets a registry user by organization and username', async () => { let user await chai.request(app) .get('/api/registry/org/win_5/user/jasminesmith@win_5.com') @@ -95,7 +95,7 @@ describe('Testing /registryUser endpoints', () => { }) await chai.request(app) - .get(`/api/registryUser/${user.UUID}`) + .get(`/api/registry/org/win_5/user/${user.username}`) .set(secretariatHeaders) .then((res) => { expect(res).to.have.status(200) @@ -105,12 +105,11 @@ describe('Testing /registryUser endpoints', () => { }) }) - it('Creates, updates, and deletes a registry user by UUID', async () => { + it('Creates, updates, and deletes a registry user by organization and username', async () => { const username = `${uuidv4()}@registry-user.test` - let userUUID await chai.request(app) - .post('/api/registryUser/range_4') + .post('/api/registry/org/range_4/user') .set(secretariatHeaders) .send({ username, @@ -125,12 +124,11 @@ describe('Testing /registryUser endpoints', () => { expect(res.body).to.have.property('created') expect(res.body.created).to.have.property('UUID') expect(res.body.created).to.have.property('username', username) - userUUID = res.body.created.UUID }) let user await chai.request(app) - .get(`/api/registryUser/${userUUID}`) + .get(`/api/registry/org/range_4/user/${username}`) .set(secretariatHeaders) .then((res) => { expect(res).to.have.status(200) @@ -138,7 +136,7 @@ describe('Testing /registryUser endpoints', () => { }) await chai.request(app) - .put(`/api/registryUser/${userUUID}`) + .put(`/api/registry/org/range_4/user/${username}`) .set(secretariatHeaders) .send({ ...user, @@ -153,7 +151,7 @@ describe('Testing /registryUser endpoints', () => { }) await chai.request(app) - .delete(`/api/registryUser/${userUUID}`) + .delete(`/api/registry/org/range_4/user/${username}`) .set(secretariatHeaders) .then((res) => { expect(res).to.have.status(200) @@ -161,20 +159,20 @@ describe('Testing /registryUser endpoints', () => { }) await chai.request(app) - .get(`/api/registryUser/${userUUID}`) + .get(`/api/registry/org/range_4/user/${username}`) .set(secretariatHeaders) .then((res) => { expect(res).to.have.status(404) }) }) - it('Logs the updated user UUID when updating a registry user by identifier', async () => { - const { createdUser } = await createRegistryUser() + it('Logs the updated user UUID when updating a registry user by canonical identity', async () => { + const { orgShortName, createdUser } = await createRegistryUser() const loggerInfoStub = sinon.stub(logger, 'info') try { await chai.request(app) - .put(`/api/registryUser/${createdUser.UUID}`) + .put(`/api/registry/org/${orgShortName}/user/${createdUser.username}`) .set(secretariatHeaders) .send({ UUID: createdUser.UUID, @@ -198,13 +196,13 @@ describe('Testing /registryUser endpoints', () => { } }) - it('Logs the deleted user UUID when deleting a registry user by identifier', async () => { - const { createdUser } = await createRegistryUser() + it('Logs the deleted user UUID when deleting a registry user by canonical identity', async () => { + const { orgShortName, createdUser } = await createRegistryUser() const loggerInfoStub = sinon.stub(logger, 'info') try { await chai.request(app) - .delete(`/api/registryUser/${createdUser.UUID}`) + .delete(`/api/registry/org/${orgShortName}/user/${createdUser.username}`) .set(secretariatHeaders) .then((res) => { expect(res).to.have.status(200) @@ -221,7 +219,7 @@ describe('Testing /registryUser endpoints', () => { context('Negative Tests', () => { it('Fails when page query parameter is not an integer', async () => { await chai.request(app) - .get('/api/registryUser') + .get('/api/registry/users') .set(secretariatHeaders) // Must be secretariat to reach validation .query({ page: 'not-a-number' }) // Invalid data .then((res) => { @@ -232,7 +230,7 @@ describe('Testing /registryUser endpoints', () => { it('Fails when page query parameter is below the minimum', async () => { await chai.request(app) - .get('/api/registryUser') + .get('/api/registry/users') .set(secretariatHeaders) .query({ page: 0 }) // Assuming min is 1 .then((res) => { @@ -240,9 +238,9 @@ describe('Testing /registryUser endpoints', () => { }) }) - it('Fails when identifier contains invalid characters', async () => { + it('Fails when username contains invalid characters', async () => { await chai.request(app) - .get('/api/registryUser/uuid