diff --git a/api-docs/openapi.json b/api-docs/openapi.json index 7b2dcbd2f..eaf2feb02 100644 --- a/api-docs/openapi.json +++ b/api-docs/openapi.json @@ -1,7 +1,7 @@ { "openapi": "3.0.2", "info": { - "version": "2.7.2", + "version": "2.8.0", "title": "CVE Services API", "description": "The CVE Services API supports automation tooling for the CVE Program. Credentials are required for most service endpoints. Representatives of CVE Numbering Authorities (CNAs) should use one of the methods below to obtain credentials:
CVE data is to be in the JSON 5.2 CVE Record format. Details of the JSON 5.2 schema are located here.
Contact the CVE Services team", "contact": { diff --git a/datadump/pre-population/glossary.json b/datadump/pre-population/glossary.json new file mode 100644 index 000000000..ac9ccc4e7 --- /dev/null +++ b/datadump/pre-population/glossary.json @@ -0,0 +1,7 @@ +[ + { + "services_short_name": "long_name", + "label": "Long Name", + "def": "The full, official name of an organization participating in the CVE program." + } +] diff --git a/package-lock.json b/package-lock.json index 88c96fde7..6c434b937 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cve-services", - "version": "2.7.0", + "version": "2.8.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cve-services", - "version": "2.7.0", + "version": "2.8.0", "license": "(CC0)", "dependencies": { "ajv": "^8.6.2", @@ -431,9 +431,9 @@ "license": "Python-2.0" }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "dev": true, "license": "MIT", "dependencies": { @@ -512,9 +512,9 @@ } }, "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "dev": true, "license": "MIT", "dependencies": { @@ -1510,9 +1510,9 @@ } }, "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "dev": true, "license": "MIT", "dependencies": { @@ -2883,9 +2883,9 @@ } }, "node_modules/eslint-plugin-import/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "dev": true, "license": "MIT", "dependencies": { @@ -2978,9 +2978,9 @@ } }, "node_modules/eslint-plugin-node/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "dev": true, "license": "MIT", "dependencies": { @@ -3126,9 +3126,9 @@ "license": "Python-2.0" }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "dev": true, "license": "MIT", "dependencies": { @@ -5305,9 +5305,9 @@ } }, "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "license": "MIT" }, "node_modules/lodash.flattendeep": { @@ -5846,9 +5846,9 @@ } }, "node_modules/multimatch/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "dev": true, "license": "MIT", "dependencies": { @@ -6116,9 +6116,9 @@ } }, "node_modules/nyc/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "dev": true, "license": "MIT", "dependencies": { @@ -6685,9 +6685,9 @@ "license": "MIT" }, "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", "license": "MIT" }, "node_modules/path-type": { @@ -7482,9 +7482,9 @@ } }, "node_modules/replace-in-file/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -7685,9 +7685,9 @@ } }, "node_modules/rimraf/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "dev": true, "license": "MIT", "dependencies": { @@ -8359,9 +8359,9 @@ } }, "node_modules/standard/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "dev": true, "license": "MIT", "dependencies": { @@ -9059,9 +9059,9 @@ } }, "node_modules/swagger-autogen/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -9158,9 +9158,9 @@ } }, "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "dev": true, "license": "MIT", "dependencies": { @@ -9867,9 +9867,9 @@ } }, "node_modules/yamljs/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", diff --git a/package.json b/package.json index 4e7a31a7b..2b6186b3c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cve-services", "author": "Automation Working Group", - "version": "2.7.2", + "version": "2.8.0", "license": "(CC0)", "devDependencies": { "@faker-js/faker": "^7.6.0", diff --git a/schemas/glossary/create-glossary-item-response.json b/schemas/glossary/create-glossary-item-response.json new file mode 100644 index 000000000..c4d5a2394 --- /dev/null +++ b/schemas/glossary/create-glossary-item-response.json @@ -0,0 +1,17 @@ +{ + "$id": "https://cve.mitre.org/api-docs/schema/glossary/create-glossary-item-response.json", + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "created": { + "$ref": "glossary.json" + } + }, + "required": [ + "message", + "created" + ], + "additionalProperties": false +} diff --git a/schemas/glossary/glossary.json b/schemas/glossary/glossary.json new file mode 100644 index 000000000..d5e59f8b0 --- /dev/null +++ b/schemas/glossary/glossary.json @@ -0,0 +1,24 @@ +{ + "$id": "https://cve.mitre.org/api-docs/schema/glossary/glossary.json", + "type": "object", + "properties": { + "services_short_name": { + "type": "string", + "minLength": 1 + }, + "label": { + "type": "string", + "minLength": 1 + }, + "def": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "services_short_name", + "label", + "def" + ], + "additionalProperties": false +} diff --git a/schemas/glossary/list-glossary-items-response.json b/schemas/glossary/list-glossary-items-response.json new file mode 100644 index 000000000..fee135e11 --- /dev/null +++ b/schemas/glossary/list-glossary-items-response.json @@ -0,0 +1,16 @@ +{ + "$id": "https://cve.mitre.org/api-docs/schema/glossary/list-glossary-items-response.json", + "type": "object", + "properties": { + "glossary": { + "type": "array", + "items": { + "type": "object" + } + } + }, + "required": [ + "glossary" + ], + "additionalProperties": false +} diff --git a/schemas/glossary/update-glossary-item-response.json b/schemas/glossary/update-glossary-item-response.json new file mode 100644 index 000000000..64bc27db6 --- /dev/null +++ b/schemas/glossary/update-glossary-item-response.json @@ -0,0 +1,17 @@ +{ + "$id": "https://cve.mitre.org/api-docs/schema/glossary/update-glossary-item-response.json", + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "updated": { + "$ref": "glossary.json" + } + }, + "required": [ + "message", + "updated" + ], + "additionalProperties": false +} diff --git a/src/controller/glossary.controller/glossary.controller.js b/src/controller/glossary.controller/glossary.controller.js new file mode 100644 index 000000000..b55d65850 --- /dev/null +++ b/src/controller/glossary.controller/glossary.controller.js @@ -0,0 +1,140 @@ +const errors = require('../../utils/error') +const error = new errors.IDRError() + +/** + * Retrieves all glossary items. + * + * @param {Object} req - The Express request object. + * @param {Object} res - The Express response object. + * @param {Function} next - The Express next middleware function. + * @returns {PromiseUser must belong to an organization with the Secretariat role
+Secretariat: Retrieves all glossary items
" + #swagger.parameters['$ref'] = [ + '#/components/parameters/apiEntityHeader', + '#/components/parameters/apiUserHeader', + '#/components/parameters/apiSecretHeader' + ] + #swagger.responses[200] = { + description: 'Returns a list of all glossary items', + content: { + "application/json": { + schema: { + $ref: '../schemas/glossary/list-glossary-items-response.json' + } + } + } + } + #swagger.responses[401] = { + description: 'Not Authenticated', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[403] = { + description: 'Forbidden', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[500] = { + description: 'Internal Server Error', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + */ + mw.validateUser, + mw.onlySecretariat, + controller.getAllGlossaryItems +) + +// Get glossary item by services_short_name - SEC only +router.get('/glossary/:services_short_name', + /* + #swagger.tags = ['Glossary'] + #swagger.operationId = 'glossarySingle' + #swagger.summary = "Retrieves a single glossary item by its short name (accessible to Secretariat only)" + #swagger.description = " +User must belong to an organization with the Secretariat role
+Secretariat: Retrieves the specified glossary item
" + #swagger.parameters['services_short_name'] = { description: 'The short name of the glossary item' } + #swagger.parameters['$ref'] = [ + '#/components/parameters/apiEntityHeader', + '#/components/parameters/apiUserHeader', + '#/components/parameters/apiSecretHeader' + ] + #swagger.responses[200] = { + description: 'Returns the specified glossary item', + content: { + "application/json": { + schema: { + $ref: '../schemas/glossary/glossary.json' + } + } + } + } + #swagger.responses[401] = { + description: 'Not Authenticated', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[403] = { + description: 'Forbidden', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[404] = { + description: 'Not Found', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[500] = { + description: 'Internal Server Error', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + */ + mw.validateUser, + mw.onlySecretariat, + controller.getGlossaryItem +) + +// Create a glossary item - SEC only +router.post('/glossary', + /* + #swagger.tags = ['Glossary'] + #swagger.operationId = 'glossaryCreate' + #swagger.summary = "Creates a new glossary item (accessible to Secretariat only)" + #swagger.description = " +User must belong to an organization with the Secretariat role
+Secretariat: Creates a new glossary item
" + #swagger.parameters['$ref'] = [ + '#/components/parameters/apiEntityHeader', + '#/components/parameters/apiUserHeader', + '#/components/parameters/apiSecretHeader' + ] + #swagger.requestBody = { + required: true, + content: { + 'application/json': { + schema: { + $ref: '../schemas/glossary/glossary.json' + } + } + } + } + #swagger.responses[200] = { + description: 'Returns the created glossary item wrapped in a success message', + content: { + "application/json": { + schema: { + $ref: '../schemas/glossary/create-glossary-item-response.json' + } + } + } + } + #swagger.responses[400] = { + description: 'Bad Request', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/bad-request.json' } + } + } + } + #swagger.responses[401] = { + description: 'Not Authenticated', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[403] = { + description: 'Forbidden', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[500] = { + description: 'Internal Server Error', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + */ + mw.validateUser, + mw.onlySecretariat, + controller.createGlossaryItem +) + +// Update a glossary item - SEC only +router.put('/glossary/:services_short_name', + /* + #swagger.tags = ['Glossary'] + #swagger.operationId = 'glossaryUpdate' + #swagger.summary = "Updates an existing glossary item (accessible to Secretariat only)" + #swagger.description = " +User must belong to an organization with the Secretariat role
+Secretariat: Updates the specified glossary item
" + #swagger.parameters['services_short_name'] = { description: 'The short name of the glossary item' } + #swagger.parameters['$ref'] = [ + '#/components/parameters/apiEntityHeader', + '#/components/parameters/apiUserHeader', + '#/components/parameters/apiSecretHeader' + ] + #swagger.requestBody = { + required: true, + content: { + 'application/json': { + schema: { + $ref: '../schemas/glossary/glossary.json' + } + } + } + } + #swagger.responses[200] = { + description: 'Returns the updated glossary item wrapped in a success message', + content: { + "application/json": { + schema: { + $ref: '../schemas/glossary/update-glossary-item-response.json' + } + } + } + } + #swagger.responses[400] = { + description: 'Bad Request', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/bad-request.json' } + } + } + } + #swagger.responses[401] = { + description: 'Not Authenticated', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[403] = { + description: 'Forbidden', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[404] = { + description: 'Not Found', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[500] = { + description: 'Internal Server Error', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + */ + mw.validateUser, + mw.onlySecretariat, + controller.updateGlossaryItem +) + +// Delete a glossary item - SEC only +router.delete('/glossary/:services_short_name', + /* + #swagger.tags = ['Glossary'] + #swagger.operationId = 'glossaryDelete' + #swagger.summary = "Deletes an existing glossary item (accessible to Secretariat only)" + #swagger.description = " +User must belong to an organization with the Secretariat role
+Secretariat: Deletes the specified glossary item
" + #swagger.parameters['services_short_name'] = { description: 'The short name of the glossary item' } + #swagger.parameters['$ref'] = [ + '#/components/parameters/apiEntityHeader', + '#/components/parameters/apiUserHeader', + '#/components/parameters/apiSecretHeader' + ] + #swagger.responses[200] = { + description: 'Confirms deletion of the glossary item' + } + #swagger.responses[401] = { + description: 'Not Authenticated', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[403] = { + description: 'Forbidden', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[404] = { + description: 'Not Found', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + #swagger.responses[500] = { + description: 'Internal Server Error', + content: { + "application/json": { + schema: { $ref: '../schemas/errors/generic.json' } + } + } + } + */ + mw.validateUser, + mw.onlySecretariat, + controller.deleteGlossaryItem +) + +module.exports = router diff --git a/src/controller/org.controller/index.js b/src/controller/org.controller/index.js index f7805c496..1c5e6f110 100644 --- a/src/controller/org.controller/index.js +++ b/src/controller/org.controller/index.js @@ -640,7 +640,7 @@ router.put('/registry/org/:shortname', */ mw.useRegistry(), mw.validateUser, - mw.onlySecretariat, + // mw.onlySecretariat, parseError, parsePutParams, registryOrgController.UPDATE_ORG diff --git a/src/middleware/schemas/ADPOrg.json b/src/middleware/schemas/ADPOrg.json index 7979d1f55..b5bea44cb 100644 --- a/src/middleware/schemas/ADPOrg.json +++ b/src/middleware/schemas/ADPOrg.json @@ -7,6 +7,7 @@ "allOf": [ { "$ref": "/BaseOrg" }, { + "type": "object", "properties": { "authority": { "const": ["ADP"] diff --git a/src/middleware/schemas/BaseOrg.json b/src/middleware/schemas/BaseOrg.json index f7039bcca..a87e55fe4 100644 --- a/src/middleware/schemas/BaseOrg.json +++ b/src/middleware/schemas/BaseOrg.json @@ -116,6 +116,7 @@ }, "website": { "$ref": "#/definitions/uriType", + "type": "string", "pattern": "^(ftp|http)s?://\\S+$" } }, diff --git a/src/middleware/schemas/BulkDownloadOrg.json b/src/middleware/schemas/BulkDownloadOrg.json index ada140853..768ae1123 100644 --- a/src/middleware/schemas/BulkDownloadOrg.json +++ b/src/middleware/schemas/BulkDownloadOrg.json @@ -7,6 +7,7 @@ "allOf": [ { "$ref": "/BaseOrg" }, { + "type": "object", "properties": { "authority": { "const": ["BULK_DOWNLOAD"] diff --git a/src/middleware/schemas/CNAOrg.json b/src/middleware/schemas/CNAOrg.json index c1188c8c4..705417d76 100644 --- a/src/middleware/schemas/CNAOrg.json +++ b/src/middleware/schemas/CNAOrg.json @@ -7,6 +7,7 @@ "allOf": [ { "$ref": "/BaseOrg" }, { + "type": "object", "properties": { "authority": { "const": ["CNA"] diff --git a/src/middleware/schemas/SecretariatOrg.json b/src/middleware/schemas/SecretariatOrg.json index 4e658b571..8123436eb 100644 --- a/src/middleware/schemas/SecretariatOrg.json +++ b/src/middleware/schemas/SecretariatOrg.json @@ -7,6 +7,7 @@ "allOf": [ { "$ref": "/BaseOrg" }, { + "type": "object", "properties": { "authority": { "const": ["SECRETARIAT"] diff --git a/src/model/glossary.js b/src/model/glossary.js new file mode 100644 index 000000000..d06f09a61 --- /dev/null +++ b/src/model/glossary.js @@ -0,0 +1,14 @@ +const mongoose = require('mongoose') + +const schema = { + services_short_name: { type: String, required: true }, + label: { type: String, required: true }, + def: { type: String, required: true } +} + +const GlossarySchema = new mongoose.Schema(schema, { collection: 'Glossary', timestamps: { createdAt: 'createdAt', updatedAt: 'updatedAt' } }) + +GlossarySchema.index({ services_short_name: 1 }, { unique: true }) + +const Glossary = mongoose.model('Glossary', GlossarySchema) +module.exports = Glossary diff --git a/src/repositories/glossaryRepository.js b/src/repositories/glossaryRepository.js new file mode 100644 index 000000000..3ff74abe4 --- /dev/null +++ b/src/repositories/glossaryRepository.js @@ -0,0 +1,26 @@ +const BaseRepository = require('./baseRepository') +const Glossary = require('../model/glossary') + +class GlossaryRepository extends BaseRepository { + constructor () { + super(Glossary) + } + + async getAll () { + return this.collection.find({}, { _id: 0, __v: 0, createdAt: 0, updatedAt: 0 }).exec() + } + + async findOneByServicesShortName (servicesShortName) { + return this.collection.findOne({ services_short_name: servicesShortName }, { _id: 0, __v: 0, createdAt: 0, updatedAt: 0 }).exec() + } + + async updateByServicesShortName (servicesShortName, newGlossaryData) { + return this.collection.findOneAndUpdate({ services_short_name: servicesShortName }, newGlossaryData, { projection: { _id: 0, __v: 0, createdAt: 0, updatedAt: 0 }, new: true }).exec() + } + + async deleteByServicesShortName (servicesShortName) { + return this.collection.findOneAndDelete({ services_short_name: servicesShortName }, { projection: { _id: 0, __v: 0, createdAt: 0, updatedAt: 0 } }).exec() + } +} + +module.exports = GlossaryRepository diff --git a/src/repositories/repositoryFactory.js b/src/repositories/repositoryFactory.js index 4750fffea..7f97e1177 100644 --- a/src/repositories/repositoryFactory.js +++ b/src/repositories/repositoryFactory.js @@ -7,6 +7,7 @@ const BaseOrgRepository = require('./baseOrgRepository') const BaseUserRepository = require('./baseUserRepository') const ConversationRepository = require('./conversationRepository') const ReviewObjectRepository = require('./reviewObjectRepository') +const GlossaryRepository = require('./glossaryRepository') class RepositoryFactory { getOrgRepository () { @@ -54,6 +55,11 @@ class RepositoryFactory { return repo } + getGlossaryRepository () { + const repo = new GlossaryRepository() + return repo + } + getAuditRepository () { const AuditRepository = require('./auditRepository') const repo = new AuditRepository() diff --git a/src/routes.config.js b/src/routes.config.js index 1cf2fe159..9cf95cdc3 100644 --- a/src/routes.config.js +++ b/src/routes.config.js @@ -12,6 +12,7 @@ const RegistryOrgController = require('./controller/registry-org.controller') const AuditController = require('./controller/audit.controller') const ConversationController = require('./controller/conversation.controller') const ReviewObjectController = require('./controller/review-object.controller') +const GlossaryController = require('./controller/glossary.controller') var options = { swaggerOptions: { @@ -40,6 +41,7 @@ module.exports = async function configureRoutes (app) { app.use('/api/', RegistryOrgController) app.use('/api/', ConversationController) app.use('/api/', ReviewObjectController) + app.use('/api/', GlossaryController) app.get('/api-docs/openapi.json', (req, res) => res.json(openApiSpecification)) app.use('/api-docs', swaggerUi.serveFiles(null, options), swaggerUi.setup(null, setupOptions)) app.use('/schemas/', SchemasController) diff --git a/src/scripts/migrate.js b/src/scripts/migrate.js index 7fd7fa314..c8cc10e92 100644 --- a/src/scripts/migrate.js +++ b/src/scripts/migrate.js @@ -65,6 +65,7 @@ async function run () { // Each helper handlers querying changes from srcDB and updating trgDB await orgHelper(db) await userHelper(db) + await glossaryHelper(db) } catch (err) { // Ensures that the client will close when you finish/error await dbClient.close() @@ -264,3 +265,13 @@ async function userHelper (db) { await trgUserCol.updateOne(trgQuery, updateDoc, options) } } + +async function glossaryHelper (db) { + console.log('Ensuring Glossary collection exists...') + // Create collection if it doesn't exist + await db.createCollection('Glossary').catch((err) => { + if (err.codeName !== 'NamespaceExists') { + console.warn('Could not create Glossary collection', err) + } + }) +} diff --git a/src/scripts/populate.js b/src/scripts/populate.js index 28fe3d057..b92659901 100644 --- a/src/scripts/populate.js +++ b/src/scripts/populate.js @@ -21,6 +21,7 @@ const BaseUser = require('../model/baseuser') const ReviewObject = require('../model/reviewobject') const Conversation = require('../model/conversation') const Audit = require('../model/audit') +const Glossary = require('../model/glossary') const error = new errors.IDRError() @@ -34,14 +35,16 @@ const populateTheseCollections = { BaseUser: BaseUser, ReviewObject: ReviewObject, Conversation: Conversation, - Audit: Audit + Audit: Audit, + Glossary: Glossary } const indexesToCreate = { Cve: [{ 'cve.cveMetadata.cveId': 1 }, { 'cve.cveMetadata.dateUpdated': 1 }], 'Cve-Id': [{ cve_id: 1 }, { owning_cna: 1, state: 1 }, { reserved: 1 }], User: [{ UUID: 1 }], - Org: [{ UUID: 1 }, { 'authority.active_roles': 1 }] + Org: [{ UUID: 1 }, { 'authority.active_roles': 1 }], + Glossary: [{ services_short_name: 1 }] } // Body Parser Middleware @@ -122,6 +125,12 @@ db.once('open', async () => { CveId, dataUtils.newCveIdTransform )) + // Glossary + populatePromises.push(dataUtils.populateCollection( + './datadump/pre-population/glossary.json', + Glossary + )) + // don't close database connection until all remaining populate // promises are resolved Promise.all(populatePromises).then(async function () { @@ -143,6 +152,7 @@ db.once('open', async () => { await Audit.createCollection() await ReviewObject.createCollection() await Conversation.createCollection() + await Glossary.createCollection() } catch (err) { logger.error('Error creating indexes:', err) } finally { diff --git a/test/integration-tests/glossary/glossaryCRUDTest.js b/test/integration-tests/glossary/glossaryCRUDTest.js new file mode 100644 index 000000000..844f4b4c2 --- /dev/null +++ b/test/integration-tests/glossary/glossaryCRUDTest.js @@ -0,0 +1,189 @@ +/* eslint-disable no-unused-expressions */ +const chai = require('chai') +const expect = chai.expect +chai.use(require('chai-http')) + +const constants = require('../constants.js') +const app = require('../../../src/index.js') + +const secretariatHeaders = { ...constants.headers, 'content-type': 'application/json' } + +const testGlossaryItem = { + services_short_name: 'test_glossary_item', + label: 'Test Glossary Item', + def: 'The definition of Test Glossary Item' +} + +describe('Testing /glossary endpoints', () => { + context('Testing POST /glossary endpoint', () => { + context('Positive Tests', () => { + it('Creates a new glossary item', async () => { + await chai.request(app) + .post('/api/glossary') + .set(secretariatHeaders) + .send(testGlossaryItem) + .then((res, err) => { + expect(err).to.be.undefined + expect(res).to.have.status(200) + + expect(res.body).to.haveOwnProperty('message') + expect(res.body.message).to.equal('glossary item successfully added') + + expect(res.body).to.haveOwnProperty('created') + const item = res.body.created + + expect(item).to.haveOwnProperty('services_short_name') + expect(item.services_short_name).to.equal(testGlossaryItem.services_short_name) + + expect(item).to.haveOwnProperty('label') + expect(item.label).to.equal(testGlossaryItem.label) + + expect(item).to.haveOwnProperty('def') + expect(item.def).to.equal(testGlossaryItem.def) + + expect(item).to.not.have.property('_id') + expect(item).to.not.have.property('__v') + expect(item).to.not.have.property('createdAt') + expect(item).to.not.have.property('updatedAt') + }) + }) + }) + context('Negative Tests', () => { + it('Fails to create a new glossary item with an existing short name', async () => { + await chai.request(app) + .post('/api/glossary') + .set(secretariatHeaders) + .send(testGlossaryItem) + .then((res) => { + expect(res).to.have.status(400) + expect(res.body.message).to.equal('Parameters were invalid') + expect(res.body.details[0]).to.equal('Glossary item with this services_short_name already exists') + }) + }) + }) + }) + context('Testing GET /glossary endpoints', () => { + context('Positive Tests', () => { + it('Gets a list of all glossary items', async () => { + await chai.request(app) + .get('/api/glossary') + .set(secretariatHeaders) + .then((res) => { + expect(res).to.have.status(200) + expect(res.body.glossary).to.be.an('array').that.is.not.empty + res.body.glossary.forEach(item => { + expect(item).to.not.have.property('_id') + expect(item).to.not.have.property('__v') + expect(item).to.not.have.property('createdAt') + expect(item).to.not.have.property('updatedAt') + }) + }) + }) + it('Gets a glossary item by short name', async () => { + await chai.request(app) + .get(`/api/glossary/${testGlossaryItem.services_short_name}`) + .set(secretariatHeaders) + .then((res) => { + expect(res).to.have.status(200) + expect(res.body).to.have.property('label', testGlossaryItem.label) + expect(res.body).to.have.property('services_short_name', testGlossaryItem.services_short_name) + expect(res.body).to.have.property('def', testGlossaryItem.def) + + expect(res.body).to.not.have.property('_id') + expect(res.body).to.not.have.property('__v') + expect(res.body).to.not.have.property('createdAt') + expect(res.body).to.not.have.property('updatedAt') + }) + }) + }) + context('Negative Tests', () => { + it('Fails to get a glossary item that does not exist', async () => { + await chai.request(app) + .get('/api/glossary/nonexistent_item') + .set(secretariatHeaders) + .then((res) => { + expect(res).to.have.status(404) + expect(res.body.message).to.equal('404: resource not found') + }) + }) + }) + }) + context('Testing PUT /glossary endpoint', () => { + context('Positive Tests', () => { + it('Updates a glossary item', async () => { + await chai.request(app) + .put(`/api/glossary/${testGlossaryItem.services_short_name}`) + .set(secretariatHeaders) + .send({ + ...testGlossaryItem, + label: 'Updated Glossary Item', + def: 'Updated definition' + }) + .then((res, err) => { + expect(err).to.be.undefined + expect(res).to.have.status(200) + + expect(res.body).to.haveOwnProperty('message') + expect(res.body.message).to.equal('glossary item successfully updated') + + expect(res.body).to.haveOwnProperty('updated') + const item = res.body.updated + + expect(item).to.haveOwnProperty('services_short_name') + expect(item.services_short_name).to.equal(testGlossaryItem.services_short_name) + + expect(item).to.haveOwnProperty('label') + expect(item.label).to.equal('Updated Glossary Item') + + expect(item).to.haveOwnProperty('def') + expect(item.def).to.equal('Updated definition') + + expect(item).to.not.have.property('_id') + expect(item).to.not.have.property('__v') + expect(item).to.not.have.property('createdAt') + expect(item).to.not.have.property('updatedAt') + }) + }) + }) + context('Negative Tests', () => { + it('Fails to update a glossary item changing its services_short_name', async () => { + await chai.request(app) + .put(`/api/glossary/${testGlossaryItem.services_short_name}`) + .set(secretariatHeaders) + .send({ + ...testGlossaryItem, + services_short_name: 'new_services_short_name' + }) + .then((res) => { + expect(res).to.have.status(400) + expect(res.body.message).to.equal('Parameters were invalid') + expect(res.body.details[0]).to.equal('Cannot change services_short_name through this endpoint.') + }) + }) + }) + }) + context('Testing DELETE /glossary endpoint', () => { + context('Positive Tests', () => { + it('Deletes a glossary item', async () => { + await chai.request(app) + .delete(`/api/glossary/${testGlossaryItem.services_short_name}`) + .set(secretariatHeaders) + .then((res) => { + expect(res).to.have.status(200) + expect(res.body.message).to.equal('Glossary item deleted') + }) + }) + }) + context('Negative Tests', () => { + it('Fails to delete a glossary item that does not exist', async () => { + await chai.request(app) + .delete('/api/glossary/nonexistent_item') + .set(secretariatHeaders) + .then((res) => { + expect(res).to.have.status(404) + expect(res.body.message).to.equal('404: resource not found') + }) + }) + }) + }) +})