diff --git a/api-docs/openapi.json b/api-docs/openapi.json
index e344f94be..1a122a3ff 100644
--- a/api-docs/openapi.json
+++ b/api-docs/openapi.json
@@ -1974,18 +1974,12 @@
},
"post": {
"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": "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": "orgCreateSingle",
"parameters": [
- {
- "$ref": "#/components/parameters/pageQuery"
- },
- {
- "$ref": "#/components/parameters/registry"
- },
{
"$ref": "#/components/parameters/apiEntityHeader"
},
@@ -2057,6 +2051,29 @@
}
}
}
+ },
+ "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"
+ }
+ ]
+ }
+ }
+ }
}
}
},
@@ -2597,9 +2614,6 @@
{
"$ref": "#/components/parameters/active_roles_remove"
},
- {
- "$ref": "#/components/parameters/registry"
- },
{
"$ref": "#/components/parameters/apiEntityHeader"
},
@@ -2887,10 +2901,14 @@
"operationId": "orgAll",
"parameters": [
{
- "$ref": "#/components/parameters/pageQuery"
+ "name": "registry",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
},
{
- "$ref": "#/components/parameters/registry"
+ "$ref": "#/components/parameters/pageQuery"
},
{
"$ref": "#/components/parameters/apiEntityHeader"
@@ -2980,9 +2998,6 @@
"description": " Access Control
User must belong to an organization with the Secretariat role
Expected Behavior
Secretariat: Creates an organization
",
"operationId": "orgCreateSingle",
"parameters": [
- {
- "$ref": "#/components/parameters/registry"
- },
{
"$ref": "#/components/parameters/apiEntityHeader"
},
@@ -3067,14 +3082,7 @@
"content": {
"application/json": {
"schema": {
- "oneOf": [
- {
- "$ref": "../schemas/org/create-org-request.json"
- },
- {
- "$ref": "../schemas/registry-org/create-registry-org-request.json"
- }
- ]
+ "$ref": "../schemas/org/create-org-request.json"
}
}
}
@@ -3099,9 +3107,6 @@
},
"description": "The shortname or UUID of the organization"
},
- {
- "$ref": "#/components/parameters/registry"
- },
{
"$ref": "#/components/parameters/apiEntityHeader"
},
@@ -3118,14 +3123,7 @@
"content": {
"application/json": {
"schema": {
- "oneOf": [
- {
- "$ref": "../schemas/org/get-org-response.json"
- },
- {
- "$ref": "../schemas/registry-org/get-registry-org-response.json"
- }
- ]
+ "$ref": "../schemas/org/get-org-response.json"
}
}
}
@@ -3201,6 +3199,13 @@
},
"description": "The shortname of the organization"
},
+ {
+ "name": "registry",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
{
"$ref": "#/components/parameters/id_quota"
},
@@ -3216,9 +3221,6 @@
{
"$ref": "#/components/parameters/active_roles_remove"
},
- {
- "$ref": "#/components/parameters/registry"
- },
{
"$ref": "#/components/parameters/apiEntityHeader"
},
@@ -3235,14 +3237,7 @@
"content": {
"application/json": {
"schema": {
- "oneOf": [
- {
- "$ref": "../schemas/org/update-org-response.json"
- },
- {
- "$ref": "../schemas/registry-org/update-registry-org-response.json"
- }
- ]
+ "$ref": "../schemas/org/update-org-response.json"
}
}
}
@@ -3318,9 +3313,6 @@
},
"description": "The shortname of the organization"
},
- {
- "$ref": "#/components/parameters/registry"
- },
{
"$ref": "#/components/parameters/apiEntityHeader"
},
@@ -3337,14 +3329,7 @@
"content": {
"application/json": {
"schema": {
- "oneOf": [
- {
- "$ref": "../schemas/org/get-org-quota-response.json"
- },
- {
- "$ref": "../schemas/registry-org/get-registry-org-quota-response.json"
- }
- ]
+ "$ref": "../schemas/org/get-org-quota-response.json"
}
}
}
@@ -3421,10 +3406,14 @@
"description": "The shortname of the organization"
},
{
- "$ref": "#/components/parameters/pageQuery"
+ "name": "registry",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
},
{
- "$ref": "#/components/parameters/registry"
+ "$ref": "#/components/parameters/pageQuery"
},
{
"$ref": "#/components/parameters/apiEntityHeader"
@@ -3442,14 +3431,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"
}
}
}
@@ -3525,9 +3507,6 @@
},
"description": "The shortname of the organization"
},
- {
- "$ref": "#/components/parameters/registry"
- },
{
"$ref": "#/components/parameters/apiEntityHeader"
},
@@ -3765,9 +3744,6 @@
{
"$ref": "#/components/parameters/orgShortname"
},
- {
- "$ref": "#/components/parameters/registry"
- },
{
"$ref": "#/components/parameters/apiEntityHeader"
},
diff --git a/schemas/registry-org/ADPOrg.json b/schemas/registry-org/ADPOrg.json
new file mode 100644
index 000000000..be9829003
--- /dev/null
+++ b/schemas/registry-org/ADPOrg.json
@@ -0,0 +1,17 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "$id": "ADPOrg",
+ "type": "object",
+ "title": "CVE ADP Organization",
+ "description": "Schema for a CVE ADP Organization",
+ "allOf": [
+ { "$ref": "./BaseOrg.json" },
+ {
+ "properties": {
+ "authority": {
+ "const": ["ADP"]
+ }
+ }
+ }
+ ]
+}
diff --git a/schemas/registry-org/BaseOrg.json b/schemas/registry-org/BaseOrg.json
new file mode 100644
index 000000000..87f1b1e57
--- /dev/null
+++ b/schemas/registry-org/BaseOrg.json
@@ -0,0 +1,121 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "$id": "./BaseOrg.json",
+ "type": "object",
+ "title": "CVE Base Organization",
+ "description": "Base schema for a CVE Organization",
+ "definitions": {
+ "uuidType": {
+ "description": "A version 4 (random) universally unique identifier (UUID) as defined by [RFC 4122](https://tools.ietf.org/html/rfc4122#section-4.1.3).",
+ "type": "string",
+ "format": "uuid",
+ "pattern": "^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$"
+ },
+ "uriType": {
+ "description": "A universal resource identifier (URI), according to [RFC 3986](https://tools.ietf.org/html/rfc3986).",
+ "type": "string",
+ "format": "uri",
+ "pattern": "^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?",
+ "minLength": 1,
+ "maxLength": 2048
+ },
+ "shortName": {
+ "description": "A 2-32 character name that can be used to complement an organization's UUID.",
+ "type": "string",
+ "minLength": 2,
+ "maxLength": 32
+ },
+ "longName": {
+ "description": "A 1-256 character name that can be used to complement an organization's short_name.",
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 256
+ },
+ "authority": {
+ "description": "The authority (role) of this organization within the CVE program",
+ "type": "string",
+ "enum": ["CNA", "SECRETARIAT", "BULK_DOWNLOAD", "ADP"]
+ }
+ },
+ "properties": {
+ "UUID": {
+ "$ref": "#/definitions/uuidType"
+ },
+ "short_name": {
+ "$ref": "#/definitions/shortName"
+ },
+ "long_name": {
+ "$ref": "#/definitions/longName"
+ },
+ "aliases": {
+ "type": "array",
+ "uniqueItems": true,
+ "items": {
+ "type": "string"
+ }
+ },
+ "authority": {
+ "type": "array",
+ "uniqueItems": true,
+ "items": {
+ "$ref": "#/definitions/authority"
+ }
+ },
+ "root_or_tlr": {
+ "type": "boolean"
+ },
+ "reports_to": {
+ "$ref": "#/definitions/uuidType"
+ },
+ "users": {
+ "type": "array",
+ "uniqueItems": true,
+ "items": {
+ "$ref": "#/definitions/uuidType"
+ }
+ },
+ "admins": {
+ "type": "array",
+ "uniqueItems": true,
+ "items": {
+ "$ref": "#/definitions/uuidType"
+ }
+ },
+ "contact_info": {
+ "type": "object",
+ "properties": {
+ "additional_contact_users": {
+ "type": "array",
+ "uniqueItems": true,
+ "items": {
+ "$ref": "#/definitions/uuidType"
+ }
+ },
+ "poc": {
+ "type": "string"
+ },
+ "poc_email": {
+ "type": "string",
+ "format": "email"
+ },
+ "poc_phone": {
+ "type": "string"
+ },
+ "org_email": {
+ "type": "string",
+ "format": "email"
+ },
+ "website": {
+ "type": "string",
+ "format": "uri",
+ "description": "Organization's website URL"
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "required": [
+ "short_name",
+ "long_name"
+ ]
+}
\ No newline at end of file
diff --git a/schemas/registry-org/BulkDownloadOrg.json b/schemas/registry-org/BulkDownloadOrg.json
new file mode 100644
index 000000000..cabc0777a
--- /dev/null
+++ b/schemas/registry-org/BulkDownloadOrg.json
@@ -0,0 +1,17 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "$id": "BaseOrg",
+ "type": "object",
+ "title": "CVE Bulk Download Organization",
+ "description": "Schema for a CVE Bulk Download Organization",
+ "allOf": [
+ { "$ref": "./BaseOrg.json" },
+ {
+ "properties": {
+ "authority": {
+ "const": ["BULK_DOWNLOAD"]
+ }
+ }
+ }
+ ]
+}
diff --git a/schemas/registry-org/CNAOrg.json b/schemas/registry-org/CNAOrg.json
new file mode 100644
index 000000000..0402e8338
--- /dev/null
+++ b/schemas/registry-org/CNAOrg.json
@@ -0,0 +1,42 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "type": "object",
+ "$id": "CNAOrg",
+ "title": "CVE CNA Organization",
+ "description": "Schema for a CVE CNA Organization",
+ "allOf": [
+ { "$ref": "./BaseOrg.json" },
+ {
+ "properties": {
+ "authority": {
+ "const": ["CNA"]
+ },
+ "oversees": {
+ "type": "array",
+ "uniqueItems": true,
+ "items": {
+ "$ref": "./BaseOrg.json#/definitions/uuidType"
+ }
+ },
+ "hard_quota": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "soft_quota": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "charter_or_scope": {
+ "$ref": "/BaseOrg#/definitions/uriType"
+ },
+ "disclosure_policy": {
+ "$ref": "/BaseOrg#/definitions/uriType"
+ },
+ "product_list": {
+ "$ref": "/BaseOrg#/definitions/uriType"
+ }
+ },
+ "required": ["hard_quota"]
+ }
+ ]
+}
diff --git a/schemas/registry-org/SecretariatOrg.json b/schemas/registry-org/SecretariatOrg.json
new file mode 100644
index 000000000..469bd7df5
--- /dev/null
+++ b/schemas/registry-org/SecretariatOrg.json
@@ -0,0 +1,33 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "$id": "SecretariatOrg",
+ "type": "object",
+ "title": "CVE Secretariat Organization",
+ "description": "Schema for a CVE Secretariat Organization",
+ "allOf": [
+ { "$ref": "./BaseOrg.json" },
+ {
+ "properties": {
+ "authority": {
+ "const": ["SECRETARIAT"]
+ },
+ "oversees": {
+ "type": "array",
+ "uniqueItems": true,
+ "items": {
+ "$ref": "./BaseOrg.json#/definitions/uuidType"
+ }
+ },
+ "hard_quota": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "soft_quota": {
+ "type": "integer",
+ "minimum": 0
+ }
+ },
+ "required": ["hard_quota"]
+ }
+ ]
+}
diff --git a/schemas/registry-org/create-registry-org-request.json b/schemas/registry-org/create-registry-org-request.json
index 481a80851..b7fa78bfa 100644
--- a/schemas/registry-org/create-registry-org-request.json
+++ b/schemas/registry-org/create-registry-org-request.json
@@ -20,23 +20,12 @@
},
"description": "Alternative names or aliases for the organization"
},
- "cve_program_org_function": {
- "type": "string",
- "enum": ["CNA", "ADP", "Root", "Secretariat"],
- "description": "The organization's function within the CVE program"
- },
"authority": {
- "type": "object",
- "properties": {
- "active_roles": {
- "type": "array",
+ "type": "array",
"items": {
"type": "string",
- "enum": ["CNA", "ADP", "Root", "Secretariat"]
+ "enum": ["CNA", "ADP", "BULK_DOWNLOAD", "SECRETARIAT"]
}
- }
- },
- "required": ["active_roles"]
},
"reports_to": {
"type": ["string", "null"],
@@ -117,10 +106,7 @@
},
"required": [
"short_name",
- "cve_program_org_function",
"authority",
- "root_or_tlr",
- "users",
- "contact_info"
+ "long_name"
]
}
diff --git a/src/constants/index.js b/src/constants/index.js
index d06ad3e03..310e202bc 100644
--- a/src/constants/index.js
+++ b/src/constants/index.js
@@ -44,6 +44,8 @@ function getConstants () {
USER_ROLES: [
'ADMIN'
],
+ JOINT_APPROVAL_FIELDS: ['short_name', 'long_name', 'authority', 'aliases', 'oversees', 'root_or_tlr', 'charter_or', 'product_list', 'disclosure_policy', 'contact_info.poc', 'contact_info.poc_email', 'contact_info.poc_phone', 'contact_info.org_email', 'cna_role_type', 'cna_country', 'vulnerability_advisory_locations', 'advisory_location_require_credentials', 'industry', 'tl_root_start_date', 'is_cna_discussion_list'],
+ JOINT_APPROVAL_FIELDS_LEGACY: ['short_name', 'name', 'authority.active_roles'],
USER_ROLE_ENUM: {
ADMIN: 'ADMIN'
},
diff --git a/src/controller/audit.controller/audit.controller.js b/src/controller/audit.controller/audit.controller.js
index 1540f41b6..771b9a66c 100644
--- a/src/controller/audit.controller/audit.controller.js
+++ b/src/controller/audit.controller/audit.controller.js
@@ -8,7 +8,7 @@ const validateUUID = require('uuid').validate
* Create a new audit document
* Called by POST /api/audit/org/
*/
-async function createAuditDocument (req, res, next) {
+async function createAuditDocumentForOrg (req, res, next) {
try {
const session = await mongoose.startSession()
const repo = req.ctx.repositories.getAuditRepository()
@@ -72,9 +72,25 @@ async function createAuditDocument (req, res, next) {
await session.abortTransaction()
return res.status(400).json(error.missingRequiredField('change_author'))
}
+
+ // Process entry immediately after validation
+ returnValue = await repo.appendToAuditHistoryForOrg(
+ body.target_uuid,
+ entry.audit_object,
+ entry.change_author,
+ { session }
+ )
}
+ } else {
+ // Create audit document with initial empty entry or default entry
+ returnValue = await repo.appendToAuditHistoryForOrg(
+ body.target_uuid,
+ body.audit_object || {},
+ body.change_author || req.ctx.org,
+ { session }
+ )
}
- returnValue = await repo.createAuditDocument(body, { session })
+
await session.commitTransaction()
logger.info({
@@ -100,7 +116,7 @@ async function createAuditDocument (req, res, next) {
* Called by PUT /api/audit/org/
* Allows for multiple appends in a single request
*/
-async function appendToAuditHistory (req, res, next) {
+async function appendToAuditHistoryForOrg (req, res, next) {
try {
const session = await mongoose.startSession()
const repo = req.ctx.repositories.getAuditRepository()
@@ -149,7 +165,7 @@ async function appendToAuditHistory (req, res, next) {
}
// Append this history entry
- returnValue = await repo.appendToAuditHistory(
+ returnValue = await repo.appendToAuditHistoryForOrg(
body.target_uuid,
entry.audit_object,
entry.change_author,
@@ -190,7 +206,7 @@ async function appendToAuditHistory (req, res, next) {
* Get all audit documents
* Called by GET /api/audit/org/
*/
-async function getAllAuditDocuments (req, res, next) {
+async function getAllOrgAuditDocuments (req, res, next) {
try {
const session = await mongoose.startSession()
const repo = req.ctx.repositories.getAuditRepository()
@@ -213,7 +229,7 @@ async function getAllAuditDocuments (req, res, next) {
* Get audit document by its document UUID
* Called by GET /api/audit/org/document/:document_uuid
*/
-async function getAuditByDocumentUUID (req, res, next) {
+async function getOrgAuditByDocumentUUID (req, res, next) {
try {
const session = await mongoose.startSession()
const repo = req.ctx.repositories.getAuditRepository()
@@ -247,48 +263,53 @@ async function getAuditByDocumentUUID (req, res, next) {
next(err)
}
}
+
/**
- * Get audit history by target UUID
- * Called by GET /api/audit/org/:target_uuid
- * TODO: remove comment-> I changed parameter name from org_identifier to target_uuid to be more generic.
+ * Get audit history by target identifier (shortname or UUID)
+ * Called by GET /api/audit/org/:identifier
*/
-async function getAuditByTargetUUID (req, res, next) {
+async function getOrgAuditByOrgIdentifier (req, res, next) {
try {
const session = await mongoose.startSession()
const repo = req.ctx.repositories.getAuditRepository()
const orgRepo = req.ctx.repositories.getBaseOrgRepository()
- const targetUUID = req.ctx.params.target_uuid
+ const identifier = req.ctx.params.org_identifier
+ const identifierIsUUID = validateUUID(identifier)
let returnValue
- if (!targetUUID) {
- logger.info({ uuid: req.ctx.uuid, message: 'Missing target_uuid parameter' })
- return res.status(400).json(error.missingRequiredField('target_uuid'))
- }
-
- if (!validateUUID(targetUUID)) {
- logger.info({ uuid: req.ctx.uuid, message: 'Invalid target_uuid format' })
- return res.status(400).json(error.invalidUUID('target_uuid'))
+ if (!identifier) {
+ return res.status(400).json(error.missingRequiredField('identifier'))
}
try {
session.startTransaction()
- // Find the target organization
- const targetOrg = await orgRepo.findOneByUUID(targetUUID, { session })
+ // Find the target organization by either UUID or shortname
+ const targetOrg = identifierIsUUID
+ ? await orgRepo.findOneByUUID(identifier, { session })
+ : await orgRepo.findOneByShortName(identifier, { session })
+
if (!targetOrg) {
- logger.info({ uuid: req.ctx.uuid, message: `No organization found with UUID ${targetUUID}` })
+ logger.info({
+ uuid: req.ctx.uuid,
+ message: `No organization found with ${identifierIsUUID ? 'UUID' : 'shortname'} ${identifier}`
+ })
await session.abortTransaction()
- return res.status(404).json(error.orgDne(targetUUID))
+ return res.status(404).json(error.orgDne(identifier))
}
- // TODO: confirm middleware is checking admin and secretariat permissions properly
+ // Get the org's UUID for audit lookup
+ const targetUUID = targetOrg.UUID
returnValue = await repo.findOneByTargetUUID(targetUUID, { session })
if (!returnValue) {
- logger.info({ uuid: req.ctx.uuid, message: `No audit history found for target UUID ${targetUUID}` })
+ logger.info({
+ uuid: req.ctx.uuid,
+ message: `No audit history found for organization ${identifier} (UUID: ${targetUUID})`
+ })
await session.abortTransaction()
- return res.status(404).json(error.auditDneByTarget(targetUUID))
+ return res.status(404).json(error.auditDneByTarget(identifier))
}
await session.commitTransaction()
@@ -301,7 +322,7 @@ async function getAuditByTargetUUID (req, res, next) {
logger.info({
uuid: req.ctx.uuid,
- message: `Audit history for target UUID ${targetUUID} sent to user ${req.ctx.user}`
+ message: `Audit history for ${identifierIsUUID ? 'UUID' : 'shortname'} ${identifier} sent to user ${req.ctx.user}`
})
return res.status(200).json(returnValue)
} catch (err) {
@@ -317,18 +338,14 @@ async function getLastXChanges (req, res, next) {
try {
const session = await mongoose.startSession()
const repo = req.ctx.repositories.getAuditRepository()
- const targetUUID = req.ctx.params.target_uuid
+ const orgRepo = req.ctx.repositories.getBaseOrgRepository()
+ const identifier = req.ctx.params.org_identifier
+ const identifierIsUUID = validateUUID(identifier)
const numberOfChanges = parseInt(req.ctx.params.number_of_changes)
let returnValue
- if (!targetUUID) {
- logger.info({ uuid: req.ctx.uuid, message: 'Missing org_identifier parameter' })
- return res.status(400).json(error.missingRequiredField('org_identifier'))
- }
-
- if (!validateUUID(targetUUID)) {
- logger.info({ uuid: req.ctx.uuid, message: 'Invalid target_uuid format' })
- return res.status(400).json(error.invalidUUID('target_uuid'))
+ if (!identifier) {
+ return res.status(400).json(error.missingRequiredField('identifier'))
}
if (isNaN(numberOfChanges) || numberOfChanges < 1) {
@@ -339,6 +356,23 @@ async function getLastXChanges (req, res, next) {
try {
session.startTransaction()
+ // Find the target organization by either UUID or shortname
+ const targetOrg = identifierIsUUID
+ ? await orgRepo.findOneByUUID(identifier, { session })
+ : await orgRepo.findOneByShortName(identifier, { session })
+
+ if (!targetOrg) {
+ logger.info({
+ uuid: req.ctx.uuid,
+ message: `No organization found with ${identifierIsUUID ? 'UUID' : 'shortname'} ${identifier}`
+ })
+ await session.abortTransaction()
+ return res.status(404).json(error.orgDne(identifier))
+ }
+
+ // Get the org's UUID for audit lookup
+ const targetUUID = targetOrg.UUID
+
const lastChanges = await repo.getLastXChanges(targetUUID, numberOfChanges, { session })
if (!lastChanges || lastChanges.length === 0) {
@@ -362,7 +396,7 @@ async function getLastXChanges (req, res, next) {
logger.info({
uuid: req.ctx.uuid,
- message: `Last ${numberOfChanges} changes for ${targetUUID} sent to user ${req.ctx.user}`
+ message: `Last ${numberOfChanges} changes for ${identifier} sent to user ${req.ctx.user}`
})
return res.status(200).json(returnValue)
} catch (err) {
@@ -371,10 +405,10 @@ async function getLastXChanges (req, res, next) {
}
module.exports = {
- AUDIT_CREATE_SINGLE: createAuditDocument,
- AUDIT_UPDATE: appendToAuditHistory,
- AUDIT_GET_ALL: getAllAuditDocuments,
- AUDIT_GET_BY_UUID: getAuditByDocumentUUID,
- AUDIT_GET_BY_TARGET_UUID: getAuditByTargetUUID,
+ AUDIT_CREATE_SINGLE: createAuditDocumentForOrg,
+ AUDIT_UPDATE: appendToAuditHistoryForOrg,
+ AUDIT_GET_ALL: getAllOrgAuditDocuments,
+ AUDIT_GET_BY_UUID: getOrgAuditByDocumentUUID,
+ AUDIT_GET_BY_ORG_IDENTIFIER: getOrgAuditByOrgIdentifier,
AUDIT_GET_LAST: getLastXChanges
}
diff --git a/src/controller/audit.controller/index.js b/src/controller/audit.controller/index.js
index ba5b876a2..10da8f70f 100644
--- a/src/controller/audit.controller/index.js
+++ b/src/controller/audit.controller/index.js
@@ -27,15 +27,15 @@ router.get('/audit/org/document/:document_uuid',
)
// Get audit by org identifier (Secretariat or Admin)
-router.get('/audit/org/:target_uuid',
+router.get('/audit/org/:org_identifier',
mw.validateUser,
mw.onlySecretariatOrAdmin,
auditMw.parseGetParams,
- controller.AUDIT_GET_BY_TARGET_UUID
+ controller.AUDIT_GET_BY_ORG_IDENTIFIER
)
// Get last X changes (Secretariat or Org Admin)
-router.get('/audit/org/:target_uuid/:number_of_changes',
+router.get('/audit/org/:org_identifier/:number_of_changes',
mw.onlySecretariatOrAdmin,
mw.validateUser,
auditMw.parseGetParams,
diff --git a/src/controller/conversation.controller/conversation.controller.js b/src/controller/conversation.controller/conversation.controller.js
index db9aab09a..0ed9a2c97 100644
--- a/src/controller/conversation.controller/conversation.controller.js
+++ b/src/controller/conversation.controller/conversation.controller.js
@@ -19,73 +19,27 @@ async function getAllConversations (req, res, next) {
return res.status(200).json(response)
}
-async function getConversationsForOrg (req, res, next) {
- const session = await mongoose.startSession()
-
- try {
- session.startTransaction()
-
- const repo = req.ctx.repositories.getConversationRepository()
- const orgRepo = req.ctx.repositories.getBaseOrgRepository()
- const requesterOrg = req.ctx.org
- const targetOrgUUID = req.params.uuid
-
- // Make sure target org matches user org if not secretariat
- const isSecretariat = await orgRepo.isSecretariatByShortName(requesterOrg, { session })
- const requesterOrgUUID = await orgRepo.getOrgUUID(requesterOrg, { session })
- if (!isSecretariat && (requesterOrgUUID !== targetOrgUUID)) {
- return res.status(400).json({ message: 'User is not secretariat or admin for target org' })
- }
-
- // temporary measure to allow tests to work after fixing #920
- // tests required changing the global limit to force pagination
- if (req.TEST_PAGINATOR_LIMIT) {
- CONSTANTS.PAGINATOR_OPTIONS.limit = req.TEST_PAGINATOR_LIMIT
- }
-
- const options = CONSTANTS.PAGINATOR_OPTIONS
- options.sort = { posted_at: 'desc' }
+async function getConversationsForTargetUUID (req, res, next) {
+ const repo = req.ctx.repositories.getConversationRepository()
+ const targetUUID = req.params.uuid
- const response = await repo.getAllByTargetUUID(targetOrgUUID, options)
- await session.commitTransaction()
- return res.status(200).json(response)
- } catch (err) {
- if (session && session.inTransaction()) {
- await session.abortTransaction()
- }
- next(err)
- } finally {
- if (session && session.id) { // Check if session is still valid before trying to end
- try {
- await session.endSession()
- } catch (sessionEndError) {
- logger.error({ uuid: req.ctx.uuid, message: 'Error ending session in finally block', error: sessionEndError })
- }
- }
- }
+ const response = await repo.getAllByTargetUUID(targetUUID)
+ return res.status(200).json(response)
}
-async function createConversationForOrg (req, res, next) {
+async function createConversationForTargetUUID (req, res, next) {
const session = await mongoose.startSession()
try {
session.startTransaction()
const repo = req.ctx.repositories.getConversationRepository()
- const orgRepo = req.ctx.repositories.getBaseOrgRepository()
const userRepo = req.ctx.repositories.getBaseUserRepository()
const requesterOrg = req.ctx.org
const requesterUsername = req.ctx.user
- const targetOrgUUID = req.params.uuid
+ const targetUUID = req.params.uuid
const body = req.body
- // Make sure target org matches user org if not secretariat
- const isSecretariat = await orgRepo.isSecretariatByShortName(requesterOrg, { session })
- const requesterOrgUUID = await orgRepo.getOrgUUID(requesterOrg, { session })
- if (!isSecretariat && (requesterOrgUUID !== targetOrgUUID)) {
- return res.status(400).json({ message: 'User is not secretariat or admin for target org' })
- }
-
const user = await userRepo.findOneByUsernameAndOrgShortname(requesterUsername, requesterOrg, { session })
if (!body.body) {
@@ -93,10 +47,10 @@ async function createConversationForOrg (req, res, next) {
}
const conversationBody = {
- target_uuid: targetOrgUUID,
+ target_uuid: targetUUID,
author_id: user.UUID,
author_name: [user.name.first, user.name.last].join(' '),
- author_role: isSecretariat ? 'Secretariat' : 'Partner',
+ author_role: 'Secretariat',
visibility: body.visibility ? body.visibility.toLowerCase() : 'private',
body: body.body
}
@@ -129,20 +83,20 @@ async function createConversationForOrg (req, res, next) {
async function updateMessage (req, res, next) {
const repo = req.ctx.repositories.getConversationRepository()
- const targetOrgUUID = req.params.uuid
+ const targetUUID = req.params.uuid
const body = req.body
if (!body.body) {
return res.status(400).json({ message: 'Missing required field body' })
}
- const result = await repo.updateConversation(body, targetOrgUUID)
+ const result = await repo.updateConversation(body, targetUUID)
return res.status(200).json(result)
}
module.exports = {
getAllConversations,
- getConversationsForOrg,
- createConversationForOrg,
+ getConversationsForTargetUUID,
+ createConversationForTargetUUID,
updateMessage
}
diff --git a/src/controller/conversation.controller/index.js b/src/controller/conversation.controller/index.js
index dfcb2b792..d2a8c44e0 100644
--- a/src/controller/conversation.controller/index.js
+++ b/src/controller/conversation.controller/index.js
@@ -15,33 +15,22 @@ router.get('/conversation',
controller.getAllConversations
)
-// Get conversations for all orgs - SEC only
-router.get('/conversation/org',
+// Get all conversations for target UUID - SEC only
+router.get('/conversation/target/:uuid',
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 }),
- controller.getAllConversations // TODO: for now, all conversations are targeted to orgs. Update this when conversations added for other objects
+ controller.getConversationsForTargetUUID
)
-// Get conversations for org - SEC/ADMIN
-router.get('/conversation/org/:uuid',
+// Post conversation for target UUID - SEC only
+router.post('/conversation/target/:uuid',
mw.validateUser,
- mw.onlySecretariatOrAdmin,
- query().custom((query) => { return mw.validateQueryParameterNames(query, ['page']) }),
- query(['page']).custom((val) => { return mw.containsNoInvalidCharacters(val) }),
- query(['page']).optional().isInt({ min: CONSTANTS.PAGINATOR_PAGE }),
- param(['uuid']).isUUID(4),
- controller.getConversationsForOrg
-)
-
-// Post conversation for org - SEC/ADMIN
-router.post('/conversation/org/:uuid',
- mw.validateUser,
- mw.onlySecretariatOrAdmin,
+ mw.onlySecretariat,
param(['uuid']).isUUID(4),
- controller.createConversationForOrg
+ controller.createConversationForTargetUUID
)
// Update conversation message - SEC only
diff --git a/src/controller/org.controller/index.js b/src/controller/org.controller/index.js
index 00ab0aba5..10d0ef5be 100644
--- a/src/controller/org.controller/index.js
+++ b/src/controller/org.controller/index.js
@@ -161,6 +161,8 @@ router.get('/registry/org/:shortname/users',
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,
@@ -237,6 +239,7 @@ router.get('/registry/org/:shortname/id_quota',
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)
@@ -311,6 +314,7 @@ router.get('/registry/org/:identifier',
*/
mw.useRegistry(),
mw.validateUser,
+ query().custom((query) => { return mw.validateQueryParameterNames(query, ['']) }),
parseError,
parseGetParams,
controller.ORG_SINGLE
@@ -394,27 +398,41 @@ router.get('/registry/org/:shortname/user/:username',
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,
controller.USER_SINGLE
)
router.post('/registry/org',
/*
- #swagger.tags = ['Organization']
- #swagger.operationId = 'orgAll'
- #swagger.summary = "Retrieves all organizations (accessible to Secretariat)"
+ #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: Retrieves information about all organizations
"
+ Secretariat: Creates a new organization
"
#swagger.parameters['$ref'] = [
- '#/components/parameters/pageQuery',
- '#/components/parameters/registry',
'#/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' }
+ ]
+ }
+ }
+ }
+ }
#swagger.responses[200] = {
description: 'Returns information about all organizations, along with pagination fields if results span multiple pages of data',
content: {
@@ -469,6 +487,7 @@ router.post('/registry/org',
mw.useRegistry(),
mw.validateUser,
mw.onlySecretariat,
+ query().custom((query) => { return mw.validateQueryParameterNames(query, ['']) }),
parsePostParams,
parseError,
controller.REGISTRY_CREATE_ORG
@@ -491,7 +510,6 @@ router.put('/registry/org/:shortname',
'#/components/parameters/newShortname',
'#/components/parameters/active_roles_add',
'#/components/parameters/active_roles_remove',
- '#/components/parameters/registry',
'#/components/parameters/apiEntityHeader',
'#/components/parameters/apiUserHeader',
'#/components/parameters/apiSecretHeader'
@@ -733,10 +751,10 @@ router.put('/registry/org/:shortname/user/:username',
mw.onlyOrgWithPartnerRole,
query().custom((query) => {
return mw.validateQueryParameterNames(query, ['active', 'new_username', 'org_short_name', 'name.first', 'name.last', 'name.middle',
- 'name.suffix', 'active_roles.add', 'active_roles.remove', 'registry'])
+ 'name.suffix', 'active_roles.add', 'active_roles.remove'])
}),
query(['active', 'new_username', 'org_short_name', 'name.first', 'name.last', 'name.middle',
- 'name.suffix', 'active_roles.add', 'active_roles.remove', 'registry']).custom((val) => { return mw.containsNoInvalidCharacters(val) }),
+ 'name.suffix', 'active_roles.add', 'active_roles.remove']).custom((val) => { return mw.containsNoInvalidCharacters(val) }),
param(['shortname']).isString().trim().notEmpty().isLength({ min: CONSTANTS.MIN_SHORTNAME_LENGTH, max: CONSTANTS.MAX_SHORTNAME_LENGTH }),
param(['username']).isString().trim().notEmpty().custom(isValidUsername),
query(['active']).optional().isBoolean({ loose: true }),
@@ -847,7 +865,6 @@ router.get('/org',
Secretariat: Retrieves information about all organizations
"
#swagger.parameters['$ref'] = [
'#/components/parameters/pageQuery',
- '#/components/parameters/registry',
'#/components/parameters/apiEntityHeader',
'#/components/parameters/apiUserHeader',
'#/components/parameters/apiSecretHeader'
@@ -906,11 +923,10 @@ router.get('/org',
}
}
*/
- param(['registry']).optional().isBoolean(),
mw.handleRegistryParameter,
mw.validateUser,
mw.onlySecretariat,
- query().custom((query) => { return mw.validateQueryParameterNames(query, ['page', 'registry']) }),
+ 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,
@@ -930,7 +946,6 @@ router.post(
Secretariat: Creates an organization
"
#swagger.parameters['$ref'] = [
- '#/components/parameters/registry',
'#/components/parameters/apiEntityHeader',
'#/components/parameters/apiUserHeader',
'#/components/parameters/apiSecretHeader'
@@ -939,12 +954,7 @@ router.post(
required: true,
content: {
'application/json': {
- schema: {
- oneOf: [
- { $ref: '../schemas/org/create-org-request.json' },
- { $ref: '../schemas/registry-org/create-registry-org-request.json' }
- ]
- }
+ schema: { $ref: '../schemas/org/create-org-request.json' }
}
}
}
@@ -1004,8 +1014,13 @@ router.post(
*/
mw.validateUser,
mw.onlySecretariat,
- body(['short_name']).isString().trim().notEmpty().isLength({ min: CONSTANTS.MIN_SHORTNAME_LENGTH, max: CONSTANTS.MAX_SHORTNAME_LENGTH }),
- body(['name']).isString().trim().notEmpty(),
+ body(['short_name'])
+ .isString().withMessage(errorMsgs.MUST_BE_STRING).trim()
+ .notEmpty().withMessage(errorMsgs.NOT_EMPTY)
+ .isLength({ min: CONSTANTS.MIN_SHORTNAME_LENGTH, max: CONSTANTS.MAX_SHORTNAME_LENGTH }).withMessage(errorMsgs.SHORTNAME_LENGTH),
+ body(['name'])
+ .isString().withMessage(errorMsgs.MUST_BE_STRING).trim()
+ .notEmpty().withMessage(errorMsgs.NOT_EMPTY),
body(['authority.active_roles']).optional()
.custom(isFlatStringArray)
.customSanitizer(toUpperCaseArray)
@@ -1029,7 +1044,6 @@ router.get(
Secretariat: Retrieves information about any organization
"
#swagger.parameters['identifier'] = { description: 'The shortname or UUID of the organization' }
#swagger.parameters['$ref'] = [
- '#/components/parameters/registry',
'#/components/parameters/apiEntityHeader',
'#/components/parameters/apiUserHeader',
'#/components/parameters/apiSecretHeader'
@@ -1038,11 +1052,7 @@ router.get(
description: 'Returns the organization information',
content: {
"application/json": {
- schema: {
- oneOf: [
- { $ref: '../schemas/org/get-org-response.json' },
- { $ref: '../schemas/registry-org/get-registry-org-response.json' }
- ]
+ schema: { $ref: '../schemas/org/get-org-response.json' }
}
}
}
@@ -1088,10 +1098,9 @@ router.get(
}
}
*/
- param(['registry']).optional().isBoolean(),
- mw.handleRegistryParameter,
mw.validateUser,
param(['identifier']).isString().trim(),
+ query().custom((query) => { return mw.validateQueryParameterNames(query, ['']) }),
parseError,
parseGetParams,
controller.ORG_SINGLE
@@ -1113,7 +1122,6 @@ router.put('/org/:shortname',
'#/components/parameters/newShortname',
'#/components/parameters/active_roles_add',
'#/components/parameters/active_roles_remove',
- '#/components/parameters/registry',
'#/components/parameters/apiEntityHeader',
'#/components/parameters/apiUserHeader',
'#/components/parameters/apiSecretHeader'
@@ -1122,12 +1130,7 @@ router.put('/org/:shortname',
description: 'Returns information about the organization updated',
content: {
"application/json": {
- schema: {
- oneOf: [
- { $ref: '../schemas/org/update-org-response.json' },
- { $ref: '../schemas/registry-org/update-registry-org-response.json' }
- ]
- }
+ schema: { $ref: '../schemas/org/update-org-response.json' }
}
}
}
@@ -1178,6 +1181,7 @@ router.put('/org/:shortname',
parseError,
parsePostParams,
controller.ORG_UPDATE_SINGLE)
+
router.get('/org/:shortname/id_quota',
/*
#swagger.tags = ['Organization']
@@ -1191,7 +1195,6 @@ router.get('/org/:shortname/id_quota',
Secretariat: Retrieves the CVE ID quota for any organization
"
#swagger.parameters['shortname'] = { description: 'The shortname of the organization' }
#swagger.parameters['$ref'] = [
- '#/components/parameters/registry',
'#/components/parameters/apiEntityHeader',
'#/components/parameters/apiUserHeader',
'#/components/parameters/apiSecretHeader'
@@ -1200,11 +1203,7 @@ router.get('/org/:shortname/id_quota',
description: 'Returns the CVE ID quota for an organization',
content: {
"application/json": {
- schema: {
- oneOf: [
- { $ref: '../schemas/org/get-org-quota-response.json' },
- { $ref: '../schemas/registry-org/get-registry-org-quota-response.json' }
- ]
+ schema: { $ref: '../schemas/org/get-org-quota-response.json' }
}
}
}
@@ -1250,9 +1249,9 @@ router.get('/org/:shortname/id_quota',
}
}
*/
-
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)
@@ -1270,7 +1269,6 @@ router.get('/org/:shortname/users',
#swagger.parameters['shortname'] = { description: 'The shortname of the organization' }
#swagger.parameters['$ref'] = [
'#/components/parameters/pageQuery',
- '#/components/parameters/registry',
'#/components/parameters/apiEntityHeader',
'#/components/parameters/apiUserHeader',
'#/components/parameters/apiSecretHeader'
@@ -1279,12 +1277,7 @@ router.get('/org/:shortname/users',
description: 'Returns all users for the organization, along with pagination fields if results span multiple pages of data',
content: {
"application/json": {
- schema: {
- oneOf: [
- { $ref: '../schemas/user/list-users-response.json' },
- { $ref: '../schemas/registry-user/list-registry-users-response.json' }
- ]
- }
+ schema: { $ref: '../schemas/user/list-users-response.json' }
}
}
}
@@ -1329,10 +1322,11 @@ router.get('/org/:shortname/users',
}
}
*/
- param(['registry']).optional().isBoolean(),
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']) }),
+ query(['page']).custom((val) => { return mw.containsNoInvalidCharacters(val) }),
query(['page']).optional().isInt({ min: CONSTANTS.PAGINATOR_PAGE }),
parseError,
parseGetParams,
@@ -1351,7 +1345,6 @@ router.post('/org/:shortname/user',
Secretariat: Creates a user for any organization
"
#swagger.parameters['shortname'] = { description: 'The shortname of the organization' }
#swagger.parameters['$ref'] = [
- '#/components/parameters/registry',
'#/components/parameters/apiEntityHeader',
'#/components/parameters/apiUserHeader',
'#/components/parameters/apiSecretHeader'
@@ -1418,6 +1411,7 @@ router.post('/org/:shortname/user',
mw.onlySecretariatOrAdmin,
mw.onlyOrgWithPartnerRole,
param(['shortname']).isString().trim().notEmpty().isLength({ min: CONSTANTS.MIN_SHORTNAME_LENGTH, max: CONSTANTS.MAX_SHORTNAME_LENGTH }),
+ query().custom((query) => { return mw.validateQueryParameterNames(query, ['']) }),
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),
@@ -1432,6 +1426,7 @@ router.post('/org/:shortname/user',
parseError,
parsePostParams,
controller.USER_CREATE_SINGLE)
+
router.get('/org/:shortname/user/:username',
/*
#swagger.tags = ['Users']
@@ -1502,9 +1497,11 @@ router.get('/org/:shortname/user/:username',
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,
controller.USER_SINGLE)
+
router.put('/org/:shortname/user/:username',
/*
#swagger.tags = ['Users']
@@ -1529,7 +1526,6 @@ router.put('/org/:shortname/user/:username',
'#/components/parameters/nameSuffix',
'#/components/parameters/newUsername',
'#/components/parameters/orgShortname',
- '#/components/parameters/registry',
'#/components/parameters/apiEntityHeader',
'#/components/parameters/apiUserHeader',
'#/components/parameters/apiSecretHeader'
@@ -1588,10 +1584,10 @@ router.put('/org/:shortname/user/:username',
mw.onlyOrgWithPartnerRole,
query().custom((query) => {
return mw.validateQueryParameterNames(query, ['active', 'new_username', 'org_short_name', 'name.first', 'name.last', 'name.middle',
- 'name.suffix', 'active_roles.add', 'active_roles.remove', 'registry'])
+ 'name.suffix', 'active_roles.add', 'active_roles.remove'])
}),
query(['active', 'new_username', 'org_short_name', 'name.first', 'name.last', 'name.middle',
- 'name.suffix', 'active_roles.add', 'active_roles.remove', 'registry']).custom((val) => { return mw.containsNoInvalidCharacters(val) }),
+ 'name.suffix', 'active_roles.add', 'active_roles.remove']).custom((val) => { return mw.containsNoInvalidCharacters(val) }),
param(['shortname']).isString().trim().notEmpty().isLength({ min: CONSTANTS.MIN_SHORTNAME_LENGTH, max: CONSTANTS.MAX_SHORTNAME_LENGTH }),
param(['username']).isString().trim().notEmpty().custom(isValidUsername),
query(['active']).optional().isBoolean({ loose: true }),
@@ -1613,6 +1609,7 @@ router.put('/org/:shortname/user/:username',
parseError,
parsePostParams,
controller.USER_UPDATE_SINGLE)
+
router.put('/org/:shortname/user/:username/reset_secret',
/*
#swagger.tags = ['Users']
@@ -1685,6 +1682,7 @@ router.put('/org/:shortname/user/:username/reset_secret',
mw.onlyOrgWithPartnerRole,
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,
parsePostParams,
controller.USER_RESET_SECRET)
diff --git a/src/controller/org.controller/org.controller.js b/src/controller/org.controller/org.controller.js
index ba90751b8..32ff68af9 100644
--- a/src/controller/org.controller/org.controller.js
+++ b/src/controller/org.controller/org.controller.js
@@ -67,6 +67,10 @@ async function getOrg (req, res, next) {
returnValue = await repo.getOrg(identifier, identifierIsUUID, { session }, !req.useRegistry)
} catch (error) {
await session.abortTransaction()
+ // Handle the specific error thrown by BaseOrgRepository.createOrg
+ if (error.message && error.message.includes('Unknown Org type requested')) {
+ return res.status(400).json({ message: error.message })
+ }
throw error
} finally {
await session.endSession()
@@ -85,7 +89,7 @@ async function getOrg (req, res, next) {
/**
* Get the details of all users from an org given the specified shortname
- * Called by GET /api/org/{shortname}/users
+ * Called by GET /api/registry/org/{shortname}/users, GET /api/org/{shortname}/users
**/
async function getUsers (req, res, next) {
try {
@@ -118,7 +122,7 @@ async function getUsers (req, res, next) {
return res.status(403).json(error.notSameOrgOrSecretariat())
}
- const payload = await userRepo.getAllUsersByOrgShortname(orgShortName, options, true)
+ const payload = await userRepo.getAllUsersByOrgShortname(orgShortName, options, !req.useRegistry)
logger.info({ uuid: req.ctx.uuid, message: `The users of ${orgShortName} organization were sent to the user.` })
return res.status(200).json(payload)
@@ -129,7 +133,7 @@ async function getUsers (req, res, next) {
/**
* Get the details of a single user for the specified username
- * Called by GET /api/org/{shortname}/user/{username}
+ * Called by GET /api/registry/org/{shortname}/user/{username}, GET /api/org/{shortname}/user/{username}
**/
async function getUser (req, res, next) {
try {
@@ -234,9 +238,9 @@ async function registryCreateOrg (req, res, next) {
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({ 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', details: [{ param: 'authority', msg: 'Parameter must be a one-dimensional array of strings' }] })
}
- return res.status(400).json({ message: 'Parameters were invalid', errors: result.errors })
+ return res.status(400).json({ error: 'BAD_INPUT', message: 'Parameters were invalid', errors: result.errors })
}
// Check to see if the org already exists
@@ -249,7 +253,8 @@ async function registryCreateOrg (req, res, next) {
// If we get here, we know we are good to create
const userRepo = req.ctx.repositories.getBaseUserRepository()
const requestingUserUUID = await userRepo.getUserUUID(req.ctx.user, req.ctx.org, { session })
- returnValue = await repo.createOrg(req.ctx.body, { session, upsert: true }, false, requestingUserUUID)
+ const isSecretariat = await repo.isSecretariatByShortName(req.ctx.org, { session })
+ returnValue = await repo.createOrg(req.ctx.body, { session, upsert: true }, false, requestingUserUUID, isSecretariat)
await session.commitTransaction()
logger.info({
@@ -293,7 +298,10 @@ async function createOrg (req, res, next) {
await session.abortTransaction()
return res.status(400).json(error.orgExists(body?.short_name))
}
- returnValue = await repo.createOrg(req.ctx.body, { session, upsert: true }, true)
+ const isSecretariat = await repo.isSecretariatByShortName(req.ctx.org, { session })
+ const userRepo = req.ctx.repositories.getBaseUserRepository()
+ const requestingUserUUID = await userRepo.getUserUUID(req.ctx.user, req.ctx.org, { session })
+ returnValue = await repo.createOrg(req.ctx.body, { session, upsert: true }, true, requestingUserUUID, isSecretariat)
await session.commitTransaction()
} catch (error) {
@@ -366,7 +374,9 @@ async function registryUpdateOrg (req, res, next) {
const userRepo = req.ctx.repositories.getBaseUserRepository()
const requestingUserUUID = await userRepo.getUserUUID(req.ctx.user, req.ctx.org, { session })
- const updatedOrg = await orgRepository.updateOrg(shortNameUrlParameter, queryParametersJson, { session }, false, requestingUserUUID)
+ const isSecretariat = await orgRepository.isSecretariatByShortName(req.ctx.org, { session })
+ const isAdmin = await userRepo.isAdmin(req.ctx.user, req.ctx.org, { session })
+ const updatedOrg = await orgRepository.updateOrg(shortNameUrlParameter, queryParametersJson, { session }, false, requestingUserUUID, isAdmin, isSecretariat)
responseMessage = { message: `${updatedOrg.short_name} organization was successfully updated.`, updated: updatedOrg } // Clarify message
const payload = { action: 'update_org', change: `${updatedOrg.short_name} organization was successfully updated.`, org: updatedOrg }
@@ -413,9 +423,13 @@ async function updateOrg (req, res, next) {
return res.status(403).json(error.duplicateShortname(queryParametersJson.new_short_name))
}
- const updatedOrg = await orgRepository.updateOrg(shortNameUrlParameter, queryParametersJson, { session }, true)
-
const userRepo = req.ctx.repositories.getBaseUserRepository()
+ const isSecretariat = await orgRepository.isSecretariatByShortName(req.ctx.org, { session })
+ const isAdmin = await userRepo.isAdmin(req.ctx.user, req.ctx.org, { session })
+ const requestingUserUUID = await userRepo.getUserUUID(req.ctx.user, req.ctx.org, { session })
+
+ const updatedOrg = await orgRepository.updateOrg(shortNameUrlParameter, queryParametersJson, { session }, true, requestingUserUUID, isAdmin, isSecretariat)
+
responseMessage = { message: `${updatedOrg.short_name} organization was successfully updated.`, updated: updatedOrg } // Clarify message
const payload = { action: 'update_org', change: `${updatedOrg.short_name} organization was successfully updated.`, org: updatedOrg }
payload.user_UUID = await userRepo.getUserUUID(req.ctx.user, updatedOrg.UUID)
@@ -438,7 +452,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/org/{shortname}/user
+ * Called by POST /api/registry/org/{shortname}/user, POST /api/org/{shortname}/user
**/
async function createUser (req, res, next) {
const session = await mongoose.startSession()
@@ -447,6 +461,7 @@ 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
@@ -472,6 +487,9 @@ async function createUser (req, res, next) {
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 (body?.role && !constants.USER_ROLES.includes(body?.role)) {
+ 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()
@@ -534,7 +552,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 /org/{shortname}/user/{username}, PUT /org/{shortname}/user/{username}
**/
async function updateUser (req, res, next) {
const session = await mongoose.startSession()
diff --git a/src/controller/org.controller/org.middleware.js b/src/controller/org.controller/org.middleware.js
index 6e8cbcd0c..cd9d2333e 100644
--- a/src/controller/org.controller/org.middleware.js
+++ b/src/controller/org.controller/org.middleware.js
@@ -48,6 +48,18 @@ function validateCreateOrgParameters () {
.isArray(),
body(['root_or_tlr']).default(false)
.isBoolean(),
+ body(['vulnerability_advisory_locations'])
+ .default([])
+ .custom(isFlatStringArray),
+ body(['advisory_location_require_credentials'])
+ .default(false)
+ .isBoolean(),
+ body(['tl_root_start_date'])
+ .default(null)
+ .isDate(),
+ body(['is_cna_discussion_list'])
+ .default(false)
+ .isBoolean(),
body(
[
'charter_or_scope',
@@ -58,7 +70,10 @@ function validateCreateOrgParameters () {
'contact_info.poc_email',
'contact_info.poc_phone',
'contact_info.org_email',
- 'contact_info.website'
+ 'contact_info.website',
+ 'cna_role_type',
+ 'cna_country',
+ 'industry'
])
.default('')
.isString(),
@@ -119,7 +134,14 @@ function validateCreateOrgParameters () {
'contact_info.poc_phone',
'contact_info.org_email',
'contact_info.additional_contact_users',
- 'contact_info.website')
+ 'contact_info.website',
+ 'cna_role_type',
+ 'cna_country',
+ 'vulnerability_advisory_locations',
+ 'advisory_location_require_credentials',
+ 'industry',
+ 'tl_root_start_date',
+ 'is_cna_discussion_list')
]
}
@@ -169,7 +191,7 @@ function validateUpdateOrgParameters () {
const useRegistry = req.query.registry === 'true'
const legacyParametersOnly = ['id_quota', 'name']
- const registryParametersOnly = ['hard_quota', 'long_name', 'cve_program_org_function', 'oversees', 'root_or_tlr', 'charter_or_scope', 'disclosure_policy', 'product_list']
+ const registryParametersOnly = ['hard_quota', 'long_name', 'cve_program_org_function', 'oversees', 'root_or_tlr', 'charter_or_scope', 'disclosure_policy', 'product_list', 'cna_role_type', 'cna_country', 'vulnerability_advisory_locations', 'advisory_location_require_credentials', 'industry', 'tl_root_start_date', 'is_cna_discussion_list']
const sharedParameters = ['new_short_name', 'active_roles.add', 'active_roles.remove', 'registry']
const allParameters = [
@@ -191,28 +213,40 @@ function validateUpdateOrgParameters () {
if (useRegistry) {
validations.push(
-
- query(['hard_quota']).optional().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),
+ query(['hard_quota'])
+ .optional()
+ .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),
query(['long_name']).optional().isString().trim().notEmpty(),
query(['oversees']).optional().isArray(),
query(['root_or_tlr']).optional().isBoolean(),
- query(
- [
- 'cve_program_org_function',
- 'charter_or_scope',
- 'disclosure_policy',
- 'product_list',
- 'contact_info.poc',
- 'contact_info.poc_email',
- 'contact_info.poc_phone',
- 'contact_info.org_email',
- 'contact_info.website'
- ])
+ query([
+ 'cve_program_org_function',
+ 'charter_or_scope',
+ 'disclosure_policy',
+ 'product_list',
+ 'contact_info.poc',
+ 'contact_info.poc_email',
+ 'contact_info.poc_phone',
+ 'contact_info.org_email',
+ 'contact_info.website',
+ 'cna_role_type',
+ 'cna_country',
+ 'vulnerability_advisory_locations',
+ 'advisory_location_require_credentials',
+ 'industry',
+ 'tl_root_start_date',
+ 'is_cna_discussion_list'
+ ])
.optional()
.isString(),
...isNotAllowedQuery(...legacyParametersOnly)
// if we decide that we want to allow more, we can add them here.
-
)
} else {
validations.push(
@@ -273,10 +307,20 @@ function isUserRole (val) {
function parsePostParams (req, res, next) {
utils.reqCtxMapping(req, 'body', [])
utils.reqCtxMapping(req, 'query', [
- 'new_short_name', 'name', 'id_quota', 'active',
- 'active_roles.add', 'active_roles.remove',
- 'new_username', 'org_short_name',
- 'name.first', 'name.last', 'name.middle', 'name.suffix', 'long_name', 'cve_program_org_function',
+ 'new_short_name',
+ 'name',
+ 'id_quota',
+ 'active',
+ 'active_roles.add',
+ 'active_roles.remove',
+ 'new_username',
+ 'org_short_name',
+ 'name.first',
+ 'name.last',
+ 'name.middle',
+ 'name.suffix',
+ 'long_name',
+ 'cve_program_org_function',
'charter_or_scope',
'disclosure_policy',
'product_list',
@@ -285,7 +329,16 @@ function parsePostParams (req, res, next) {
'contact_info.poc_phone',
'contact_info.org_email',
'hard_quota',
- 'contact_info.website', 'root_or_tlr', 'oversees'
+ 'contact_info.website',
+ 'root_or_tlr',
+ 'oversees',
+ 'cna_role_type',
+ 'cna_country',
+ 'vulnerability_advisory_locations',
+ 'advisory_location_require_credentials',
+ 'industry',
+ 'tl_root_start_date',
+ 'is_cna_discussion_list'
])
utils.reqCtxMapping(req, 'params', ['shortname', 'username'])
next()
diff --git a/src/controller/registry-org.controller/index.js b/src/controller/registry-org.controller/index.js
index 25bc038a1..36dad9aa7 100644
--- a/src/controller/registry-org.controller/index.js
+++ b/src/controller/registry-org.controller/index.js
@@ -213,7 +213,6 @@ router.post('/registryOrg',
*/
mw.useRegistry(),
mw.validateUser,
- mw.onlySecretariat,
parseError,
parsePostParams,
controller.CREATE_ORG
@@ -300,7 +299,6 @@ router.put('/registryOrg/:shortname',
*/
mw.useRegistry(),
mw.validateUser,
- mw.onlySecretariat,
param(['shortname']).isString().trim(),
parseError,
parsePostParams,
diff --git a/src/controller/registry-org.controller/registry-org.controller.js b/src/controller/registry-org.controller/registry-org.controller.js
index 638650b5d..a0f98e611 100644
--- a/src/controller/registry-org.controller/registry-org.controller.js
+++ b/src/controller/registry-org.controller/registry-org.controller.js
@@ -1,6 +1,7 @@
const mongoose = require('mongoose')
const logger = require('../../middleware/logger')
const { getConstants } = require('../../constants')
+const _ = require('lodash')
const errors = require('./error')
const error = new errors.RegistryOrgControllerError()
const validateUUID = require('uuid').validate
@@ -115,7 +116,11 @@ async function createOrg (req, res, next) {
try {
const session = await mongoose.startSession()
const repo = req.ctx.repositories.getBaseOrgRepository()
+ const userRepo = req.ctx.repositories.getBaseUserRepository()
const body = req.ctx.body
+ const isSecretariat = await repo.isSecretariatByShortName(req.ctx.org, { session })
+ const requestingUserUUID = await userRepo.getUserUUID(req.ctx.user, req.ctx.org, { session })
+
let createdOrg
// Do not allow the user to pass in a UUID
@@ -125,7 +130,7 @@ async function createOrg (req, res, next) {
try {
session.startTransaction()
- const result = repo.validateOrg(req.ctx.body, { session })
+ const result = repo.validateOrg(body, { session })
if (!result.isValid) {
logger.error(JSON.stringify({ uuid: req.ctx.uuid, message: 'CVE JSON schema validation FAILED.' }))
await session.abortTransaction()
@@ -151,27 +156,45 @@ async function createOrg (req, res, next) {
}
// Create the org – repo.createOrg will handle field mapping
- createdOrg = await repo.createOrg(body, { session, upsert: true })
+ createdOrg = await repo.createOrg(body, { session, upsert: true }, false, requestingUserUUID, isSecretariat)
await session.commitTransaction()
} catch (createErr) {
await session.abortTransaction()
+ if (createErr.message && createErr.message.includes('Unknown Org type requested')) {
+ return res.status(400).json({ message: createErr.message })
+ }
throw createErr
} finally {
await session.endSession()
}
- const responseMessage = {
- message: `${body?.short_name} organization was successfully created.`,
- created: createdOrg
- }
+ let responseMessage
+ let payload
+ if (isSecretariat) {
+ responseMessage = {
+ message: `${body?.short_name} organization was successfully created.`,
+ created: createdOrg
+ }
- const payload = {
- action: 'create_org',
- change: `${body?.short_name} organization was successfully created.`,
- req_UUID: req.ctx.uuid,
- org_UUID: createdOrg.UUID,
- org: createdOrg
+ payload = {
+ action: 'create_org',
+ change: `${body?.short_name} organization was successfully created.`,
+ req_UUID: req.ctx.uuid,
+ org_UUID: createdOrg.UUID,
+ org: createdOrg
+ }
+ } else {
+ payload = {
+ action: 'create_review_org',
+ change: body?.short_name + ' was successfully requested to be Reviewed.',
+ req_UUID: req.ctx.uuid
+ }
+
+ responseMessage = {
+ message: body?.short_name + ' was successfully received to be reviewed. By using Load ReviewObject data, you can check for a reply from the Secretariat about Joint Approval items.',
+ created: body?.shortName
+ }
}
logger.info(JSON.stringify(payload))
@@ -198,16 +221,42 @@ async function updateOrg (req, res, next) {
const session = await mongoose.startSession()
const shortName = req.ctx.params.shortname
const repo = req.ctx.repositories.getBaseOrgRepository()
- const body = req.ctx.body
+ const userRepo = req.ctx.repositories.getBaseUserRepository()
+ const conversationRepo = req.ctx.repositories.getConversationRepository()
+ const { conversation, ...body } = req.ctx.body
let updatedOrg
+ let jointApprovalRequired
try {
session.startTransaction()
+ const isSecretariat = await repo.isSecretariatByShortName(req.ctx.org, { session })
+ const isAdmin = await userRepo.isAdmin(req.ctx.user, req.ctx.org, { session })
+ const requestingUser = await userRepo.findOneByUsernameAndOrgShortname(req.ctx.user, req.ctx.org, { session })
const org = await repo.findOneByShortName(shortName)
+
+ // Edge Case: if a user has requested an org, but it is not approved yet, then we need to check to see if if there is a review org for the shortname request.
+
if (!org) {
- logger.info({ uuid: req.ctx.uuid, message: shortName + ' organization could not be updated because it does not exist.' })
- await session.abortTransaction()
- return res.status(404).json(error.orgDnePathParam(shortName))
+ // resolve edge case
+ const reviewRepo = req.ctx.repositories.getReviewObjectRepository()
+ const reviewOrg = await reviewRepo.getOrgReviewObjectStandaloneByRequestedOrgShortname(shortName, { session })
+
+ // Eventually we should validate this, but this is a bit tricky.
+ if (reviewOrg) {
+ const updateResult = await reviewRepo.updateReviewOrgObject(body, reviewOrg.uuid, { session })
+ if (updateResult) {
+ updatedOrg = reviewOrg
+ if (conversation && conversation.length) {
+ await conversationRepo.processConversationHistory(conversation, updateResult.uuid, requestingUser, isSecretariat, { session })
+ }
+ await session.commitTransaction()
+ return res.status(200).json({ message: 'Review object updated successfully' })
+ }
+ } else {
+ logger.info({ uuid: req.ctx.uuid, message: shortName + ' organization could not be updated because it does not exist.' })
+ await session.abortTransaction()
+ return res.status(404).json(error.orgDnePathParam(shortName))
+ }
}
const result = repo.validateOrg(body, { session })
@@ -227,7 +276,10 @@ async function updateOrg (req, res, next) {
return res.status(400).json(error.duplicateShortname(body?.short_name))
}
- updatedOrg = await repo.updateOrgFull(shortName, body, { session })
+ updatedOrg = await repo.updateOrgFull(shortName, req.ctx.body, { session }, false, requestingUser.UUID, isAdmin, isSecretariat)
+ jointApprovalRequired = _.get(updatedOrg, 'joint_approval_required', false)
+ _.unset(updatedOrg, 'joint_approval_required')
+
await session.commitTransaction()
} catch (updateErr) {
await session.abortTransaction()
@@ -236,20 +288,38 @@ async function updateOrg (req, res, next) {
await session.endSession()
}
- const responseMessage = {
- message: `${body?.short_name} organization was successfully updated.`,
- updated: updatedOrg
- }
+ if (jointApprovalRequired) {
+ const responseMessage = {
+ message: `${body?.short_name} organization was successfully updated, but joint approval is required for some fields. Check the ReviewObject for your org to check for a reply from the Secretariat about Joint Approval items.`,
+ updated: updatedOrg
+ }
- const payload = {
- action: 'update_registry_org',
- change: body?.short_name + ' was successfully updated.',
- req_UUID: req.ctx.uuid,
- org_UUID: await repo.getOrgUUID(req.ctx.org),
- org: updatedOrg
+ const payload = {
+ action: 'update_registry_org',
+ change: body?.short_name + 'organization was successfully updated, but joint approval is required for some fields. Check the ReviewObject for your org to check for a reply from the Secretariat about Joint Approval items.',
+ req_UUID: req.ctx.uuid,
+ org_UUID: await repo.getOrgUUID(req.ctx.org),
+ org: updatedOrg
+ }
+
+ logger.info(JSON.stringify(payload))
+ return res.status(200).json(responseMessage)
+ } else {
+ const responseMessage = {
+ message: `${body?.short_name} organization was successfully updated.`,
+ updated: updatedOrg
+ }
+
+ const payload = {
+ action: 'update_registry_org',
+ change: body?.short_name + ' was successfully updated.',
+ req_UUID: req.ctx.uuid,
+ org_UUID: await repo.getOrgUUID(req.ctx.org),
+ org: updatedOrg
+ }
+ logger.info(JSON.stringify(payload))
+ return res.status(200).json(responseMessage)
}
- logger.info(JSON.stringify(payload))
- return res.status(200).json(responseMessage)
} catch (err) {
next(err)
}
diff --git a/src/controller/registry-org.controller/registry-org.middleware.js b/src/controller/registry-org.controller/registry-org.middleware.js
index e2283f589..325c9d107 100644
--- a/src/controller/registry-org.controller/registry-org.middleware.js
+++ b/src/controller/registry-org.controller/registry-org.middleware.js
@@ -15,7 +15,14 @@ function parsePostParams (req, res, next) {
'charter_or_scope', 'disclosure_policy', 'product_list',
'soft_quota', 'hard_quota',
'contact_info.additional_contact_users', 'contact_info.poc', 'contact_info.poc_email', 'contact_info.poc_phone',
- 'contact_info.admins', 'contact_info.org_email', 'contact_info.website'
+ 'contact_info.admins', 'contact_info.org_email', 'contact_info.website',
+ 'cna_role_type',
+ 'cna_country',
+ 'vulnerability_advisory_locations',
+ 'advisory_location_require_credentials',
+ 'industry',
+ 'tl_root_start_date',
+ 'is_cna_discussion_list'
])
next()
}
diff --git a/src/controller/registry-user.controller/index.js b/src/controller/registry-user.controller/index.js
index 55d650ae3..db97b684b 100644
--- a/src/controller/registry-user.controller/index.js
+++ b/src/controller/registry-user.controller/index.js
@@ -145,7 +145,7 @@ router.get('/registryUser/:identifier',
controller.SINGLE_USER
)
-router.post('/registryUser',
+router.post('/registryUser/:shortname',
/*
#swagger.tags = ['Registry User']
#swagger.operationId = 'createRegistryUser'
@@ -212,9 +212,6 @@ router.post('/registryUser',
*/
mw.validateUser,
mw.onlySecretariat,
- // mw.onlySecretariat, // TODO: permissions
- // TODO: validation
- // parseError,
parsePostParams,
controller.CREATE_USER
)
diff --git a/src/controller/registry-user.controller/registry-user.controller.js b/src/controller/registry-user.controller/registry-user.controller.js
index 7261bda22..2ce44cd53 100644
--- a/src/controller/registry-user.controller/registry-user.controller.js
+++ b/src/controller/registry-user.controller/registry-user.controller.js
@@ -1,16 +1,15 @@
-const argon2 = require('argon2')
-const cryptoRandomString = require('crypto-random-string')
-const uuid = require('uuid')
+const mongoose = require('mongoose')
const logger = require('../../middleware/logger')
const { getConstants } = require('../../constants')
-const RegistryUser = require('../../model/registry-user')
-const RegistryOrg = require('../../model/registry-org')
const errors = require('../user.controller/error')
const error = new errors.UserControllerError()
async function getAllUsers (req, res, next) {
try {
const CONSTANTS = getConstants()
+ const session = await mongoose.startSession()
+ const repo = req.ctx.repositories.getBaseUserRepository()
+ let returnValue
// temporary measure to allow tests to work after fixing #920
// tests required changing the global limit to force pagination
@@ -21,27 +20,15 @@ async function getAllUsers (req, res, next) {
const options = CONSTANTS.PAGINATOR_OPTIONS
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 repo = req.ctx.repositories.getRegistryUserRepository()
- const agt = setAggregateUserObj({})
- const pg = await repo.aggregatePaginate(agt, options)
-
- await RegistryOrg.populateOrgAffiliations(pg.itemsList)
- await RegistryOrg.populateCVEProgramOrgMembership(pg.itemsList)
-
- const payload = { users: pg.itemsList }
-
- if (pg.itemCount >= CONSTANTS.PAGINATOR_OPTIONS.limit) {
- payload.totalCount = pg.itemCount
- payload.itemsPerPage = pg.itemsPerPage
- payload.pageCount = pg.pageCount
- payload.currentPage = pg.currentPage
- payload.prevPage = pg.prevPage
- payload.nextPage = pg.nextPage
+ try {
+ returnValue = await repo.getAllUsers(options)
+ } finally {
+ await session.endSession()
}
logger.info({ uuid: req.ctx.uuid, message: 'The user information was sent to the secretariat user.' })
- return res.status(200).json(payload)
+ return res.status(200).json(returnValue)
} catch (err) {
next(err)
}
@@ -49,13 +36,14 @@ async function getAllUsers (req, res, next) {
async function getUser (req, res, next) {
try {
- const repo = req.ctx.repositories.getRegistryUserRepository()
+ const repo = req.ctx.repositories.getBaseUserRepository()
const identifier = req.ctx.params.identifier
- const agt = setAggregateUserObj({ UUID: identifier })
- let result = await repo.aggregate(agt)
- result = result.length > 0 ? result[0] : null
- logger.info({ uuid: req.ctx.uuid, message: identifier + ' user was sent to the user.', user: result })
+ const result = await repo.findUserByUUID(identifier)
+ if (!result) {
+ logger.info({ uuid: req.ctx.uuid, message: identifier + 'user could not be found.' })
+ return res.status(404).json(error.userDne(identifier))
+ }
return res.status(200).json(result)
} catch (err) {
next(err)
@@ -63,68 +51,67 @@ async function getUser (req, res, next) {
}
async function createUser (req, res, next) {
+ const session = await mongoose.startSession()
try {
- // const requesterUsername = req.ctx.user
- // const requesterShortName = req.ctx.org
- const orgRepo = req.ctx.repositories.getOrgRepository()
- const userRepo = req.ctx.repositories.getUserRepository()
- const registryUserRepo = req.ctx.repositories.getRegistryUserRepository()
+ 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
- // Short circuit if UUID provided
- const bodyKeys = Object.keys(body).map((k) => k.toLowerCase())
- if (bodyKeys.includes('uuid')) {
+ 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'))
}
- // TODO: check if affiliated orgs and program orgs exist, and if their membership limit is reached
+ if ((body?.org_UUID ?? null) || (body?.org_uuid ?? null)) {
+ return res.status(400).json(error.uuidProvided('org'))
+ }
+
+ try {
+ session.startTransaction()
- const newUser = new RegistryUser()
- Object.keys(body).map(k => k.toLowerCase()).forEach(k => {
- if (k === 'user_id' || k === 'username') {
- newUser.user_id = body[k]
- } else if (k === 'name') {
- newUser.name = {
- first: '',
- last: '',
- middle: '',
- suffix: '',
- ...body.name
- }
- } else if (k === 'org_affiliations') {
- // TODO: dedupe
- } else if (k === 'cve_program_org_membership') {
- // TODO: dedupe
+ const result = await userRepo.validateUser(body)
+ 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 (!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 })
}
- })
-
- // TODO: check that requesting user is admin of org for new user
- newUser.UUID = uuid.v4()
- const randomKey = cryptoRandomString({ length: getConstants().CRYPTO_RANDOM_STRING_LENGTH })
- newUser.secret = await argon2.hash(randomKey)
- newUser.last_active = null
- newUser.deactivation_date = null
+ // 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))
+ }
- await registryUserRepo.updateByUUID(newUser.UUID, newUser, { upsert: true })
- const agt = setAggregateUserObj({ UUID: newUser.UUID })
- let result = await registryUserRepo.aggregate(agt)
- result = result.length > 0 ? result[0] : null
+ const users = await userRepo.findUsersByOrgShortname(orgShortName, { session })
+ if (users.length >= 100) {
+ await session.abortTransaction()
+ return res.status(400).json(error.userLimitReached())
+ }
- const payload = {
- action: 'create_registry_user',
- change: result.user_id + ' was successfully created.',
- req_UUID: req.ctx.uuid,
- org_UUID: await orgRepo.getOrgUUID(req.ctx.org),
- user: result
+ returnValue = await userRepo.createUser(orgShortName, body, { session, upsert: true })
+ await session.commitTransaction()
+ } catch (error) {
+ await session.abortTransaction()
+ throw error
+ } finally {
+ await session.endSession()
}
- payload.user_UUID = await userRepo.getUserUUID(req.ctx.user, payload.org_UUID)
- logger.info(JSON.stringify(payload))
- result.secret = randomKey
const responseMessage = {
- message: result.user_id + ' was successfully created.',
- created: result
+ message: `${body?.user_id} + ' was successfully created.`,
+ created: returnValue
}
return res.status(200).json(responseMessage)
@@ -134,55 +121,34 @@ async function createUser (req, res, next) {
}
async function updateUser (req, res, next) {
- try {
- // const username = req.ctx.params.username
- // const shortName = req.ctx.params.shortname
- const userUUID = req.ctx.params.identifier
- const userRepo = req.ctx.repositories.getUserRepository()
- const orgRepo = req.ctx.repositories.getOrgRepository()
- const registryUserRepo = req.ctx.repositories.getRegistryUserRepository()
- // const orgUUID = await orgRepo.getOrgUUID(shortName)
- // Check if requester is Admin of the designated user's org
-
- const user = await registryUserRepo.findOneByUUID(userUUID)
- const newUser = new RegistryUser()
-
- // Sets the name values to what currently exists in the database, this ensures data is retained during partial name updates
- newUser.name.first = user.name.first
- newUser.name.last = user.name.last
- newUser.name.middle = user.name.middle
- newUser.name.suffix = user.name.suffix
-
- // TODO: check permissions
- // Check to ensure that the user has the right permissions to edit the fields tha they are requesting to edit, and fail fast if they do not.
- // if (Object.keys(req.ctx.query).length > 0 && Object.keys(req.ctx.query).some((key) => { return queryParameterPermissions[key] }) && !(isAdmin || isSecretariat)) {
- // logger.info({ uuid: req.ctx.uuid, message: 'The user could not be updated because ' + requesterUsername + ' user is not Org Admin or Secretariat to modify these fields.' })
- // return res.status(403).json(error.notOrgAdminOrSecretariatUpdate())
- // }
-
- for (const k in req.ctx.query) {
- const key = k.toLowerCase()
+ const session = await mongoose.startSession()
+ const userUUID = req.ctx.params.identifier
+ const userRepo = req.ctx.repositories.getBaseUserRepository()
+ const orgRepo = req.ctx.repositories.getBaseOrgRepository()
+ const body = req.ctx.body
+ let result
- if (key === 'new_user_id') {
- newUser.user_id = req.ctx.query.new_user_id
- } else if (key === 'name.first') {
- newUser.name.first = req.ctx.query['name.first']
- } else if (key === 'name.last') {
- newUser.name.last = req.ctx.query['name.last']
- } else if (key === 'name.middle') {
- newUser.name.middle = req.ctx.query['name.middle']
- } else if (key === 'name.suffix') {
- newUser.name.suffix = req.ctx.query['name.suffix']
+ try {
+ session.startTransaction()
+ try {
+ result = await userRepo.validateUser(body)
+ 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' }] })
}
-
- // TODO: process org affiliations and program org membership updates
+ 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 })
+ }
+ await userRepo.updateUserFull(userUUID, body, { session })
+ await session.commitTransaction()
+ } catch (error) {
+ await session.abortTransaction()
+ throw error
+ } finally {
+ await session.endSession()
}
- await registryUserRepo.updateByUUID(userUUID, newUser)
- const agt = setAggregateUserObj({ UUID: userUUID })
- let result = await registryUserRepo.aggregate(agt)
- result = result.length > 0 ? result[0] : null
-
const payload = {
action: 'update_registry_user',
change: result.user_id + ' was successfully updated.',
@@ -242,29 +208,6 @@ async function deleteUser (req, res, next) {
}
}
-function setAggregateUserObj (query) {
- return [
- {
- $match: query
- },
- {
- $project: {
- _id: false,
- UUID: true,
- user_id: true,
- name: true,
- org_affiliations: true,
- cve_program_org_membership: true,
- created: true,
- created_by: true,
- last_updated: true,
- deactivation_date: true,
- last_active: true
- }
- }
- ]
-}
-
module.exports = {
ALL_USERS: getAllUsers,
SINGLE_USER: getUser,
diff --git a/src/controller/review-object.controller/index.js b/src/controller/review-object.controller/index.js
index e13cb5b38..8c2b65a08 100644
--- a/src/controller/review-object.controller/index.js
+++ b/src/controller/review-object.controller/index.js
@@ -2,9 +2,11 @@ const router = require('express').Router()
const controller = require('./review-object.controller')
const mw = require('../../middleware/middleware')
+router.get('/review/byUUID/:uuid', mw.useRegistry(), mw.validateUser, mw.onlySecretariatOrAdmin, controller.getReviewObjectByUUID)
router.get('/review/org/:identifier', mw.useRegistry(), mw.validateUser, mw.onlySecretariat, controller.getReviewObjectByOrgIdentifier)
router.get('/review/orgs', mw.useRegistry(), mw.validateUser, mw.onlySecretariat, controller.getAllReviewObjects)
router.put('/review/org/:uuid', mw.useRegistry(), mw.validateUser, mw.onlySecretariat, controller.updateReviewObjectByReviewUUID)
+router.put('/review/org/:uuid/approve', mw.useRegistry(), mw.validateUser, mw.onlySecretariat, controller.approveReviewObject)
router.post('/review/org/', mw.useRegistry(), mw.validateUser, mw.onlySecretariat, controller.createReviewObject)
module.exports = router
diff --git a/src/controller/review-object.controller/review-object.controller.js b/src/controller/review-object.controller/review-object.controller.js
index 3ef73148f..44717797e 100644
--- a/src/controller/review-object.controller/review-object.controller.js
+++ b/src/controller/review-object.controller/review-object.controller.js
@@ -1,7 +1,11 @@
const validateUUID = require('uuid').validate
+const mongoose = require('mongoose')
+
async function getReviewObjectByOrgIdentifier (req, res, next) {
const repo = req.ctx.repositories.getReviewObjectRepository()
+ const orgRepo = req.ctx.repositories.getBaseOrgRepository()
+ const isSecretariat = await orgRepo.isSecretariatByShortName(req.ctx.org)
const identifier = req.params.identifier
const identifierIsUUID = validateUUID(identifier)
if (!identifier) {
@@ -10,9 +14,9 @@ async function getReviewObjectByOrgIdentifier (req, res, next) {
let value
// We may want this to be something different, but for now we are just testing
if (identifierIsUUID) {
- value = await repo.getOrgReviewObjectByOrgUUID(identifier)
+ value = await repo.getOrgReviewObjectByOrgUUID(identifier, isSecretariat)
} else {
- value = await repo.getOrgReviewObjectByOrgShortname(identifier)
+ value = await repo.getOrgReviewObjectByOrgShortname(identifier, isSecretariat)
}
if (!value) {
return res.status(404).json({ message: 'Review Object does not exist' })
@@ -20,19 +24,54 @@ async function getReviewObjectByOrgIdentifier (req, res, next) {
return res.status(200).json(value)
}
+async function getReviewObjectByUUID (req, res, next) {
+ const repo = req.ctx.repositories.getReviewObjectRepository()
+ const orgRepo = req.ctx.repositories.getBaseOrgRepository()
+ const isSecretariat = await orgRepo.isSecretariatByShortName(req.ctx.org)
+ const UUID = req.params.uuid
+ const value = await repo.findOneByUUIDWithConversation(UUID, isSecretariat)
+ return res.status(200).json(value)
+}
+
async function getAllReviewObjects (req, res, next) {
const repo = req.ctx.repositories.getReviewObjectRepository()
const value = await repo.getAllReviewObjects()
return res.status(200).json(value)
}
+async function approveReviewObject (req, res, next) {
+ const repo = req.ctx.repositories.getReviewObjectRepository()
+ const userRepo = req.ctx.repositories.getBaseUserRepository()
+ const UUID = req.params.uuid
+ const session = await mongoose.startSession()
+ let value
+
+ try {
+ session.startTransaction()
+ const requestingUserUUID = await userRepo.getUserUUID(req.ctx.user, req.ctx.org, { session })
+
+ value = await repo.approveReviewOrgObject(UUID, requestingUserUUID, { session })
+ await session.commitTransaction()
+ } catch (updateErr) {
+ await session.abortTransaction()
+ throw updateErr
+ } finally {
+ await session.endSession()
+ }
+
+ if (!value) {
+ return res.status(404).json({ message: `No review object found with UUID ${UUID}` })
+ }
+ return res.status(200).json(value)
+}
+
async function updateReviewObjectByReviewUUID (req, res, next) {
const repo = req.ctx.repositories.getReviewObjectRepository()
const UUID = req.params.uuid
const orgRepo = req.ctx.repositories.getBaseOrgRepository()
const body = req.body
- const result = orgRepo.validateOrg(body.new_review_data)
+ const result = orgRepo.validateOrg(body)
if (!result.isValid) {
return res.status(400).json({ message: 'Invalid new_review_data', errors: result.errors })
}
@@ -47,27 +86,8 @@ async function updateReviewObjectByReviewUUID (req, res, next) {
async function createReviewObject (req, res, next) {
const repo = req.ctx.repositories.getReviewObjectRepository()
- const orgRepo = req.ctx.repositories.getBaseOrgRepository()
const body = req.body
- if (body.uuid) {
- return res.status(400).json({ message: 'Do not pass in a uuid key when creating a review object' })
- }
-
- if (!body.target_object_uuid) {
- return res.status(400).json({ message: 'Missing required field target_object_uuid' })
- }
-
- if (!body.new_review_data) {
- return res.status(400).json({ message: 'Missing required field new_review_data' })
- }
-
- // Validate the data going into "new_review_data"
- const result = orgRepo.validateOrg(body.new_review_data)
- if (!result.isValid) {
- return res.status(400).json({ message: 'Invalid new_review_data', errors: result.errors })
- }
-
const value = await repo.createReviewOrgObject(body)
if (!value) {
@@ -77,7 +97,9 @@ async function createReviewObject (req, res, next) {
}
module.exports = {
getReviewObjectByOrgIdentifier,
+ getReviewObjectByUUID,
getAllReviewObjects,
updateReviewObjectByReviewUUID,
- createReviewObject
+ createReviewObject,
+ approveReviewObject
}
diff --git a/src/controller/user.controller/user.controller.js b/src/controller/user.controller/user.controller.js
index 2bad29144..e66edf0ce 100644
--- a/src/controller/user.controller/user.controller.js
+++ b/src/controller/user.controller/user.controller.js
@@ -31,19 +31,6 @@ async function getAllUsers (req, res, next) {
await session.endSession()
}
- /* const agt = isRegistry ? setAggregateRegistryUserObj({}) : setAggregateUserObj({})
- const pg = await repo.aggregatePaginate(agt, options)
- const payload = { users: pg.itemsList }
-
- if (pg.itemCount >= CONSTANTS.PAGINATOR_OPTIONS.limit) {
- payload.totalCount = pg.itemCount
- payload.itemsPerPage = pg.itemsPerPage
- payload.pageCount = pg.pageCount
- payload.currentPage = pg.currentPage
- payload.prevPage = pg.prevPage
- payload.nextPage = pg.nextPage
- } */
-
logger.info({ uuid: req.ctx.uuid, message: 'The user information was sent to the secretariat user.' })
return res.status(200).json(returnValue)
} catch (err) {
diff --git a/src/middleware/errorMessages.js b/src/middleware/errorMessages.js
index c7aa7db12..7ed28d794 100644
--- a/src/middleware/errorMessages.js
+++ b/src/middleware/errorMessages.js
@@ -3,6 +3,9 @@
module.exports = {
ORG_ROLES: 'Invalid role. Valid roles are CNA, SECRETARIAT',
USER_ROLES: 'Invalid role. Valid role is ADMIN',
+ NOT_EMPTY: 'Value must not be empty',
+ SHORTNAME_LENGTH: 'Value must be between 2 and 20 characters in length.',
+ MUST_BE_STRING: 'Value must be a string',
ID_QUOTA: 'The id_quota does not comply with CVE id quota limitations',
ID_STATES: 'Invalid CVE ID state. Valid states are: RESERVED, PUBLISHED, REJECTED',
ID_MODIFY_STATES: 'Invalid CVE ID state. Valid states are: RESERVED, REJECTED',
diff --git a/src/middleware/middleware.js b/src/middleware/middleware.js
index 83d8c8a66..c07ebab71 100644
--- a/src/middleware/middleware.js
+++ b/src/middleware/middleware.js
@@ -547,6 +547,27 @@ function containsNoInvalidCharacters (val) {
return true
}
+/**
+ * Middleware factory that rejects any keys in the request body
+ * that are not listed in the allowedKeys array.
+ *
+ * @param {Array} allowedKeys - List of permitted keys in req.body
+ * @returns {function} Express middleware
+ */
+function rejectUnexpectedKeys (allowedKeys) {
+ return (req, res, next) => {
+ const bodyKeys = Object.keys(req.body || {})
+ const unexpected = bodyKeys.filter(k => !allowedKeys.includes(k))
+ if (unexpected.length > 0) {
+ return res.status(400).json({
+ error: 'Unexpected keys in request body',
+ unexpected
+ })
+ }
+ next()
+ }
+}
+
module.exports = {
setCacheControl,
optionallyValidateUser,
@@ -572,5 +593,6 @@ module.exports = {
toUpperCaseArray,
toLowerCaseArray,
containsNoInvalidCharacters,
- trimJSONWhitespace
+ trimJSONWhitespace,
+ rejectUnexpectedKeys
}
diff --git a/src/middleware/schemas/ADPOrg.json b/src/middleware/schemas/ADPOrg.json
index fd0998ff0..7979d1f55 100644
--- a/src/middleware/schemas/ADPOrg.json
+++ b/src/middleware/schemas/ADPOrg.json
@@ -9,7 +9,7 @@
{
"properties": {
"authority": {
- "const": "ADP"
+ "const": ["ADP"]
}
}
}
diff --git a/src/middleware/schemas/BaseOrg.json b/src/middleware/schemas/BaseOrg.json
index fdabde496..7ce5aa663 100644
--- a/src/middleware/schemas/BaseOrg.json
+++ b/src/middleware/schemas/BaseOrg.json
@@ -39,6 +39,11 @@
"discriminator": {
"description": "Discriminator key used by Mongoose for type inheritance",
"type": "string"
+ },
+ "timestamp": {
+ "description": "Date/time format based on RFC3339 and ISO ISO8601, with an optional timezone in the format 'yyyy-MM-ddTHH:mm:ss[+-]ZH:ZM'. If timezone offset is not given, GMT (+00:00) is assumed.",
+ "pattern": "^(((2000|2400|2800|(19|2[0-9](0[48]|[2468][048]|[13579][26])))-02-29)|(((19|2[0-9])[0-9]{2})-02-(0[1-9]|1[0-9]|2[0-8]))|(((19|2[0-9])[0-9]{2})-(0[13578]|10|12)-(0[1-9]|[12][0-9]|3[01]))|(((19|2[0-9])[0-9]{2})-(0[469]|11)-(0[1-9]|[12][0-9]|30)))T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(\\.[0-9]+)?(Z|[+-][0-9]{2}:[0-9]{2})?$",
+ "type": "string"
}
},
"properties": {
@@ -62,7 +67,11 @@
}
},
"authority": {
- "$ref": "#/definitions/authority"
+ "type": "array",
+ "uniqueItems": true,
+ "items": {
+ "$ref": "#/definitions/authority"
+ }
},
"root_or_tlr": {
"type": "boolean"
@@ -114,9 +123,35 @@
}
},
"additionalProperties": false
+ },
+ "cna_role_type": {
+ "type": "string"
+ },
+ "cna_country": {
+ "type": "string"
+ },
+ "vulnerability_advisory_locations": {
+ "type": "array",
+ "uniqueItems": true,
+ "items": {
+ "type": "string"
+ }
+ },
+ "advisory_location_require_credentials": {
+ "type": "boolean"
+ },
+ "industry": {
+ "type": "string"
+ },
+ "tl_root_start_date": {
+ "$ref": "#/definitions/timestamp"
+ },
+ "is_cna_discussion_list": {
+ "type": "boolean"
}
},
"required": [
- "short_name"
+ "short_name",
+ "long_name"
]
}
\ No newline at end of file
diff --git a/src/middleware/schemas/BaseUser.json b/src/middleware/schemas/BaseUser.json
index 70c898adc..2c1bf93de 100644
--- a/src/middleware/schemas/BaseUser.json
+++ b/src/middleware/schemas/BaseUser.json
@@ -57,11 +57,6 @@
"UUID": {
"$ref": "#/definitions/uuidType"
},
- "role": {
- "description": "Users permissions, Like ADMIN",
- "type": "string",
- "enum": ["ADMIN"]
- },
"status": {
"description": "User status: 'active' or 'inactive'",
"type": "string",
diff --git a/src/middleware/schemas/BulkDownloadOrg.json b/src/middleware/schemas/BulkDownloadOrg.json
index f29c6336b..ada140853 100644
--- a/src/middleware/schemas/BulkDownloadOrg.json
+++ b/src/middleware/schemas/BulkDownloadOrg.json
@@ -5,11 +5,11 @@
"title": "CVE Bulk Download Organization",
"description": "Schema for a CVE Bulk Download Organization",
"allOf": [
- { "$ref": "BaseOrg" },
+ { "$ref": "/BaseOrg" },
{
"properties": {
"authority": {
- "const": "BULK_DOWNLOAD"
+ "const": ["BULK_DOWNLOAD"]
}
}
}
diff --git a/src/middleware/schemas/CNAOrg.json b/src/middleware/schemas/CNAOrg.json
index 274e61823..c1188c8c4 100644
--- a/src/middleware/schemas/CNAOrg.json
+++ b/src/middleware/schemas/CNAOrg.json
@@ -9,7 +9,7 @@
{
"properties": {
"authority": {
- "const": "CNA"
+ "const": ["CNA"]
},
"oversees": {
"type": "array",
diff --git a/src/middleware/schemas/SecretariatOrg.json b/src/middleware/schemas/SecretariatOrg.json
index 7dcb77975..4e658b571 100644
--- a/src/middleware/schemas/SecretariatOrg.json
+++ b/src/middleware/schemas/SecretariatOrg.json
@@ -9,7 +9,7 @@
{
"properties": {
"authority": {
- "const": "SECRETARIAT"
+ "const": ["SECRETARIAT"]
},
"oversees": {
"type": "array",
diff --git a/src/model/adporg.js b/src/model/adporg.js
index 7932626c0..f5efa867c 100644
--- a/src/model/adporg.js
+++ b/src/model/adporg.js
@@ -5,13 +5,18 @@ const Ajv = require('ajv')
const addFormats = require('ajv-formats')
const BaseOrgSchema = JSON.parse(fs.readFileSync('src/middleware/schemas/BaseOrg.json'))
const AdpOrgSchema = JSON.parse(fs.readFileSync('src/middleware/schemas/ADPOrg.json'))
-const ajv = new Ajv({ allErrors: false })
+const ajv = new Ajv({ allErrors: true })
addFormats(ajv)
ajv.addSchema(BaseOrgSchema)
const validate = ajv.compile(AdpOrgSchema)
-const schema = {}
+// Hard and soft quotas should be retained if something was a cna, then became an adp, then back to cna
+// In general, this should never happen, but we have a test case for it, so I want to make sure it works as expected.
+const schema = {
+ hard_quota: Number,
+ soft_quota: Number
+}
const options = { discriminatorKey: 'kind' }
const ADPSchema = new mongoose.Schema(schema, options)
diff --git a/src/model/baseorg.js b/src/model/baseorg.js
index e1d9c1c42..33edd6cf3 100644
--- a/src/model/baseorg.js
+++ b/src/model/baseorg.js
@@ -23,6 +23,13 @@ const schema = {
org_email: String,
website: String
},
+ cna_role_type: String,
+ cna_country: String,
+ vulnerability_advisory_locations: [String],
+ advisory_location_require_credentials: Boolean,
+ industry: String,
+ tl_root_start_date: Date,
+ is_cna_discussion_list: Boolean,
in_use: Boolean,
created: Date,
last_updated: Date
diff --git a/src/model/bulkdownloadorg.js b/src/model/bulkdownloadorg.js
index 0acba5eeb..e196b5ff3 100644
--- a/src/model/bulkdownloadorg.js
+++ b/src/model/bulkdownloadorg.js
@@ -5,7 +5,7 @@ const Ajv = require('ajv')
const addFormats = require('ajv-formats')
const BaseOrgSchema = JSON.parse(fs.readFileSync('src/middleware/schemas/BaseOrg.json'))
const BulkDownloadOrgSchema = JSON.parse(fs.readFileSync('src/middleware/schemas/BulkDownloadOrg.json'))
-const ajv = new Ajv({ allErrors: false })
+const ajv = new Ajv({ allErrors: true })
addFormats(ajv)
ajv.addSchema(BaseOrgSchema)
diff --git a/src/model/cnaorg.js b/src/model/cnaorg.js
index df1f3ca3e..ab17599c9 100644
--- a/src/model/cnaorg.js
+++ b/src/model/cnaorg.js
@@ -5,7 +5,7 @@ const Ajv = require('ajv')
const addFormats = require('ajv-formats')
const BaseOrgSchema = JSON.parse(fs.readFileSync('src/middleware/schemas/BaseOrg.json'))
const CnaOrgSchema = JSON.parse(fs.readFileSync('src/middleware/schemas/CNAOrg.json'))
-const ajv = new Ajv({ allErrors: false })
+const ajv = new Ajv({ allErrors: true })
addFormats(ajv)
ajv.addSchema(BaseOrgSchema)
diff --git a/src/model/registry-org.js b/src/model/registry-org.js
deleted file mode 100644
index 4e44f491a..000000000
--- a/src/model/registry-org.js
+++ /dev/null
@@ -1,119 +0,0 @@
-const mongoose = require('mongoose')
-const aggregatePaginate = require('mongoose-aggregate-paginate-v2')
-const MongoPaging = require('mongo-cursor-pagination')
-
-const schema = {
- _id: false,
- UUID: String,
- long_name: String,
- short_name: String,
- aliases: [String],
- cve_program_org_function: {
- type: String,
- enum: ['Top Level Root', 'Root', 'CNA', 'CNA-LR', 'Secretariat', 'Board', 'AWG', 'TWG', 'SPWG', 'Bulk Download', 'ADP']
- },
- authority: {
- active_roles: [String]
- },
- reports_to: String,
- oversees: [String],
- root_or_tlr: Boolean,
- users: [String],
- charter_or_scope: String,
- disclosure_policy: String,
- product_list: String,
- soft_quota: Number,
- hard_quota: Number,
- contact_info: {
- additional_contact_users: [String],
- poc: String,
- poc_email: String,
- poc_phone: String,
- admins: [String],
- org_email: String,
- website: String
- },
- in_use: Boolean,
- created: Date,
- last_updated: Date
-}
-
-const orgPrivate = '-_id -soft_quota -hard_quota -contact_info.admins -in_use -created -last_updated -__v'
-// const orgSecretariat = ''
-const RegistryOrgSchema = new mongoose.Schema(schema, { collection: 'RegistryOrg', timestamps: { createdAt: 'created', updatedAt: 'last_updated' } })
-
-RegistryOrgSchema.query.byShortName = function (shortName) {
- return this.where({ short_name: shortName })
-}
-
-RegistryOrgSchema.query.byUUID = function (uuid) {
- return this.where({ UUID: uuid })
-}
-
-RegistryOrgSchema.statics.populateOverseesAndReportsTo = async function (items) { // Assuming the model name is 'RegistryOrg'
- for (const item of items) {
- if (item.oversees.length > 0) {
- const populatedOversees = await Promise.all(
- item.oversees.map(async (uuid) => {
- const org = await RegistryOrg.findOne({ UUID: uuid }).select(orgPrivate)
- return org ? org.toObject() : uuid // Return the org object if found, otherwise return the UUID
- })
- )
- item.oversees = populatedOversees
- }
- if (item.reports_to) {
- const org = await RegistryOrg.findOne({ UUID: item.reports_to }).select(orgPrivate)
- item.reports_to = org ? org.toObject() : item.reports_to // Return the org object if found, otherwise return the UUID
- }
- }
-
- return this
-}
-
-RegistryOrgSchema.statics.populateOrgAffiliations = async function (items) { // Assuming the model name is 'RegistryOrg'
- for (const item of items) {
- if (item.org_affiliations.length > 0) {
- const populatedOrgs = await Promise.all(
- item.org_affiliations.map(async ({ org_id: uuid, ...orgMeta }) => {
- const org = await RegistryOrg.findOne({ UUID: uuid }).select(orgPrivate)
- return {
- org: org ? org.toObject() : uuid, // Return the org object if found, otherwise return the UUID
- ...orgMeta
- }
- })
- )
- item.org_affiliations = populatedOrgs
- }
- }
-
- return this
-}
-
-RegistryOrgSchema.statics.populateCVEProgramOrgMembership = async function (items) { // Assuming the model name is 'RegistryOrg'
- for (const item of items) {
- if (item.cve_program_org_membership.length > 0) {
- const populatedOrgs = await Promise.all(
- item.cve_program_org_membership.map(async ({ program_org: uuid, ...orgMeta }) => {
- const org = await RegistryOrg.findOne({ UUID: uuid }).select(orgPrivate)
- return {
- org: org ? org.toObject() : uuid, // Return the org object if found, otherwise return the UUID
- ...orgMeta
- }
- })
- )
- item.cve_program_org_membership = populatedOrgs
- }
- }
-
- return this
-}
-
-RegistryOrgSchema.index({ UUID: 1 })
-RegistryOrgSchema.index({ 'authority.active_roles': 1 })
-
-RegistryOrgSchema.plugin(aggregatePaginate)
-
-// Cursor pagination
-RegistryOrgSchema.plugin(MongoPaging.mongoosePlugin)
-const RegistryOrg = mongoose.model('RegistryOrg', RegistryOrgSchema)
-module.exports = RegistryOrg
diff --git a/src/model/registry-user.js b/src/model/registry-user.js
deleted file mode 100644
index 8029a3775..000000000
--- a/src/model/registry-user.js
+++ /dev/null
@@ -1,111 +0,0 @@
-const mongoose = require('mongoose')
-const aggregatePaginate = require('mongoose-aggregate-paginate-v2')
-const MongoPaging = require('mongo-cursor-pagination')
-
-const schema = {
- _id: false,
- UUID: String,
- user_id: String,
- secret: String,
- name: {
- first: String,
- last: String,
- middle: String,
- suffix: String
- },
- org_affiliations: [{
- org_id: String,
- email: String,
- phone: String
- }],
- cve_program_org_membership: [{
- program_org: String,
- roles: {
- type: [String],
- enum: ['Chair', 'Member', 'Admin']
- },
- status: {
- type: String,
- enum: ['active', 'inactive']
- }
- }],
- created: Date,
- created_by: String,
- last_updated: Date,
- deactivation_date: Date,
- last_active: Date
-}
-
-const userPrivate = '-secret -_id -org_affiliations._id -cve_program_org_membership._id -created_by -created -last_updated -last_active -__v'
-// const userSecretariat = '-secret'
-const RegistryUserSchema = new mongoose.Schema(schema, { collection: 'RegistryUser', timestamps: { createdAt: 'created', updatedAt: 'last_updated' } })
-
-RegistryUserSchema.query.byUserID = function (userID) {
- return this.where({ user_id: userID })
-}
-
-RegistryUserSchema.query.byUUID = function (uuid) {
- return this.where({ UUID: uuid })
-}
-
-RegistryUserSchema.query.byUserIdAndOrgUUID = function (userId, orgUUID) {
- return this.where({ user_id: userId, 'org_affiliations.org_id': orgUUID })
-}
-
-RegistryUserSchema.statics.populateAdmins = async function (items) { // Assuming the model name is 'RegistryUser'
- for (const item of items) {
- if (item.contact_info && item.contact_info.admins && item.contact_info.admins.length > 0) {
- const populatedAdmins = await Promise.all(
- item.contact_info.admins.map(async (uuid) => {
- const user = await RegistryUser.findOne({ UUID: uuid }).select(userPrivate) // Only return necessary fields
- return user ? user.toObject() : uuid // Return the user object if found, otherwise return the UUID
- })
- )
- item.contact_info.admins = populatedAdmins
- }
- }
-
- return this
-}
-
-RegistryUserSchema.statics.populateUsers = async function (items) { // Assuming the model name is 'RegistryUser'
- for (const item of items) {
- if (item.users && item.users.length > 0) {
- const populatedUsers = await Promise.all(
- item.users.map(async (uuid) => {
- const user = await RegistryUser.findOne({ UUID: uuid }).select(userPrivate) // Only return necessary fields
- return user ? user.toObject() : uuid // Return the user object if found, otherwise return the UUID
- })
- )
- item.users = populatedUsers
- }
- }
-
- return this
-}
-
-RegistryUserSchema.statics.populateAdditionalContactUsers = async function (items) { // Assuming the model name is 'RegistryUser'
- for (const item of items) {
- if (item.contact_info && item.contact_info.additional_contact_users && item.contact_info.additional_contact_users.length > 0) {
- const populatedUsers = await Promise.all(
- item.contact_info.additional_contact_users.map(async (uuid) => {
- const user = await RegistryUser.findOne({ UUID: uuid }).select(userPrivate) // Only return necessary fields
- return user ? user.toObject() : uuid // Return the user object if found, otherwise return the UUID
- })
- )
- item.users = populatedUsers
- }
- }
-
- return this
-}
-
-RegistryUserSchema.index({ UUID: 1 })
-RegistryUserSchema.index({ user_id: 1 })
-
-RegistryUserSchema.plugin(aggregatePaginate)
-
-// Cursor pagination
-RegistryUserSchema.plugin(MongoPaging.mongoosePlugin)
-const RegistryUser = mongoose.model('RegistryUser-Old', RegistryUserSchema)
-module.exports = RegistryUser
diff --git a/src/model/secretariatorg.js b/src/model/secretariatorg.js
index 446fb0ca6..127d236a6 100644
--- a/src/model/secretariatorg.js
+++ b/src/model/secretariatorg.js
@@ -5,7 +5,7 @@ const Ajv = require('ajv')
const addFormats = require('ajv-formats')
const BaseOrgSchema = JSON.parse(fs.readFileSync('src/middleware/schemas/BaseOrg.json'))
const SecretariatOrgSchema = JSON.parse(fs.readFileSync('src/middleware/schemas/SecretariatOrg.json'))
-const ajv = new Ajv({ allErrors: false })
+const ajv = new Ajv({ allErrors: true })
addFormats(ajv)
ajv.addSchema(BaseOrgSchema)
diff --git a/src/repositories/auditRepository.js b/src/repositories/auditRepository.js
index d79578d4a..1beabdbca 100644
--- a/src/repositories/auditRepository.js
+++ b/src/repositories/auditRepository.js
@@ -1,5 +1,6 @@
const Audit = require('../model/audit')
const BaseRepository = require('./baseRepository')
+const BaseOrgRepository = require('./baseOrgRepository')
const uuid = require('uuid')
class AuditRepository extends BaseRepository {
@@ -13,49 +14,52 @@ class AuditRepository extends BaseRepository {
return validateObject
}
- /**
- * Create a new audit document
- */
- async createAuditDocument (data, options = {}) {
- const auditData = {
- uuid: uuid.v4(),
- target_uuid: data.target_uuid,
- history: data.history || []
- }
-
- const audit = new Audit(auditData)
- const result = await audit.save(options)
- return result.toObject()
- }
-
/**
* Append a new entry to the audit history
* Creates document if it doesn't exist
*/
- async appendToAuditHistory (targetUUID, auditObject, changeAuthor, options = {}) {
+ async appendToAuditHistoryForOrg (targetUUID, auditObject, changeAuthor, options = {}) {
const historyEntry = {
timestamp: new Date(),
audit_object: auditObject,
change_author: changeAuthor
}
+ try {
// Try to find existing document
- let audit = await Audit.findOne({ target_uuid: targetUUID })
+ let audit = await this.findOneByTargetUUID(targetUUID, options)
- if (!audit) {
+ if (!audit) {
// Create new document if doesn't exist
- audit = new Audit({
- uuid: uuid.v4(),
- target_uuid: targetUUID,
- history: [historyEntry]
- })
- } else {
+ // Assuming 'uuid' is available for generating a new UUID
+ audit = new Audit({
+ uuid: uuid.v4(),
+ target_uuid: targetUUID,
+ history: [historyEntry]
+ })
+ } else {
// Append to existing history
- audit.history.push(historyEntry)
+ audit.history.push(historyEntry)
+ }
+
+ const result = await audit.save(options)
+ return result.toObject()
+ } catch (error) {
+ throw new Error('Failed to save audit history entry.')
}
+ }
- const result = await audit.save(options)
- return result.toObject()
+ /**
+ * Find audit document by target UUID
+ */
+ async findOneByOrgShortname (orgShortName, options = {}) {
+ const baseOrgRepository = new BaseOrgRepository()
+ const org = await baseOrgRepository.findOneByShortName(orgShortName)
+ if (!org) {
+ return null
+ }
+ const query = { target_uuid: org.UUID }
+ return this.collection.findOne(query, null, options)
}
/**
@@ -63,7 +67,8 @@ class AuditRepository extends BaseRepository {
*/
async findOneByTargetUUID (targetUUID, options = {}) {
const query = { target_uuid: targetUUID }
- return this.collection.findOne(query, null, options)
+ const auditObject = await Audit.findOne(query, null, options)
+ return auditObject
}
/**
diff --git a/src/repositories/baseOrgRepository.js b/src/repositories/baseOrgRepository.js
index 04c470e98..30945fe65 100644
--- a/src/repositories/baseOrgRepository.js
+++ b/src/repositories/baseOrgRepository.js
@@ -9,6 +9,7 @@ const uuid = require('uuid')
const _ = require('lodash')
const BaseOrg = require('../model/baseorg')
const AuditRepository = require('./auditRepository')
+const ConversationRepository = require('./conversationRepository')
const getConstants = require('../constants').getConstants
function setAggregateOrgObj (query) {
@@ -34,6 +35,12 @@ function setAggregateRegistryOrgObj (query) {
return [
{
$match: query
+ },
+ {
+ $project: {
+ _id: false,
+ __t: false
+ }
}
]
}
@@ -167,7 +174,7 @@ class BaseOrgRepository extends BaseRepository {
* @returns {Promise