Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion api-docs/openapi.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"openapi": "3.0.2",
"info": {
"version": "2.8.2",
"version": "2.8.3",
"title": "CVE Services API",
"description": "The CVE Services API supports automation tooling for the CVE Program. Credentials are required for most service endpoints. Representatives of <a href='https://www.cve.org/ProgramOrganization/CNAs'>CVE Numbering Authorities (CNAs)</a> should use one of the methods below to obtain credentials: <ul><li>If your organization already has an Organizational Administrator (OA) account for the CVE Services, ask your admin for credentials</li> <li>Contact your Root (<a href='https://www.cve.org/PartnerInformation/ListofPartners/partner/Google'>Google</a>, <a href='https://www.cve.org/PartnerInformation/ListofPartners/partner/INCIBE'>INCIBE</a>, <a href='https://www.cve.org/PartnerInformation/ListofPartners/partner/jpcert'>JPCERT/CC</a>, or <a href='https://www.cve.org/PartnerInformation/ListofPartners/partner/redhat'>Red Hat</a>) or Top-Level Root (<a href='https://www.cve.org/PartnerInformation/ListofPartners/partner/icscert'>CISA ICS</a> or <a href='https://www.cve.org/PartnerInformation/ListofPartners/partner/mitre'>MITRE</a>) to request credentials </ul> <p>CVE data is to be in the JSON 5.2 CVE Record format. Details of the JSON 5.2 schema are located <a href='https://github.com/CVEProject/cve-schema/releases/tag/v5.2.0' target='_blank'>here</a>.</p> <a href='https://cveform.mitre.org/' class='link' target='_blank'>Contact the CVE Services team</a>",
"contact": {
Expand Down Expand Up @@ -2859,6 +2859,7 @@
},
"example": {
"username": "jdoe",
"status": "active",
"name": {
"first": "John",
"last": "Doe"
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "cve-services",
"author": "Automation Working Group",
"version": "2.8.2",
"version": "2.8.3",
"license": "(CC0)",
"devDependencies": {
"@faker-js/faker": "^7.6.0",
Expand Down
3 changes: 2 additions & 1 deletion schemas/registry-user/BaseUser.json
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
}
},
"required": [
"username"
"username",
"status"
]
}
6 changes: 6 additions & 0 deletions schemas/registry-user/create-registry-user-request.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@
"type": "string",
"description": "User's identifier or username"
},
"status": {
"type": "string",
"enum": ["active", "inactive"],
"description": "User status"
},
"name": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -62,6 +67,7 @@
},
"required": [
"username",
"status",
"name"
]
}
11 changes: 10 additions & 1 deletion schemas/registry-user/update-registry-user-request.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@
"type": "string",
"description": "User's identifier or username"
},
"status": {
"type": "string",
"enum": ["active", "inactive"],
"description": "User status"
},
"name": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -57,5 +62,9 @@
}
}
}
}
},
"required": [
"username",
"status"
]
}
1 change: 1 addition & 0 deletions src/controller/org.controller/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,7 @@ router.post('/registry/org/:shortname/user',
},
example: {
"username": "jdoe",
"status": "active",
"name": {
"first": "John",
"last": "Doe"
Expand Down
26 changes: 13 additions & 13 deletions src/controller/org.controller/org.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -742,6 +742,19 @@ async function resetSecret (req, res, next) {
return res.status(404).json(error.orgDnePathParam(targetOrgShortName))
}

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)

if (!isRequesterSecretariat) {
const requesterSameOrg = await authContext.isRequesterSameOrg(req, orgRepo, targetOrg, { session }, !req.useRegistry)
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()
return res.status(403).json(error.notSameOrgOrSecretariat())
}
}

// Check if target user exists in target org
const targetUserUUID = await userRepo.getUserUUID(targetUsername, targetOrgShortName, { session }, !!req.useRegistry)
if (!targetUserUUID) {
Expand All @@ -750,25 +763,12 @@ async function resetSecret (req, res, next) {
return res.status(404).json(error.userDne(targetUsername))
}

const requesterUserUUID = await authContext.getRequesterUserUUID(req, userRepo, orgRepo, { session }, !!req.useRegistry)

const isRequesterSecretariat = await authContext.isRequesterSecretariat(req, orgRepo, { session }, !req.useRegistry)

if (!isRequesterSecretariat) {
// If they are in the same organization, they must be the target user themselves OR an admin of the target org.
const targetOrg = { UUID: targetOrgUUID, short_name: targetOrgShortName }

// 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)
// The tests say we have to check the org next:
const requesterSameOrg = await authContext.isRequesterSameOrg(req, orgRepo, targetOrg, { session }, !req.useRegistry)
if (!requesterSameOrg && !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.' })
await session.abortTransaction()
return res.status(403).json(error.notSameOrgOrSecretariat())
}

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.' })
Expand Down
53 changes: 32 additions & 21 deletions src/controller/registry-user.controller/registry-user.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,23 +135,32 @@ async function getUser (req, res, next) {
username: result.username
}
} else {
org = await repo.findOneByShortName(req.ctx.params.shortname)

const isSameOrg = await authContext.isRequesterSameOrg(req, repo, org)
if (!isSecretariat && !isSameOrg) {
logger.info({ uuid: req.ctx.uuid, message: userToGetParameters.org + ' organization can only be viewed by the users of the same organization or the Secretariat.' })
return res.status(403).json(error.notSameOrgOrSecretariat())
}

result = await userRepo.findOneByUsernameAndOrgShortname(userToGetParameters.username, userToGetParameters.org)
if (!result) {
logger.info({ uuid: req.ctx.uuid, message: userToGetParameters.username + ' user could not be found.' })
return res.status(404).json(error.userDne(userToGetParameters.username))
}

org = await repo.findOneByShortName(req.ctx.params.shortname)
userToGetParameters = {
org: org.short_name,
username: result.username
}
}

const isSameOrg = await authContext.isRequesterSameOrg(req, repo, org)
if (!isSecretariat && !isSameOrg) {
logger.info({ uuid: req.ctx.uuid, message: identifier + ' organization can only be viewed by the users of the same organization or the Secretariat.' })
return res.status(403).json(error.notSameOrgOrSecretariat())
if (identifier) {
const isSameOrg = await authContext.isRequesterSameOrg(req, repo, org)
if (!isSecretariat && !isSameOrg) {
logger.info({ uuid: req.ctx.uuid, message: identifier + ' organization can only be viewed by the users of the same organization or the Secretariat.' })
return res.status(403).json(error.notSameOrgOrSecretariat())
}
}

const user = result.toObject()
Expand Down Expand Up @@ -505,24 +514,25 @@ async function grantRole (req, res, next) {
return res.status(404).json(error.orgDnePathParam(orgShortName))
}

// Check if target user exists in target org
const targetUser = await userRepo.findOneByUsernameAndOrgShortname(username, orgShortName)
if (!targetUser) {
return res.status(404).json(error.userDne(username))
}

const isSecretariat = await authContext.isRequesterSecretariat(req, orgRepo)
const isAdmin = await authContext.isRequesterAdminOfOrg(req, userRepo, orgRepo, orgShortName)
const targetOrg = { UUID: targetOrgUUID, short_name: orgShortName }

const requesterSameOrg = await authContext.isRequesterSameOrg(req, orgRepo, { UUID: targetOrgUUID, short_name: orgShortName })
const requesterSameOrg = await authContext.isRequesterSameOrg(req, orgRepo, targetOrg)
if (!requesterSameOrg && !isSecretariat) {
return res.status(403).json(error.notSameOrgOrSecretariat())
}

const isAdmin = await authContext.isRequesterAdminOfOrg(req, userRepo, orgRepo, targetOrg)
if (!isSecretariat && !isAdmin) {
return res.status(403).json(error.notOrgAdminOrSecretariatUpdate())
}

// Check if target user exists in target org
const targetUser = await userRepo.findOneByUsernameAndOrgShortname(username, orgShortName)
if (!targetUser) {
return res.status(404).json(error.userDne(username))
}

const session = await mongoose.startSession({ causalConsistency: false })

try {
Expand Down Expand Up @@ -569,24 +579,25 @@ async function revokeRole (req, res, next) {
return res.status(404).json(error.orgDnePathParam(orgShortName))
}

// Check if target user exists in target org
const targetUser = await userRepo.findOneByUsernameAndOrgShortname(username, orgShortName)
if (!targetUser) {
return res.status(404).json(error.userDne(username))
}

const isSecretariat = await authContext.isRequesterSecretariat(req, orgRepo)
const isAdmin = await authContext.isRequesterAdminOfOrg(req, userRepo, orgRepo, orgShortName)
const targetOrg = { UUID: targetOrgUUID, short_name: orgShortName }

const requesterSameOrg = await authContext.isRequesterSameOrg(req, orgRepo, { UUID: targetOrgUUID, short_name: orgShortName })
const requesterSameOrg = await authContext.isRequesterSameOrg(req, orgRepo, targetOrg)
if (!requesterSameOrg && !isSecretariat) {
return res.status(403).json(error.notSameOrgOrSecretariat())
}

const isAdmin = await authContext.isRequesterAdminOfOrg(req, userRepo, orgRepo, targetOrg)
if (!isSecretariat && !isAdmin) {
return res.status(403).json(error.notOrgAdminOrSecretariatUpdate())
}

// Check if target user exists in target org
const targetUser = await userRepo.findOneByUsernameAndOrgShortname(username, orgShortName)
if (!targetUser) {
return res.status(404).json(error.userDne(username))
}

// Prevent Self-Demotion
const callingUserUUID = await authContext.getRequesterUserUUID(req, userRepo, orgRepo)
if (callingUserUUID === targetUser.UUID) {
Expand Down
2 changes: 1 addition & 1 deletion src/scripts/migrate.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ async function orgHelper (db) {
// Doc to update existing org record, or to be created

let type = 'CNAOrg'
if (doc.short_name.toLowerCase().includes('mitre')) { type = 'SECRETARIAT' }
if (doc.short_name.toLowerCase().includes('mitre')) { type = 'SecretariatOrg' }
updateDoc = {
$set: {
UUID: doc.UUID,
Expand Down
2 changes: 1 addition & 1 deletion src/swagger.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const fullCnaContainerRequest = require('../schemas/cve/create-cve-record-cna-re
/* eslint-disable no-multi-str */
const doc = {
info: {
version: '2.8.2',
version: '2.8.3',
title: 'CVE Services API',
description: "The CVE Services API supports automation tooling for the CVE Program. Credentials are \
required for most service endpoints. Representatives of \
Expand Down
3 changes: 2 additions & 1 deletion test/integration-tests/audit/registryOrgCreatesAuditTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,8 @@ describe('Create and Update Audit Collection with Org Endpoints', () => {
name: {
first: 'Test',
last: 'User'
}
},
status: 'active'
})

expect(createUserRes).to.have.status(200)
Expand Down
3 changes: 2 additions & 1 deletion test/integration-tests/org/legacyAdminRoleRevokeTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ const postNewUser = async (orgShortName, username) => {
.post(`/api/registry/org/${orgShortName}/user`)
.set(secretariatHeaders)
.send({
username
username,
status: 'active'
})
}

Expand Down
4 changes: 4 additions & 0 deletions test/integration-tests/org/postOrgUsersTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ describe('Testing user post endpoint', () => {
middle: 'Cool',
suffix: 'Mr.'
},
status: 'active',
role: 'ADMIN'
})
.then((res, err) => {
Expand All @@ -74,6 +75,7 @@ describe('Testing user post endpoint', () => {
first: 'Authority',
last: 'Admin'
},
status: 'active',
authority: {
active_roles: [
'Admin'
Expand Down Expand Up @@ -392,6 +394,7 @@ describe('Testing user post endpoint', () => {
middle: 'Cool',
suffix: 'Mr.'
},
status: 'active',
role: 'ADMIN'
})
.then((res, err) => {
Expand All @@ -415,6 +418,7 @@ describe('Testing user post endpoint', () => {
middle: 'MiddleName',
suffix: 'Suffix'
},
status: 'active',
role: 'ADMIN',
test: 'additional key not in schema'
})
Expand Down
9 changes: 6 additions & 3 deletions test/integration-tests/org/registryOrg.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ const postNewUser = async (orgShortName, username) => {
.post(`/api/registry/org/${orgShortName}/user`)
.set(secretariatHeaders)
.send({
username
username,
status: 'active'
})
}

Expand Down Expand Up @@ -317,6 +318,7 @@ describe('Testing Secretariat functionality for Orgs', () => {
.set(secretariatHeaders)
.send({
username,
status: 'active',
ubiquitous: 'mendacious'
})
.then((res) => {
Expand Down Expand Up @@ -437,7 +439,7 @@ describe('Testing Secretariat functionality for Orgs', () => {
await chai.request(app)
.post('/api/registry/org/mitre/user')
.set(secretariatHeaders)
.send({ username: '' })
.send({ username: '', status: 'active' })
.then((res) => {
expect(res).to.have.status(400)
expect(res.body.message).to.equal('Parameters were invalid')
Expand All @@ -452,6 +454,7 @@ describe('Testing Secretariat functionality for Orgs', () => {
.set(secretariatHeaders)
.send({
username: username,
status: 'active',
role: roles
})
.then((res) => {
Expand All @@ -467,7 +470,7 @@ describe('Testing Secretariat functionality for Orgs', () => {
await chai.request(app)
.post('/api/registry/org/mitre/user')
.set(secretariatHeaders)
.send({ username: 'test_secretariat_0@mitre.org' })
.send({ username: 'test_secretariat_0@mitre.org', status: 'active' })
.then((res) => {
expect(res).to.have.status(400)
expect(res.body.message).to.equal('The user \'test_secretariat_0@mitre.org\' already exists.')
Expand Down
Loading
Loading