Repository: CVEProject/cve-services
Pull request #1008 (2023-02-06 14:16:10)

Udate INT from DEV
Deploy Sprint #24 to INT


Patch:
diff --git a/api-docs/openapi.json b/api-docs/openapi.json
index 4760f48f..855f208e 100644
--- a/api-docs/openapi.json
+++ b/api-docs/openapi.json
@@ -301,6 +301,16 @@
               }
             }
           },
+          "429": {
+            "description": "Too Many Requests",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "/schemas/errors/generic.json"
+                }
+              }
+            }
+          },
           "500": {
             "description": "Internal Server Error",
             "content": {
@@ -570,6 +580,16 @@
               }
             }
           },
+          "429": {
+            "description": "Too Many Requests",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "/schemas/errors/generic.json"
+                }
+              }
+            }
+          },
           "500": {
             "description": "Internal Server Error",
             "content": {
diff --git a/schemas/cve/list-cve-records-response.json b/schemas/cve/list-cve-records-response.json
index 950fbd07..735e76c4 100644
--- a/schemas/cve/list-cve-records-response.json
+++ b/schemas/cve/list-cve-records-response.json
@@ -26,7 +26,7 @@
             "type": "integer",
             "format": "int32"
         },
-        "cve_records": {
+        "cveRecords": {
             "type": "array",
             "items": {
                 "type": "object",
diff --git a/src/constants/index.js b/src/constants/index.js
index b87dcbd6..b5ef4644 100644
--- a/src/constants/index.js
+++ b/src/constants/index.js
@@ -29,12 +29,14 @@ function getConstants () {
     AUTH_ROLE_ENUM: {
       SECRETARIAT: 'SECRETARIAT',
       CNA: 'CNA',
+      BULK_DOWNLOAD: 'BULK_DOWNLOAD',
       ROOT_CNA: 'ROOT_CNA',
       ADP: 'ADP'
     },
     ORG_ROLES: [
       'CNA',
       'SECRETARIAT',
+      'BULK_DOWNLOAD',
       'ROOT_CNA',
       'ADP'
     ],
@@ -100,4 +102,4 @@ function getConstants () {
 
 module.exports = {
   getConstants
-}
+}
diff --git a/src/controller/cve-id.controller/cve-id.controller.js b/src/controller/cve-id.controller/cve-id.controller.js
index e8c06d9f..111ba84a 100644
--- a/src/controller/cve-id.controller/cve-id.controller.js
+++ b/src/controller/cve-id.controller/cve-id.controller.js
@@ -34,6 +34,7 @@ async function getFilteredCveId (req, res, next) {
     const cveIdRepo = req.ctx.repositories.getCveIdRepository()
     const orgRepo = req.ctx.repositories.getOrgRepository()
     const isSecretariat = await orgRepo.isSecretariat(orgShortName)
+    const isBulkDownload = await orgRepo.isBulkDownload(orgShortName)
 
     Object.keys(req.ctx.query).forEach(k => {
       const key = k.toLowerCase()
@@ -61,7 +62,8 @@ async function getFilteredCveId (req, res, next) {
       state: { $ne: CONSTANTS.CVE_STATES.AVAILABLE }
     }
 
-    if (!isSecretariat) {
+    // Secretariat and BulkDownload get results for all CNAs
+    if (!(isSecretariat || isBulkDownload)) {
       query.owning_cna = await orgRepo.getOrgUUID(orgShortName)
     }
 
diff --git a/src/controller/cve.controller/cve.controller.js b/src/controller/cve.controller/cve.controller.js
index 15183683..430bc8d0 100644
--- a/src/controller/cve.controller/cve.controller.js
+++ b/src/controller/cve.controller/cve.controller.js
@@ -312,7 +312,8 @@ async function submitCna (req, res, next) {
     const additionalCveMetadataFields = {
       assignerShortName: assignerShortName,
       dateReserved: (cveId.reserved).toISOString(),
-      datePublished: dateUpdated
+      datePublished: dateUpdated,
+      dateUpdated: dateUpdated
     }
 
     const providerMetadata = createProviderMetadata(orgUuid, req.ctx.org, dateUpdated)
diff --git a/src/controller/cve.controller/error.js b/src/controller/cve.controller/error.js
index 9b492283..83ed5ed9 100644
--- a/src/controller/cve.controller/error.js
+++ b/src/controller/cve.controller/error.js
@@ -70,7 +70,7 @@ class CveControllerError extends idrErr.IDRError {
   unableToStoreCveRecord () { // cve
     const err = {}
     err.error = 'UNABLE_TO_STORE_CVE_RECORD'
-    err.message = 'A problem occurred while saving the CVE Record'
+    err.message = 'A problem occurred while saving the CVE Record, ensure that x_ values do not start with $'
     return err
   }
 
diff --git a/src/controller/cve.controller/index.js b/src/controller/cve.controller/index.js
index 81244b42..e18bf3b3 100644
--- a/src/controller/cve.controller/index.js
+++ b/src/controller/cve.controller/index.js
@@ -153,7 +153,7 @@ router.get('/cve',
   }
   */
   mw.validateUser,
-  mw.onlySecretariat,
+  mw.onlySecretariatOrBulkDownload,
   query().custom((query) => { return mw.validateQueryParameterNames(query, ['page', 'time_modified.lt', 'time_modified.gt', 'state', 'count_only', 'assigner_short_name', 'assigner']) }),
   query(['page']).optional().isInt({ min: CONSTANTS.PAGINATOR_PAGE }),
   query(['time_modified.lt']).optional().isString().trim().escape().customSanitizer(val => { return toDate(val) }).not().isEmpty().withMessage(errorMsgs.TIMESTAMP_FORMAT),
diff --git a/src/controller/org.controller/error.js b/src/controller/org.controller/error.js
index cce70e5a..795f8f70 100644
--- a/src/controller/org.controller/error.js
+++ b/src/controller/org.controller/error.js
@@ -43,10 +43,10 @@ class OrgControllerError extends idrErr.IDRError {
     return err
   }
 
-  uuidProvided () { // org
+  uuidProvided (creationType) {
     const err = {}
     err.error = 'UUID_PROVIDED'
-    err.message = 'Providing UUIDs for user creation or update is not allowed.'
+    err.message = `Providing UUIDs for ${creationType} creation or update is not allowed.`
     return err
   }
 
diff --git a/src/controller/org.controller/index.js b/src/controller/org.controller/index.js
index b69e7f1e..7951cd2a 100644
--- a/src/controller/org.controller/index.js
+++ b/src/controller/org.controller/index.js
@@ -157,8 +157,6 @@ router.post('/org',
   mw.onlySecretariat,
   body(['short_name']).isString().trim().escape().notEmpty().isLength({ min: CONSTANTS.MIN_SHORTNAME_LENGTH, max: CONSTANTS.MAX_SHORTNAME_LENGTH }),
   body(['name']).isString().trim().escape().notEmpty(),
-  // TODO remove uuid - providing it throws an error in the controller
-  body(['uuid']).optional().isString().trim().escape(),
   body(['authority.active_roles']).optional().customSanitizer(val => { return val.map(x => { return x.toUpperCase() }) }).custom(val => { return isOrgRole(val) }),
   body(['policies.id_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),
   parseError,
diff --git a/src/controller/org.controller/org.controller.js b/src/controller/org.controller/org.controller.js
index 826c8adc..20652bb2 100644
--- a/src/controller/org.controller/org.controller.js
+++ b/src/controller/org.controller/org.controller.js
@@ -241,25 +241,34 @@ async function createOrg (req, res, next) {
     const newOrg = new Org()
     const orgRepo = req.ctx.repositories.getOrgRepository()
 
-    Object.keys(req.ctx.body).forEach(k => {
+    for (const k in req.ctx.body) {
       const key = k.toLowerCase()
 
-      if (key === 'short_name') {
-        newOrg.short_name = decodeEntities(req.ctx.body.short_name)
-      } else if (key === 'name') {
-        newOrg.name = decodeEntities(req.ctx.body.name)
-      } else if (key === 'authority') {
-        if (req.ctx.body.authority.active_roles) {
-          newOrg.authority.active_roles = req.ctx.body.authority.active_roles
-        }
-      } else if (key === 'policies') {
-        if (req.ctx.body.policies.id_quota) {
-          newOrg.policies.id_quota = req.ctx.body.policies.id_quota
-        }
-      } else if (key === 'uuid') {
-        return res.status(400).json(error.uuidProvided())
+      switch (key) {
+        case 'short_name':
+          newOrg.short_name = decodeEntities(req.ctx.body.short_name)
+          break
+
+        case 'name':
+          newOrg.name = decodeEntities(req.ctx.body.name)
+          break
+
+        case 'authority':
+          if ('active_roles' in req.ctx.body.authority) {
+            newOrg.authority.active_roles = req.ctx.body.authority.active_roles
+          }
+          break
+
+        case 'policies':
+          if ('id_quota' in req.ctx.body.policies) {
+            newOrg.policies.id_quota = req.ctx.body.policies.id_quota
+          }
+          break
+
+        case 'uuid':
+          return res.status(400).json(error.uuidProvided('org'))
       }
-    })
+    }
 
     let result = await orgRepo.findOneByShortName(newOrg.short_name) // Find org in MongoDB
     if (result) {
@@ -269,9 +278,12 @@ async function createOrg (req, res, next) {
 
     newOrg.inUse = false
     newOrg.UUID = uuid.v4()
-    newOrg.authority.active_roles = [CONSTANTS.AUTH_ROLE_ENUM.CNA] // default role
 
-    if (!newOrg.policies.id_quota) {
+    if (newOrg.authority.active_roles.length === 0) { // default is to make the Org a CNA if no role is specified
+      newOrg.authority.active_roles = [CONSTANTS.AUTH_ROLE_ENUM.CNA]
+    }
+
+    if (newOrg.policies.id_quota === undefined) { // set to default quota if none is specified
       newOrg.policies.id_quota = CONSTANTS.DEFAULT_ID_QUOTA
     }
 
@@ -452,9 +464,9 @@ async function createUser (req, res, next) {
           newUser.name.suffix = decodeEntities(req.ctx.body.name.suffix)
         }
       } else if (key === 'org_uuid') {
-        return res.status(400).json(error.uuidProvided())
+        return res.status(400).json(error.uuidProvided('org'))
       } else if (key === 'uuid') {
-        return res.status(400).json(error.uuidProvided())
+        return res.status(400).json(error.uuidProvided('user'))
       }
     })
 
@@ -607,7 +619,7 @@ async function updateUser (req, res, next) {
     // Check for correct privileges if the requested changes require them
     if (changesRequirePrivilegedRole && !(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.notOrgAdminOrSecretariat())
+      return res.status(403).json(error.notOrgAdminOrSecretariatUpdate())
     }
 
     // check if the new org exist
diff --git a/src/controller/org.controller/org.middleware.js b/src/controller/org.controller/org.middleware.js
index c20e4915..f3ba4e57 100644
--- a/src/controller/org.controller/org.middleware.js
+++ b/src/controller/org.controller/org.middleware.js
@@ -59,7 +59,7 @@ function parseError (req, res, next) {
 function isValidUsername (val) {
   const value = val.match(/^[A-Za-z0-9\-_@.]{3,128}$/)
   if (value == null) {
-    throw new Error('Username should be 3-50 characters. Allowed characters are alphanumberic and -_@.')
+    throw new Error('Username should be 3-128 characters. Allowed characters are alphanumberic and -_@.')
   }
   return true
 }
diff --git a/src/middleware/error.js b/src/middleware/error.js
index a666f2ab..76a4ecbd 100644
--- a/src/middleware/error.js
+++ b/src/middleware/error.js
@@ -52,7 +52,7 @@ class MiddlewareError extends idrErr.IDRError {
   orgDoesNotOwnId (org, id) {
     const err = {
       error: 'ORG_DOES_NOT_OWN_ID',
-      message: `It appears that ${org} does not own ${id}`
+      message: `${org} does not own ${id}`
     }
     return err
   }
diff --git a/src/middleware/middleware.js b/src/middleware/middleware.js
index 436a65d2..5ddb0727 100644
--- a/src/middleware/middleware.js
+++ b/src/middleware/middleware.js
@@ -135,6 +135,31 @@ async function validateUser (req, res, next) {
   }
 }
 
+// Checks that the requester belongs to an org that has the 'BULK_DOWNLOAD' role
+async function onlySecretariatOrBulkDownload (req, res, next) {
+  const org = req.ctx.org
+  const orgRepo = req.ctx.repositories.getOrgRepository()
+  const CONSTANTS = getConstants()
+
+  try {
+    const isSec = await orgRepo.isSecretariat(org)
+    const isBulkDownload = await orgRepo.isBulkDownload(org)
+    if (!(isSec || isBulkDownload)) { // error message should only mention Secretariat
+      logger.info({ uuid: req.ctx.uuid, message: org + ' is NOT a ' + CONSTANTS.AUTH_ROLE_ENUM.SECRETARIAT })
+      return res.status(403).json(error.secretariatOnly())
+    }
+
+    logger.info({
+      uuid: req.ctx.uuid,
+      message: 'Confirmed ' + org + ' as a ' + CONSTANTS.AUTH_ROLE_ENUM.SECRETARIAT +
+                ' or as a ' + CONSTANTS.AUTH_ROLE_ENUM.BULK_DOWNLOAD
+    })
+    next()
+  } catch (err) {
+    next(err)
+  }
+}
+
 // Checks that the requester belongs to an org that has the 'SECREATARIAT' role
 async function onlySecretariat (req, res, next) {
   const org = req.ctx.org
@@ -155,7 +180,7 @@ async function onlySecretariat (req, res, next) {
   }
 }
 
-// Checks that the requester belongs to an org that has the 'SECREATARIAT' role or is a user with the 'ADMIN' role
+// Checks that the requester belongs to an org that has the 'SECRETARIAT' role or is a user with the 'ADMIN' role
 async function onlySecretariatOrAdmin (req, res, next) {
   const org = req.ctx.org
   const username = req.ctx.user
@@ -343,6 +368,7 @@ module.exports = {
   optionallyValidateUser,
   validateUser,
   onlySecretariat,
+  onlySecretariatOrBulkDownload,
   onlySecretariatOrAdmin,
   onlyCnas,
   onlyOrgWithRole,
diff --git a/src/repositories/orgRepository.js b/src/repositories/orgRepository.js
index 5257f05c..57062696 100644
--- a/src/repositories/orgRepository.js
+++ b/src/repositories/orgRepository.js
@@ -30,6 +30,10 @@ class OrgRepository extends BaseRepository {
   async isSecretariatUUID (shortName) {
     return utils.isSecretariatUUID(shortName)
   }
+
+  async isBulkDownload (shortName) {
+    return utils.isBulkDownload(shortName)
+  }
 }
 
 module.exports = OrgRepository
diff --git a/src/utils/error.js b/src/utils/error.js
index 42ddc6f1..bf38db9e 100644
--- a/src/utils/error.js
+++ b/src/utils/error.js
@@ -82,7 +82,14 @@ class IDRError {
   notOrgAdminOrSecretariat () { // super
     const err = {}
     err.error = 'NOT_ORG_ADMIN_OR_SECRETARIAT'
-    err.message = 'This information can only be viewed or modified by the Secretariat or Org Admin.'
+    err.message = 'Users can only be created by the Secretariat or Org Admin.'
+    return err
+  }
+
+  notOrgAdminOrSecretariatUpdate () {
+    const err = {}
+    err.error = 'NOT_ORG_ADMIN_OR_SECRETARIAT_UPDATE'
+    err.message = 'Contact your org Admin to update fields other than your name.'
     return err
   }
 
diff --git a/src/utils/utils.js b/src/utils/utils.js
index ef83b775..cebeeda9 100644
--- a/src/utils/utils.js
+++ b/src/utils/utils.js
@@ -54,6 +54,23 @@ async function isSecretariatUUID (orgUUID) {
   return result // org is not secretariat
 }
 
+async function isBulkDownload (shortName) {
+  let result = false
+  const CONSTANTS = getConstants()
+  const orgUUID = await getOrgUUID(shortName) // may be null if org does not exists
+  const bulkDownloadOrgs = await Org.find({ 'authority.active_roles': { $in: [CONSTANTS.AUTH_ROLE_ENUM.BULK_DOWNLOAD] } })
+
+  if (orgUUID) {
+    bulkDownloadOrgs.forEach((obj) => {
+      if (obj.UUID === orgUUID) {
+        result = true // org has the bulk download role
+      }
+    })
+  }
+
+  return result // org does not have bulk download as a role
+}
+
 async function isAdmin (requesterUsername, requesterShortName) {
   let result = false
   const CONSTANTS = getConstants()
@@ -140,6 +157,7 @@ function toDate (val) {
 
 module.exports = {
   isSecretariat,
+  isBulkDownload,
   isAdmin,
   isAdminUUID,
   isSecretariatUUID,


Test patch:
diff --git a/test-http/src/conftest.py b/test-http/src/conftest.py
index 07c243f2..93443800 100644
--- a/test-http/src/conftest.py
+++ b/test-http/src/conftest.py
@@ -41,6 +41,33 @@ def org_admin_headers():
         'CVE-API-USER': user
     }
 
+@pytest.fixture
+def bulk_download_user_headers():
+    """ create an org and user for BULK_DOWNLOAD testing, return user's headers """
+    letters = string.ascii_lowercase
+    org = ''.join(random.choice(letters)
+                  for i in range(10))  # generate random org name
+    org_res = requests.post(
+        f'{env.AWG_BASE_URL}/api/org',  # secretariat creates an org
+        headers=utils.BASE_HEADERS,
+        json={'name': org, 'short_name': org, 'authority': {'active_roles': ['BULK_DOWNLOAD']}, \
+        'policies': {'id_quota': 0}}
+    )
+    user = str(uuid.uuid4())  # dummy user name
+    user_res = requests.post(
+        # secretariat creates a user that belongs to the new org
+        f'{env.AWG_BASE_URL}/api/org/{org}/user',
+        headers=utils.BASE_HEADERS,
+        json={'username': user}
+    )
+
+    secret = json.loads(user_res.content.decode())[
+        'created']['secret']  # dummy user API key
+    return {
+        'CVE-API-KEY': secret,
+        'CVE-API-ORG': org,
+        'CVE-API-USER': user
+    }    
 
 @pytest.fixture
 def reg_user_headers():
diff --git a/test-http/src/test/cve_id_tests/cve_id_as_general_user.py b/test-http/src/test/cve_id_tests/cve_id_as_general_user.py
index 0597bb87..9e23ba06 100644
--- a/test-http/src/test/cve_id_tests/cve_id_as_general_user.py
+++ b/test-http/src/test/cve_id_tests/cve_id_as_general_user.py
@@ -36,7 +36,6 @@ def test_get_cve_id_bad_org_header(reg_user_headers):
     assert res.status_code == 200
     assert 'requested_by' not in res.content.decode()
 
-
 def test_get_cve_id_id(reg_user_headers):
     """ the id parameter must be a string """
     res = requests.get(
@@ -145,6 +144,19 @@ def test_post_cve_id_empty_year(reg_user_headers):
     assert res.status_code == 400
     response_contains_json(res, 'error', 'BAD_INPUT') 
 
+def test_get_cve_id_bulk_download_user(bulk_download_user_headers):
+    """ bulk download user should not be a CNA so can't reserve ids """
+    res = requests.post(
+        f'{env.AWG_BASE_URL}{CVE_ID_URL}',
+        headers=bulk_download_user_headers,
+        params={
+            'amount': '1',
+            'cve_year': f'{utils.CURRENT_YEAR}',
+            'short_name': bulk_download_user_headers['CVE-API-ORG']
+        }
+    )
+    assert res.status_code == 403
+    response_contains_json(res, 'error', 'CNA_ONLY')    
 
 def test_post_cve_id_bad_year(reg_user_headers):
     """ cve services rejects year that isn't a 4 digit number"""
diff --git a/test-http/src/test/cve_tests/cve.py b/test-http/src/test/cve_tests/cve.py
index 344bdbe8..c91ee4b1 100644
--- a/test-http/src/test/cve_tests/cve.py
+++ b/test-http/src/test/cve_tests/cve.py
@@ -1,6 +1,7 @@
 import copy
 import datetime as dt
 import json
+import pytest
 import requests
 import time
 import uuid
@@ -24,8 +25,8 @@ ORG_URL = '/api/org'
 ### GET /cve ####
 
 
-def test_get_all_cves():
-    """ services api accepts requests for all CVEs for secretariat users """
+def test_get_all_cves_secretariat():
+    """ services api accepts requests for all CVEs for secretariat and bulk_download users """
     res = requests.get(
         f'{env.AWG_BASE_URL}{CVE_URL}/',
         headers=utils.BASE_HEADERS
@@ -33,6 +34,14 @@ def test_get_all_cves():
     assert res.status_code == utils.HTTP_OK
     assert len(json.loads(res.content.decode())['cveRecords']) >= 1
 
+def test_get_all_cves_bulk_download(bulk_download_user_headers):
+    """ services api accepts requests for all CVEs for secretariat and bulk_download users """
+    res = requests.get(
+        f'{env.AWG_BASE_URL}{CVE_URL}/',
+        headers=bulk_download_user_headers
+    )
+    assert res.status_code == utils.HTTP_OK
+    assert len(json.loads(res.content.decode())['cveRecords']) >= 1
 
 def test_get_cve_by_time_modified():
     # @todo - These records are created by the populate scripts on the node side
diff --git a/test-http/src/test/org_user_tests/org.py b/test-http/src/test/org_user_tests/org.py
index ad6d4585..5547297a 100644
--- a/test-http/src/test/org_user_tests/org.py
+++ b/test-http/src/test/org_user_tests/org.py
@@ -196,6 +196,36 @@ def test_post_new_org_duplicate_parameter():
     response_contains_json(res, 'message', 'Parameters were invalid')
 
 
+def test_post_new_org_uuid_parameter():
+    """ should reject creating new orgs with uuid param """
+    uid = str(uuid.uuid4())[:MAX_SHORTNAME_LENGTH]
+    res = requests.post(
+        f'{env.AWG_BASE_URL}{ORG_URL}',
+        headers=utils.BASE_HEADERS,
+        json={
+            'short_name': uid,
+            'name': uid,
+            'uuid': str(uuid.uuid4())
+        }
+    )
+
+    json_content = json.loads(res.content.decode())
+    assert res.status_code == 400
+    assert 'error' in json_content
+    assert json_content['error'] == 'UUID_PROVIDED'
+    assert 'message' in json_content
+    response_contains_json(res, 'message', 'Providing UUIDs for org creation or update is not allowed.')
+
+    # check that it didn't actually create the org:
+    # https://github.com/CVEProject/cve-services/issues/887
+    res = requests.get(
+        f'{env.AWG_BASE_URL}{ORG_URL}/{uid}',
+        headers=utils.BASE_HEADERS
+    )
+
+    assert res.status_code == 404
+
+
 #### POST /org/:shortname/user ####
 def test_post_new_org_user():
     """ secretariat user can create a new user without active roles """
diff --git a/test-http/src/test/org_user_tests/org_as_org_admin.py b/test-http/src/test/org_user_tests/org_as_org_admin.py
index aef9230a..4a0b8473 100644
--- a/test-http/src/test/org_user_tests/org_as_org_admin.py
+++ b/test-http/src/test/org_user_tests/org_as_org_admin.py
@@ -7,13 +7,15 @@ import requests
 import uuid
 from src import env, utils
 from src.test.org_user_tests.org import (ORG_URL, create_new_user_with_new_org_by_uuid,
-                            create_new_user_with_new_org_by_shortname,
-                            post_new_org_user, post_new_org)
+                                         create_new_user_with_new_org_by_shortname,
+                                         post_new_org_user, post_new_org)
 from src.utils import response_contains, response_contains_json
 
 MAX_SHORTNAME_LENGTH = 32
 
 ### GET /org ####
+
+
 def test_org_admin_get_all_orgs(org_admin_headers):
     """ services api rejects requests for all orgs by non-secretariat users """
     res = requests.get(
@@ -28,7 +30,7 @@ def test_org_admin_get_all_orgs(org_admin_headers):
 def test_org_admin_get_mitre_org(org_admin_headers):
     """ services api rejects requests for secretariat by non-secretariat users """
     res = requests.get(
-        f'{env.AWG_BASE_URL}{ORG_URL}/mitre', # the secretariat's org
+        f'{env.AWG_BASE_URL}{ORG_URL}/mitre',  # the secretariat's org
         headers=org_admin_headers
     )
     assert res.status_code == 403
@@ -37,8 +39,8 @@ def test_org_admin_get_mitre_org(org_admin_headers):
 
 def test_org_admin_get_another_org(org_admin_headers):
     """ services api rejects requests for any org by another org user """
-    different_org = str(uuid.uuid4())[:MAX_SHORTNAME_LENGTH] # name of an org
-    res = post_new_org(different_org, different_org) # create an org
+    different_org = str(uuid.uuid4())[:MAX_SHORTNAME_LENGTH]  # name of an org
+    res = post_new_org(different_org, different_org)  # create an org
     assert res.status_code == 200
     res = requests.get(
         f'{env.AWG_BASE_URL}{ORG_URL}/{different_org}',
@@ -63,7 +65,7 @@ def test_org_admin_get_own_org(org_admin_headers):
 def test_org_admin_get_secretariat_id_quota_info(org_admin_headers):
     """ services api rejects requests for secretariat by non-secretariat users """
     res = requests.get(
-        f'{env.AWG_BASE_URL}{ORG_URL}/mitre/id_quota', # the secretariat's org
+        f'{env.AWG_BASE_URL}{ORG_URL}/mitre/id_quota',  # the secretariat's org
         headers=org_admin_headers
     )
     assert res.status_code == 403
@@ -72,8 +74,8 @@ def test_org_admin_get_secretariat_id_quota_info(org_admin_headers):
 
 def test_org_admin_get_another_org_id_quota_info(org_admin_headers):
     """ services api rejects requests for any org by another org user """
-    different_org = str(uuid.uuid4())[:MAX_SHORTNAME_LENGTH] # name of an org
-    res = post_new_org(different_org, different_org) # create an org
+    different_org = str(uuid.uuid4())[:MAX_SHORTNAME_LENGTH]  # name of an org
+    res = post_new_org(different_org, different_org)  # create an org
     assert res.status_code == 200
     res = requests.get(
         f'{env.AWG_BASE_URL}{ORG_URL}/{different_org}/id_quota',
@@ -209,6 +211,8 @@ def test_org_get_bad_user_page(org_admin_headers):
     response_contains_json(res, 'details', utils.BAD_PAGE_ERROR_DETAILS)
 
 #### POST /org ####
+
+
 def test_org_admin_cannot_create_another_org(org_admin_headers):
     """ services api does not allow org admins to create other orgs """
     res = requests.post(
@@ -240,7 +244,7 @@ def test_org_admin_cannot_create_user_for_another_org(org_admin_headers):
     res = requests.post(
         f'{env.AWG_BASE_URL}{ORG_URL}/{org}/user',
         headers=org_admin_headers,
-        json={'username':'BLARG', 'org_UUID': 'test'}
+        json={'username': 'BLARG', 'org_UUID': 'test'}
     )
     assert res.status_code == 400
     response_contains_json(res, 'error', 'SHORTNAME_MISMATCH')
@@ -254,7 +258,7 @@ def test_org_admin_cannot_create_user_for_another_org(org_admin_headers):
     res = requests.post(
         f'{env.AWG_BASE_URL}{ORG_URL}/{org}/user',
         headers=org_admin_headers,
-        json={'username':'BLARG'}
+        json={'username': 'BLARG'}
     )
     assert res.status_code == 403
     response_contains_json(res, 'error', 'NOT_ORG_ADMIN_OR_SECRETARIAT')
@@ -269,7 +273,7 @@ def test_org_admin_cannot_create_existen_user(org_admin_headers):
     res = requests.post(
         f'{env.AWG_BASE_URL}{ORG_URL}/{org}/user',
         headers=org_admin_headers,
-        json={'username':user}
+        json={'username': user}
     )
     assert res.status_code == 400
     response_contains_json(res, 'error', 'USER_EXISTS')
@@ -342,7 +346,8 @@ def test_org_admin_cannot_update_duplicate_user_with_new_username(org_admin_head
     org = org_admin_headers["CVE-API-ORG"][:MAX_SHORTNAME_LENGTH]
     user1 = org_admin_headers['CVE-API-USER']
     user2 = str(uuid.uuid4())
-    res = post_new_org_user(org, user2) # creating a user with same org as admin org user
+    # creating a user with same org as admin org user
+    res = post_new_org_user(org, user2)
     assert res.status_code == 200
     res = requests.put(
         f'{env.AWG_BASE_URL}{ORG_URL}/{org}/user/{user1}?new_username={user2}',
@@ -370,8 +375,9 @@ def test_org_admin_update_same_org_user_state_sn_un(org_admin_headers):
     """  allows admin users to update a user's active state and user username """
     org = org_admin_headers["CVE-API-ORG"][:MAX_SHORTNAME_LENGTH]
     user = str(uuid.uuid4())
-    res = post_new_org_user(org, user) # creating a user with same org as admin org user
-    assert res.status_code == 200 
+    # creating a user with same org as admin org user
+    res = post_new_org_user(org, user)
+    assert res.status_code == 200
     new_shortname = str(uuid.uuid4())[:MAX_SHORTNAME_LENGTH]   # used in query
     new_username = str(uuid.uuid4())    # used in query
     res = post_new_org(new_shortname, new_shortname)  # create new org
@@ -382,7 +388,8 @@ def test_org_admin_update_same_org_user_state_sn_un(org_admin_headers):
     )
     assert res.status_code == 200
     assert json.loads(res.content.decode())['updated']['active'] == False
-    assert json.loads(res.content.decode())['updated']['username'] == new_username
+    assert json.loads(res.content.decode())[
+        'updated']['username'] == new_username
     assert json.loads(res.content.decode())['updated']['username'] is not None
 
 
@@ -390,22 +397,28 @@ def test_org_admin_update_same_org_user_roles_name(org_admin_headers):
     """  allows admin users to update a user's name, add role, and remove role """
     org = org_admin_headers["CVE-API-ORG"][:MAX_SHORTNAME_LENGTH]
     user = str(uuid.uuid4())
-    res = post_new_org_user(org, user) # creating a user with same org as admin org user
+    # creating a user with same org as admin org user
+    res = post_new_org_user(org, user)
     assert res.status_code == 200
     res = requests.put(
-        f'{env.AWG_BASE_URL}{ORG_URL}/{org}/user/{user}?active_roles.add=admin', # adding role
+        # adding role
+        f'{env.AWG_BASE_URL}{ORG_URL}/{org}/user/{user}?active_roles.add=admin',
         headers=org_admin_headers
     )
     assert res.status_code == 200
-    assert json.loads(res.content.decode())['updated']['authority']['active_roles'] == ["ADMIN"]
+    assert json.loads(res.content.decode())[
+        'updated']['authority']['active_roles'] == ["ADMIN"]
     res = requests.put(
-        f'{env.AWG_BASE_URL}{ORG_URL}/{org}/user/{user}?active_roles.remove=admin', # removing role
+        # removing role
+        f'{env.AWG_BASE_URL}{ORG_URL}/{org}/user/{user}?active_roles.remove=admin',
         headers=org_admin_headers
     )
     assert res.status_code == 200
-    assert json.loads(res.content.decode())['updated']['authority']['active_roles'] == []
+    assert json.loads(res.content.decode())[
+        'updated']['authority']['active_roles'] == []
     res = requests.put(
-        f'{env.AWG_BASE_URL}{ORG_URL}/{org}/user/{user}?name.first=t&name.last=e&name.middle=s&name.suffix=t', # updating name
+        # updating name
+        f'{env.AWG_BASE_URL}{ORG_URL}/{org}/user/{user}?name.first=t&name.last=e&name.middle=s&name.suffix=t',
         headers=org_admin_headers
     )
     assert res.status_code == 200
@@ -434,29 +447,36 @@ def test_org_admin_update_same_org_user_roles_name(org_admin_headers):
 
 
 def test_org_admin_update_own_user_roles_name(org_admin_headers):
-    """  allows admin users to update its own name and remove role """ 
+    """  allows admin users to update its own name and remove role """
     org = org_admin_headers["CVE-API-ORG"][:MAX_SHORTNAME_LENGTH]
     user = org_admin_headers['CVE-API-USER']
     res = requests.put(
-        f'{env.AWG_BASE_URL}{ORG_URL}/{org}/user/{user}?active_roles.remove=admin', # removing role
+        # removing role
+        f'{env.AWG_BASE_URL}{ORG_URL}/{org}/user/{user}?active_roles.remove=admin',
         headers=org_admin_headers
     )
     assert res.status_code == 200
-    assert json.loads(res.content.decode())['updated']['authority']['active_roles'] == []
+    assert json.loads(res.content.decode())[
+        'updated']['authority']['active_roles'] == []
     res = requests.put(
-        f'{env.AWG_BASE_URL}{ORG_URL}/{org}/user/{user}?active_roles.add=admin', # adding role
+        # adding role
+        f'{env.AWG_BASE_URL}{ORG_URL}/{org}/user/{user}?active_roles.add=admin',
         headers=org_admin_headers
     )
     assert res.status_code == 403
-    response_contains_json(res, 'error', 'NOT_ORG_ADMIN_OR_SECRETARIAT') # cannot add role because org admin doesn't have "ADMIN" role anymore
+    # cannot add role because org admin doesn't have "ADMIN" role anymore
+    response_contains_json(res, 'error', 'NOT_ORG_ADMIN_OR_SECRETARIAT_UPDATE')
     res = requests.put(
-        f'{env.AWG_BASE_URL}{ORG_URL}/{org}/user/{user}?active_roles.add=admin', # adding "ADMIN" role back to org admin user
+        # adding "ADMIN" role back to org admin user
+        f'{env.AWG_BASE_URL}{ORG_URL}/{org}/user/{user}?active_roles.add=admin',
         headers=utils.BASE_HEADERS
     )
     assert res.status_code == 200
-    assert json.loads(res.content.decode())['updated']['authority']['active_roles'] == ["ADMIN"]
+    assert json.loads(res.content.decode())[
+        'updated']['authority']['active_roles'] == ["ADMIN"]
     res = requests.put(
-        f'{env.AWG_BASE_URL}{ORG_URL}/{org}/user/{user}?name.first=t&name.last=e&name.middle=s&name.suffix=t', # updating name
+        # updating name
+        f'{env.AWG_BASE_URL}{ORG_URL}/{org}/user/{user}?name.first=t&name.last=e&name.middle=s&name.suffix=t',
         headers=org_admin_headers
     )
     assert res.status_code == 200
@@ -545,4 +565,6 @@ def test_admin_role_preserved_after_resetting_own_secret(org_admin_headers):
         headers=headers2
     )
     assert res2.status_code == 200
-    assert json.loads(res2.content.decode())["authority"]["active_roles"][0] == "ADMIN" # admin role still remains after changing secret
+    # admin role still remains after changing secret
+    assert json.loads(res2.content.decode())[
+        "authority"]["active_roles"][0] == "ADMIN"
diff --git a/test-http/src/test/org_user_tests/regular_users.py b/test-http/src/test/org_user_tests/regular_users.py
index 3677716e..3d015c1e 100644
--- a/test-http/src/test/org_user_tests/regular_users.py
+++ b/test-http/src/test/org_user_tests/regular_users.py
@@ -39,7 +39,7 @@ def test_regular_user_update_name_and_username(reg_user_headers):
         headers=reg_user_headers
     )
     assert res.status_code == 403
-    response_contains_json(res, 'error', 'NOT_ORG_ADMIN_OR_SECRETARIAT')
+    response_contains_json(res, 'error', 'NOT_ORG_ADMIN_OR_SECRETARIAT_UPDATE')
 
 
 def test_regular_user_cannot_update_for_another_user(reg_user_headers):
@@ -72,7 +72,7 @@ def test_regular_user_cannot_update_duplicate_user_with_new_username(reg_user_he
         headers=reg_user_headers
     )
     assert res.status_code == 403
-    response_contains_json(res, 'error', 'NOT_ORG_ADMIN_OR_SECRETARIAT')
+    response_contains_json(res, 'error', 'NOT_ORG_ADMIN_OR_SECRETARIAT_UPDATE')
 
 
 def test_regular_user_cannot_update_organization_with_new_shortname(reg_user_headers):
@@ -97,7 +97,7 @@ def test_regular_user_cannot_update_active_state(reg_user_headers):
         headers=reg_user_headers
     )
     assert res.status_code == 403
-    response_contains_json(res, 'error', 'NOT_ORG_ADMIN_OR_SECRETARIAT')
+    response_contains_json(res, 'error', 'NOT_ORG_ADMIN_OR_SECRETARIAT_UPDATE')
 
 
 def test_regular_user_cannot_add_role(reg_user_headers):
@@ -110,7 +110,7 @@ def test_regular_user_cannot_add_role(reg_user_headers):
         headers=reg_user_headers
     )
     assert res.status_code == 403
-    response_contains_json(res, 'error', 'NOT_ORG_ADMIN_OR_SECRETARIAT')
+    response_contains_json(res, 'error', 'NOT_ORG_ADMIN_OR_SECRETARIAT_UPDATE')
 
 
 def test_regular_user_cannot_remove_role(reg_user_headers):
@@ -123,7 +123,7 @@ def test_regular_user_cannot_remove_role(reg_user_headers):
         headers=reg_user_headers
     )
     assert res.status_code == 403
-    response_contains_json(res, 'error', 'NOT_ORG_ADMIN_OR_SECRETARIAT')
+    response_contains_json(res, 'error', 'NOT_ORG_ADMIN_OR_SECRETARIAT_UPDATE')
 
 
 def test_regular_user_cannot_update_user_org_dne(reg_user_headers):
diff --git a/test-http/src/test/user_tests/user.py b/test-http/src/test/user_tests/user.py
index 4a1ee99f..bdd51f01 100644
--- a/test-http/src/test/user_tests/user.py
+++ b/test-http/src/test/user_tests/user.py
@@ -132,6 +132,18 @@ def test_put_user_update_name():
 
     assert json.loads(res.content.decode())['name']['first'] == test_name
 
+    # put the name back to what it was because tests don't reset the data
+
+    res = requests.put(
+        f'{env.AWG_BASE_URL}/api/org/{org["short_name"]}/user/{test_user["username"]}',
+        headers=utils.BASE_HEADERS,
+        params={
+            'name.first': test_user['name']['first']
+        }
+    )
+
+    assert res.status_code == 200
+
 
 
 def test_put_user_error_for_same_org():
@@ -172,4 +184,4 @@ def test_put_user_error_for_same_org():
     err = json.loads(res.content.decode())
 
     assert err['error'] == 'USER_ALREADY_IN_ORG'
-    assert err['message'] == 'The user could not be updated because the user \'cps@mitre.org\' already belongs to the \'mitre\' organization.'
+    assert err['message'] == f'The user could not be updated because the user \'{test_user["username"]}\' already belongs to the \'{org["short_name"]}\' organization.'
diff --git a/test/unit-tests/cve-id/cveIdGetAllTest.js b/test/unit-tests/cve-id/cveIdGetAllTest.js
index a8ebd25a..43e06a80 100644
--- a/test/unit-tests/cve-id/cveIdGetAllTest.js
+++ b/test/unit-tests/cve-id/cveIdGetAllTest.js
@@ -21,6 +21,10 @@ class OrgGetCveIdNoCveIdsWithParams {
     return false
   }
 
+  async isBulkDownload () {
+    return false
+  }
+
   async getOrgUUID () {
     return cveIdFixtures.owningOrg.UUID
   }
@@ -302,6 +306,10 @@ describe('Testing the GET /cve-id endpoint in CveId Controller', () => {
           return true
         }
 
+        async isBulkDownload () {
+          return false
+        }
+
         async getOrgUUID () {
           return cveIdFixtures.org.UUID
         }
@@ -400,6 +408,10 @@ describe('Testing the GET /cve-id endpoint in CveId Controller', () => {
         async isSecretariat () {
           return false
         }
+
+        async isBulkDownload () {
+          return false
+        }
       }
 
       app.route('/cve-id-filtered-requested-field-not-changed')
@@ -491,6 +503,10 @@ describe('Testing the GET /cve-id endpoint in CveId Controller', () => {
         async isSecretariat () {
           return true
         }
+
+        async isBulkDownload () {
+          return false
+        }
       }
 
       app.route('/cve-id-filtered-secretariat-no-query')
@@ -544,6 +560,10 @@ describe('Testing the GET /cve-id endpoint in CveId Controller', () => {
         async isSecretariat () {
           return true
         }
+
+        async isBulkDownload () {
+          return false
+        }
       }
 
       class MyCveIdRepo {
@@ -619,6 +639,10 @@ describe('Testing the GET /cve-id endpoint in CveId Controller', () => {
         async isSecretariat () {
           return true
         }
+
+        async isBulkDownload () {
+          return false
+        }
       }
 
       class MyCveIdRepo {
@@ -707,6 +731,10 @@ describe('Testing the GET /cve-id endpoint in CveId Controller', () => {
         async isSecretariat () {
           return true
         }
+
+        async isBulkDownload () {
+          return false
+        }
       }
 
       class MyCveIdRepo {
@@ -795,6 +823,10 @@ describe('Testing the GET /cve-id endpoint in CveId Controller', () => {
         async isSecretariat () {
           return true
         }
+
+        async isBulkDownload () {
+          return false
+        }
       }
 
       class MyCveIdRepo {
diff --git a/test/unit-tests/org/orgCreateTest.js b/test/unit-tests/org/orgCreateTest.js
index d884745d..c4e44726 100644
--- a/test/unit-tests/org/orgCreateTest.js
+++ b/test/unit-tests/org/orgCreateTest.js
@@ -127,11 +127,17 @@ describe('Testing the POST /org endpoint in Org Controller', () => {
 
           expect(res).to.have.status(400)
           expect(res).to.have.property('body').and.to.be.a('object')
-          const errObj = error.uuidProvided()
+          const errObj = error.uuidProvided('org')
           expect(res.body.error).to.equal(errObj.error)
           expect(res.body.message).to.equal(errObj.message)
           done()
         })
+
+      // check that it really didn't create the org
+      // https://github.com/CVEProject/cve-services/issues/887
+
+      chai.request(app)
+        .get('/')
     })
 
     it('Org is not created because it already exists', (done) => {
diff --git a/test/unit-tests/user/userCreateTest.js b/test/unit-tests/user/userCreateTest.js
index 57658695..ec493ae0 100644
--- a/test/unit-tests/user/userCreateTest.js
+++ b/test/unit-tests/user/userCreateTest.js
@@ -172,7 +172,7 @@ describe('Testing the POST /org/:shortname/user endpoint in Org Controller', ()
 
           expect(res).to.have.status(400)
           expect(res).to.have.property('body').and.to.be.a('object')
-          const errObj = error.uuidProvided()
+          const errObj = error.uuidProvided('user')
           expect(res.body.error).to.equal(errObj.error)
           expect(res.body.message).to.equal(errObj.message)
           done()
@@ -201,7 +201,7 @@ describe('Testing the POST /org/:shortname/user endpoint in Org Controller', ()
 
           expect(res).to.have.status(400)
           expect(res).to.have.property('body').and.to.be.a('object')
-          const errObj = error.uuidProvided()
+          const errObj = error.uuidProvided('user')
           expect(res.body.error).to.equal(errObj.error)
           expect(res.body.message).to.equal(errObj.message)
           done()
diff --git a/test/unit-tests/user/userUpdateTest.js b/test/unit-tests/user/userUpdateTest.js
index 80b5d400..6002a4cc 100644
--- a/test/unit-tests/user/userUpdateTest.js
+++ b/test/unit-tests/user/userUpdateTest.js
@@ -422,7 +422,7 @@ describe('Testing the PUT /org/:shortname/user/:username endpoint in Org Control
 
           expect(res).to.have.status(403)
           expect(res).to.have.property('body').and.to.be.a('object')
-          const errObj = error.notOrgAdminOrSecretariat()
+          const errObj = error.notOrgAdminOrSecretariatUpdate()
           expect(res.body.error).to.equal(errObj.error)
           expect(res.body.message).to.equal(errObj.message)
           done()