Repository: CVEProject/cve-services
Pull request #388 (2021-04-27 19:19:43)

Issue 375
Resolves issue #375 

- Includes blackbox tests for cve controller (RSUS-1A) and User Registry for org admin user only
- Blackbox tests do not test pagination functionality


Patch:
diff --git a/src/controller/org.controller/error.js b/src/controller/org.controller/error.js
index b5ed943c..196391a4 100644
--- a/src/controller/org.controller/error.js
+++ b/src/controller/org.controller/error.js
@@ -22,13 +22,6 @@ class OrgControllerError extends idrErr.IDRError {
     return err
   }
 
-  notOrgAdminOrSecretariat () { // org
-    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.'
-    return err
-  }
-
   orgExists (shortname) { // org
     const err = {}
     err.error = 'ORG_EXISTS'
@@ -46,7 +39,7 @@ class OrgControllerError extends idrErr.IDRError {
   shortnameMismatch (shortname, shortnameBody) { // org
     const err = {}
     err.error = 'SHORTNAME_MISMATCH'
-    err.message = `The '${shortname}' organization designated by the username parameter does not match the username '${shortnameBody}' in the JSON body.`
+    err.message = `The '${shortname}' organization designated by the shortname parameter does not match the shortname '${shortnameBody}' in the JSON body.`
     return err
   }
 
diff --git a/src/controller/org.controller/org.controller.js b/src/controller/org.controller/org.controller.js
index 9123d125..1a04aaf8 100644
--- a/src/controller/org.controller/org.controller.js
+++ b/src/controller/org.controller/org.controller.js
@@ -83,7 +83,7 @@ async function getOrg (req, res, next) {
         }
       ])
 
-      if (utils.isEmptyObject(result)) {
+      if (utils.isEmptyObject(result)) { // an empty result can only happen if the requestor is the Secretariat
         logger.info({ uuid: req.ctx.uuid, message: shortName + ' organization does not exist.' })
         return res.status(404).json(error.orgDneParam(shortName))
       }
@@ -108,7 +108,7 @@ async function getUsers (req, res, next) {
     const isSecretariat = await orgRepo.isSecretariat(shortName)
     let returned = false
 
-    if (!orgUUID) {
+    if (!orgUUID) { // the org can only be non-existent if the requestor is the Secretariat
       returned = true
       logger.info({ uuid: req.ctx.uuid, message: orgShortName + ' organization does not exist.' })
       return res.status(404).json(error.orgDneParam(orgShortName))
@@ -176,7 +176,7 @@ async function getUser (req, res, next) {
     }
 
     const orgUUID = await orgRepo.getOrgUUID(orgShortName)
-    if (!orgUUID && !returned) {
+    if (!orgUUID && !returned) { // the org can only be non-existent if the requestor is the Secretariat
       returned = true
       logger.info({ uuid: req.ctx.uuid, message: orgShortName + ' organization does not exist.' })
       return res.status(404).json(error.orgDneParam(orgShortName))
@@ -202,7 +202,7 @@ async function getUser (req, res, next) {
         }
       ])
 
-      if (utils.isEmptyObject(result)) {
+      if (utils.isEmptyObject(result)) { // an empty result can only happen if the requestor is the Secretariat
         logger.info({ uuid: req.ctx.uuid, message: username + ' does not exist.' })
         return res.status(404).json(error.userDne(username))
       }
@@ -222,13 +222,14 @@ async function getOrgIdQuota (req, res, next) {
     const shortName = req.ctx.params.shortname
     const repo = req.ctx.repositories.getOrgRepository()
     const isSecretariat = await repo.isSecretariat(orgShortName)
+
     if (orgShortName !== shortName && !isSecretariat) {
       logger.info({ uuid: req.ctx.uuid, message: shortName + ' organization id quota can only be viewed by the users of the same organization or the Secretariat.' })
       return res.status(403).json(error.notSameOrgOrSecretariat())
     }
 
     let result = await repo.findOneByShortName(shortName)
-    if (!result) {
+    if (!result) { // a null result can only happen if the requestor is the Secretariat
       logger.info({ uuid: req.ctx.uuid, message: shortName + ' organization does not exist.' })
       return res.status(404).json(error.orgDneParam(shortName))
     }
@@ -438,7 +439,7 @@ async function createUser (req, res, next) {
     const userRepo = req.ctx.repositories.getUserRepository()
 
     const orgUUID = await orgRepo.getOrgUUID(orgShortName)
-    if (!orgUUID) {
+    if (!orgUUID) { // the org can only be non-existent if the requestor is the Secretariat
       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.orgDneParam(orgShortName))
     }
@@ -467,7 +468,7 @@ async function createUser (req, res, next) {
     // check if user is only an Admin (not Secretatiat) and the user does not belong to the same organization as the new user
     if (!isSecretariat && isAdmin) {
       if (requesterOrgUUID !== newUser.org_UUID) {
-        return res.status(400).json(error.notOrgAdminOrSecretariat()) // The Admin user must belong to the new user's organization
+        return res.status(403).json(error.notOrgAdminOrSecretariat()) // The Admin user must belong to the new user's organization
       }
     }
 
@@ -599,7 +600,6 @@ async function updateUser (req, res, next) {
       user.name.surname = name.surname
       nameChanged = true
     }
-
     if ('active_roles.add' in req.ctx.query) {
       if (Array.isArray(req.ctx.query['active_roles.add'])) {
         req.ctx.query['active_roles.add'].forEach(r => {
@@ -654,21 +654,21 @@ async function updateUser (req, res, next) {
       const duplicateUsers = await userRepo.find({ org_UUID: newUser.org_UUID, username: newUser.username })
       if (duplicateUsers.length) {
         returned = true
-        logger.info({ uuid: req.ctx.uuid, message: 'The user could not be updated because ' + newOrgShortName + ' organization contains a user with the same name.' })
+        logger.info({ uuid: req.ctx.uuid, message: 'The user could not be updated because ' + newOrgShortName + ' organization contains a user with the same username.' })
         return res.status(403).json(error.duplicateUsername(newOrgShortName, newUsername))
       }
     } else if (newUsername && !returned) {
       const duplicateUsers = await userRepo.find({ org_UUID: orgUUID, username: newUser.username })
       if (duplicateUsers.length) {
         returned = true
-        logger.info({ uuid: req.ctx.uuid, message: 'The user could not be updated because ' + shortName + ' organization contains a user with the same name.' })
+        logger.info({ uuid: req.ctx.uuid, message: 'The user could not be updated because ' + shortName + ' organization contains a user with the same username.' })
         return res.status(403).json(error.duplicateUsername(shortName, newUsername))
       }
     } else if (newOrgShortName && !returned) {
       const duplicateUsers = await userRepo.find({ org_UUID: newUser.org_UUID, username: username })
       if (duplicateUsers.length) {
         returned = true
-        logger.info({ uuid: req.ctx.uuid, message: 'The user could not be updated because ' + newOrgShortName + ' organization contains a user with the same name.' })
+        logger.info({ uuid: req.ctx.uuid, message: 'The user could not be updated because ' + newOrgShortName + ' organization contains a user with the same username.' })
         return res.status(403).json(error.duplicateUsername(newOrgShortName, username))
       }
     }


Test patch:
diff --git a/test-http/src/conftest.py b/test-http/src/conftest.py
index e6fe5233..1fd10e69 100644
--- a/test-http/src/conftest.py
+++ b/test-http/src/conftest.py
@@ -8,23 +8,23 @@ from src import env, utils
 @pytest.fixture
 def org_admin_headers():
     """ create an org and an admin user for that org, return user's headers """
-    org = str(uuid.uuid4())
+    org = str(uuid.uuid4()) # dummy org name
     org_res = requests.post(
-        f'{env.AWG_BASE_URL}/api/org',
+        f'{env.AWG_BASE_URL}/api/org', # secretariat creates an org
         headers=utils.BASE_HEADERS,
         json={'name': org, 'short_name': org}
     )
-    user = str(uuid.uuid4())
+    user = str(uuid.uuid4()) # dummy user name
     user_res = requests.post(
-        f'{env.AWG_BASE_URL}/api/org/{org}/user',
+        f'{env.AWG_BASE_URL}/api/org/{org}/user', # secretariat creates a user that belongs to the new org
         headers=utils.BASE_HEADERS,
         json={'username': user}
     )
-    secret = json.loads(user_res.content.decode())['created']['secret']
-    admin_res = requests.post(
+    secret = json.loads(user_res.content.decode())['created']['secret'] # dummy user API key
+    admin_res = requests.put(
         f'{env.AWG_BASE_URL}/api/org/{org}/user/{user}',
         headers=utils.BASE_HEADERS,
-        params={'active_roles.add':'ADMIN'}
+        params={'active_roles.add':'ADMIN'} # secretariat adds the ADMIN role to the new user
     )
     return {
         'CVE-API-KEY': secret,
@@ -33,6 +33,29 @@ def org_admin_headers():
     }
 
 
+@pytest.fixture
+def reg_user_headers():
+    """ create an org and an regular user for that org, return user's headers """
+    org = str(uuid.uuid4()) # dummy 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}
+    )
+    user = str(uuid.uuid4()) # dummy user name
+    user_res = requests.post(
+        f'{env.AWG_BASE_URL}/api/org/{org}/user', # secretariat creates a user that belongs to the new org
+        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
+    }
+
+
 # TEST RE-ORDERING
 # ==============================================================================
 # Tests are re-ordered, and since the default `config/.bashrc` test aliases use
diff --git a/test-http/src/test/CVE-2000-0008_public.json b/test-http/src/test/CVE-2000-0008_public.json
new file mode 100644
index 00000000..72cb1f2b
--- /dev/null
+++ b/test-http/src/test/CVE-2000-0008_public.json
@@ -0,0 +1,72 @@
+{
+    "CVE_data_meta": {
+        "ASSIGNER": "cvemitre@mitre.org",
+        "ID": "CVE-2000-0008",
+        "STATE": "PUBLIC"
+    },
+    "affects": {
+        "vendor": {
+            "vendor_data": [
+                {
+                    "product": {
+                        "product_data": [
+                            {
+                                "product_name": "n/a",
+                                "version": {
+                                    "version_data": [
+                                        {
+                                            "version_value": "n/a"
+                                        }
+                                    ]
+                                }
+                            }
+                        ]
+                    },
+                    "vendor_name": "n/a"
+                }
+            ]
+        }
+    },
+    "data_format": "MITRE",
+    "data_type": "CVE",
+    "data_version": "4.0",
+    "description": {
+        "description_data": [
+            {
+                "lang": "eng",
+                "value": "Cross-site scripting (XSS) vulnerability in Revive Adserver before 4.0.1 allows remote authenticated users to inject arbitrary web script or HTML via the user's email address."
+            }
+        ]
+    },
+    "problemtype": {
+        "problemtype_data": [
+            {
+                "description": [
+                    {
+                        "lang": "eng",
+                        "value": "n/a"
+                    }
+                ]
+            }
+        ]
+    },
+    "references": {
+        "reference_data": [
+            {
+                "name": "[oss-security] 20170202 Re: CVE request: multiples vulnerabilities in Revive Adserver",
+                "refsource": "MLIST",
+                "url": "http://www.openwall.com/lists/oss-security/2017/02/02/3"
+            },
+            {
+                "name": "https://www.revive-adserver.com/security/revive-sa-2017-001/",
+                "refsource": "CONFIRM",
+                "url": "https://www.revive-adserver.com/security/revive-sa-2017-001/"
+            },
+            {
+                "name": "95875",
+                "refsource": "BID",
+                "url": "http://www.securityfocus.com/bid/95875"
+            }
+        ]
+    }
+}
diff --git a/test-http/src/test/CVE-2009-0009_reject.json b/test-http/src/test/CVE-2009-0009_reject.json
new file mode 100644
index 00000000..08d9fef6
--- /dev/null
+++ b/test-http/src/test/CVE-2009-0009_reject.json
@@ -0,0 +1,18 @@
+{
+    "data_type": "CVE",
+    "data_format": "MITRE",
+    "data_version": "4.0",
+    "CVE_data_meta": {
+        "ID": "CVE-2009-0009",
+        "ASSIGNER": "cve@mitre.org",
+        "STATE": "REJECT"
+    },
+    "description": {
+        "description_data": [
+            {
+                "lang": "eng",
+                "value": "I professional site herself recently behavior. Situation institution meeting recognize successful."
+            }
+        ]
+    }
+}
diff --git a/test-http/src/test/CVE-2017-5833_reserved.json b/test-http/src/test/CVE-2017-5833_reserved.json
new file mode 100644
index 00000000..515e32b9
--- /dev/null
+++ b/test-http/src/test/CVE-2017-5833_reserved.json
@@ -0,0 +1,18 @@
+{
+    "CVE_data_meta": {
+        "ASSIGNER": "cve@mitre.org",
+        "ID": "CVE-2017-5833",
+        "STATE": "RESERVED"
+    },
+    "data_format": "MITRE",
+    "data_type": "CVE",
+    "data_version": "4.0",
+    "description": {
+        "description_data": [
+            {
+                "lang": "eng",
+                "value": "** RESERVED ** This candidate has been reserved by an organization or individual that will use it when announcing a new security problem. When the candidate has been publicized, the details for this candidate will be provided."
+            }
+        ]
+    }
+} 
diff --git a/test-http/src/test/CVE-2021-0003_public.json b/test-http/src/test/CVE-2021-0003_public.json
new file mode 100644
index 00000000..8105fccd
--- /dev/null
+++ b/test-http/src/test/CVE-2021-0003_public.json
@@ -0,0 +1,72 @@
+{
+    "CVE_data_meta": {
+        "ASSIGNER": "cvemitre@mitre.org",
+        "ID": "CVE-2021-0003",
+        "STATE": "PUBLIC"
+    },
+    "affects": {
+        "vendor": {
+            "vendor_data": [
+                {
+                    "product": {
+                        "product_data": [
+                            {
+                                "product_name": "n/a",
+                                "version": {
+                                    "version_data": [
+                                        {
+                                            "version_value": "n/a"
+                                        }
+                                    ]
+                                }
+                            }
+                        ]
+                    },
+                    "vendor_name": "n/a"
+                }
+            ]
+        }
+    },
+    "data_format": "MITRE",
+    "data_type": "CVE",
+    "data_version": "4.0",
+    "description": {
+        "description_data": [
+            {
+                "lang": "eng",
+                "value": "Cross-site scripting (XSS) vulnerability in Revive Adserver before 4.0.1 allows remote authenticated users to inject arbitrary web script or HTML via the user's email address."
+            }
+        ]
+    },
+    "problemtype": {
+        "problemtype_data": [
+            {
+                "description": [
+                    {
+                        "lang": "eng",
+                        "value": "n/a"
+                    }
+                ]
+            }
+        ]
+    },
+    "references": {
+        "reference_data": [
+            {
+                "name": "[oss-security] 20170202 Re: CVE request: multiples vulnerabilities in Revive Adserver",
+                "refsource": "MLIST",
+                "url": "http://www.openwall.com/lists/oss-security/2017/02/02/3"
+            },
+            {
+                "name": "https://www.revive-adserver.com/security/revive-sa-2017-001/",
+                "refsource": "CONFIRM",
+                "url": "https://www.revive-adserver.com/security/revive-sa-2017-001/"
+            },
+            {
+                "name": "95875",
+                "refsource": "BID",
+                "url": "http://www.securityfocus.com/bid/95875"
+            }
+        ]
+    }
+}
diff --git a/test-http/src/test/CVE-2021-0004_public.json b/test-http/src/test/CVE-2021-0004_public.json
new file mode 100644
index 00000000..c888b8d5
--- /dev/null
+++ b/test-http/src/test/CVE-2021-0004_public.json
@@ -0,0 +1,72 @@
+{
+    "data_type": "CVE",
+    "data_format": "MITRE",
+    "data_version": "4.0",
+    "CVE_data_meta": {
+        "ID": "CVE-2021-0004",
+        "ASSIGNER": "franciscoanderson@today_2.com",
+        "STATE": "PUBLIC"
+    },
+    "affects": {
+        "vendor": {
+            "vendor_data": [
+                {
+                    "vendor_name": "Myers, Snow and Christian",
+                    "product": {
+                        "product_data": [
+                            {
+                                "product_name": "away",
+                                "version": {
+                                    "version_data": [
+                                        {
+                                            "version_value": "fine, sometimes, visit, various, two, maintain"
+                                        }
+                                    ]
+                                }
+                            }
+                        ]
+                    }
+                }
+            ]
+        }
+    },
+    "problemtype": {
+        "problemtype_data": [
+            {
+                "description": [
+                    {
+                        "lang": "eng",
+                        "value": "De-engineered uniform open system"
+                    }
+                ]
+            }
+        ]
+    },
+    "references": {
+        "reference_data": [
+            {
+                "refsource": "THESE",
+                "name": "Michael Carroll",
+                "url": "http://jones-blackburn.org/blog/tag/posts/post/"
+            },
+            {
+                "refsource": "MISC",
+                "name": "https://clark.biz/",
+                "url": "https://clark.biz/"
+            },
+            {
+                "refsource": "DARK",
+                "name": "Dr. Roger Patterson Jr.",
+                "url": "http://rodriguez.com/"
+            }
+        ]
+    },
+    "description": {
+        "description_data": [
+            {
+                "lang": "eng",
+                "value": "Deep all certain. Then send how dark pick specific. Not general face any cup."
+            }
+        ]
+    }
+}
diff --git a/test-http/src/test/CVE-3000-0001_nonexistent.json b/test-http/src/test/CVE-3000-0001_nonexistent.json
new file mode 100644
index 00000000..f1dfe120
--- /dev/null
+++ b/test-http/src/test/CVE-3000-0001_nonexistent.json
@@ -0,0 +1,72 @@
+{
+    "CVE_data_meta": {
+        "ASSIGNER": "cvemitre@mitre.org",
+        "ID": "CVE-3000-0001",
+        "STATE": "PUBLIC"
+    },
+    "affects": {
+        "vendor": {
+            "vendor_data": [
+                {
+                    "product": {
+                        "product_data": [
+                            {
+                                "product_name": "n/a",
+                                "version": {
+                                    "version_data": [
+                                        {
+                                            "version_value": "n/a"
+                                        }
+                                    ]
+                                }
+                            }
+                        ]
+                    },
+                    "vendor_name": "n/a"
+                }
+            ]
+        }
+    },
+    "data_format": "MITRE",
+    "data_type": "CVE",
+    "data_version": "4.0",
+    "description": {
+        "description_data": [
+            {
+                "lang": "eng",
+                "value": "Cross-site scripting (XSS) vulnerability in Revive Adserver before 4.0.1 allows remote authenticated users to inject arbitrary web script or HTML via the user's email address."
+            }
+        ]
+    },
+    "problemtype": {
+        "problemtype_data": [
+            {
+                "description": [
+                    {
+                        "lang": "eng",
+                        "value": "n/a"
+                    }
+                ]
+            }
+        ]
+    },
+    "references": {
+        "reference_data": [
+            {
+                "name": "[oss-security] 20170202 Re: CVE request: multiples vulnerabilities in Revive Adserver",
+                "refsource": "MLIST",
+                "url": "http://www.openwall.com/lists/oss-security/2017/02/02/3"
+            },
+            {
+                "name": "https://www.revive-adserver.com/security/revive-sa-2017-001/",
+                "refsource": "CONFIRM",
+                "url": "https://www.revive-adserver.com/security/revive-sa-2017-001/"
+            },
+            {
+                "name": "95875",
+                "refsource": "BID",
+                "url": "http://www.securityfocus.com/bid/95875"
+            }
+        ]
+    }
+}
diff --git a/test-http/src/test/cve.py b/test-http/src/test/cve.py
new file mode 100644
index 00000000..bbadb088
--- /dev/null
+++ b/test-http/src/test/cve.py
@@ -0,0 +1,326 @@
+import copy
+import datetime as dt
+import json
+import pytest
+import requests
+import time
+import uuid
+from src import env, utils
+from src.utils import (assert_contains, ok_response_contains,
+                       response_contains, response_contains_json)
+
+CVE_URL = '/api/cve'
+cve_id = 'CVE-1999-0004'
+cve_id_dne = 'CVE-3000-0001'
+curr_cve_id = 'CVE-2021-0003'
+update_cve_id = create_cve_id = 'CVE-2000-0008'
+reserved_cve_id = 'CVE-2017-5833'
+reject_cve_id = 'CVE-2009-0009'
+
+
+# GET /cve
+def test_get_all_cves(org_admin_headers):
+    """ services api rejects requests for admin orgs """
+    res = requests.get(
+        f'{env.AWG_BASE_URL}{CVE_URL}/',
+        headers=org_admin_headers
+    )
+    assert res.status_code == 403
+    response_contains_json(res, 'error', 'SECRETARIAT_ONLY')
+
+
+# GET /cve
+def test_get_all_cves(reg_user_headers):
+    """ services api rejects requests for regular users """
+    res = requests.get(
+        f'{env.AWG_BASE_URL}{CVE_URL}/',
+        headers=reg_user_headers
+    )
+    assert res.status_code == 403
+    response_contains_json(res, 'error', 'SECRETARIAT_ONLY')
+
+
+# GET /cve
+def test_get_all_cves():
+    """ services api accepts requests for all CVEs for secretariat users """
+    res = requests.get(
+        f'{env.AWG_BASE_URL}{CVE_URL}/',
+        headers=utils.BASE_HEADERS
+    )
+    assert res.status_code == utils.HTTP_OK
+    assert len(json.loads(res.content.decode())['cveRecords']) >= 1
+
+
+# GET /cve
+def test_get_cve_by_time_modified():
+    t_before = dt.datetime.now().strftime('%Y-%m-%dT%H:%M:%S')
+    time.sleep(1)
+    update_cve('CVE-2021-0004_public', 'CVE-2021-0004')
+    time.sleep(1)
+    update_cve('CVE-2021-0003_public', 'CVE-2021-0003')
+    time.sleep(1)
+    t_after = dt.datetime.now().strftime('%Y-%m-%dT%H:%M:%S')
+
+    res = requests.get(
+        f'{env.AWG_BASE_URL}{CVE_URL}/',
+        headers=utils.BASE_HEADERS,
+        params={
+            'time_modified.lt': t_after,
+            'time_modified.gt': t_before
+        }
+    )
+    assert res.status_code == utils.HTTP_OK
+    assert len(json.loads(res.content.decode())['cveRecords']) >= 2
+
+
+# GET /cve/:id
+def test_get_cve(org_admin_headers):
+    """ services api rejects requests for admin orgs """
+    res = requests.get(
+        f'{env.AWG_BASE_URL}{CVE_URL}/{cve_id}',
+        headers=org_admin_headers
+    )
+    assert res.status_code == 403
+    response_contains_json(res, 'error', 'SECRETARIAT_ONLY')
+
+
+# GET /cve/:id
+def test_get_cve(reg_user_headers):
+    """ services api rejects requests for regular users """
+    res = requests.get(
+        f'{env.AWG_BASE_URL}{CVE_URL}/{cve_id}',
+        headers=reg_user_headers
+    )
+    assert res.status_code == 403
+    response_contains_json(res, 'error', 'SECRETARIAT_ONLY')
+
+
+# GET /cve/:id
+def test_get_cve():
+    res = requests.get(
+        f'{env.AWG_BASE_URL}{CVE_URL}/{cve_id}',
+        headers=utils.BASE_HEADERS
+    )
+    ok_response_contains(res, cve_id)
+
+
+# GET /cve/:id
+def test_get_cve_dne():
+    res = requests.get(
+        f'{env.AWG_BASE_URL}{CVE_URL}/{cve_id_dne}',
+        headers=utils.BASE_HEADERS
+    )
+    assert res.status_code == 404
+    response_contains_json(res, 'error', 'CVE_RECORD_DNE')
+
+
+# POST /cve/:id
+def test_create_cve(org_admin_headers):
+    """ services api rejects requests for admin orgs """
+    with open('./src/test/CVE-2000-0008_public.json') as json_file:
+        data = json.load(json_file)
+        res = requests.post(
+            f'{env.AWG_BASE_URL}{CVE_URL}/{create_cve_id}',
+            headers=org_admin_headers,
+            json=data
+        )
+        assert res.status_code == 403
+        response_contains_json(res, 'error', 'SECRETARIAT_ONLY')
+
+
+# POST /cve/:id
+def test_create_cve(reg_user_headers):
+    """ services api rejects requests for regular users """
+    with open('./src/test/CVE-2000-0008_public.json') as json_file:
+        data = json.load(json_file)
+        res = requests.post(
+            f'{env.AWG_BASE_URL}{CVE_URL}/{create_cve_id}',
+            headers=reg_user_headers,
+            json=data
+        )
+        assert res.status_code == 403
+        response_contains_json(res, 'error', 'SECRETARIAT_ONLY')
+
+
+# POST /cve/:id
+def test_create_cve():
+    """ publish a cve in the PUBLIC state """
+    with open('./src/test/CVE-2000-0008_public.json') as json_file:
+        data = json.load(json_file)
+        res = requests.post(
+            f'{env.AWG_BASE_URL}{CVE_URL}/{create_cve_id}',
+            headers=utils.BASE_HEADERS,
+            json=data
+        )
+        try: 
+            ok_response_contains(res, create_cve_id) 
+        except AssertionError: 
+            assert res.status_code == 400
+            response_contains_json(res, 'error', 'CVE_RECORD_EXISTS')
+
+
+# POST /cve/:id
+def test_create_cve_mismatch():
+    """ the cve id in the json body does not match the cve id in the path parameter """
+    with open('./src/test/CVE-2000-0008_public.json') as json_file:
+        data = json.load(json_file)
+        res = requests.post(
+            f'{env.AWG_BASE_URL}{CVE_URL}/{curr_cve_id}',
+            headers=utils.BASE_HEADERS,
+            json=data
+        )
+        assert res.status_code == 400
+        response_contains_json(res, 'error', 'CVEID_MISMATCH')
+
+
+# POST /cve/:id
+def test_create_cve_reserved_state():
+    """ the cve record cannot be created in the RESERVED state """
+    with open('./src/test/CVE-2017-5833_reserved.json') as json_file:
+        data = json.load(json_file)
+        res = requests.post(
+            f'{env.AWG_BASE_URL}{CVE_URL}/{reserved_cve_id}',
+            headers=utils.BASE_HEADERS,
+            json=data
+        )
+        assert res.status_code == 400
+        response_contains_json(res, 'error', 'CVE_UNSUPPORTED_RECORD_STATE')
+
+
+# POST /cve/:id
+def test_create_nonexistent_cveid():
+    """ the cve record cannot be created because the CVE ID doesn't exist """
+    with open('./src/test/CVE-3000-0001_nonexistent.json') as json_file:
+        data = json.load(json_file)
+        res = requests.post(
+            f'{env.AWG_BASE_URL}{CVE_URL}/{cve_id_dne}',
+            headers=utils.BASE_HEADERS,
+            json=data
+        )
+        assert res.status_code == 403
+        response_contains_json(res, 'error', 'CVEID_DNE')
+
+
+# POST /cve/:id
+def test_create_existent_cve():
+    """ the cve record cannot be created because it already exists """
+    with open('./src/test/CVE-2021-0004_public.json') as json_file:
+        data = json.load(json_file)
+        res = requests.post(
+            f'{env.AWG_BASE_URL}{CVE_URL}/CVE-2021-0004',
+            headers=utils.BASE_HEADERS,
+            json=data
+        )
+        assert res.status_code == 400
+        response_contains_json(res, 'error', 'CVE_RECORD_EXISTS')
+
+
+# PUT /cve/:id
+def test_update_cve_record(org_admin_headers):
+    """ services api rejects requests for admin orgs """
+    with open('./src/test/CVE-2000-0008_public.json') as json_file:
+        data = json.load(json_file)
+        res = requests.put(
+            f'{env.AWG_BASE_URL}{CVE_URL}/{update_cve_id}',
+            headers=org_admin_headers,
+            json=data
+        )
+        assert res.status_code == 403
+        response_contains_json(res, 'error', 'SECRETARIAT_ONLY')
+
+
+# PUT /cve/:id
+def test_update_cve_record(reg_user_headers):
+    """ services api rejects requests for admin orgs """
+    with open('./src/test/CVE-2000-0008_public.json') as json_file:
+        data = json.load(json_file)
+        res = requests.put(
+            f'{env.AWG_BASE_URL}{CVE_URL}/{update_cve_id}',
+            headers=reg_user_headers,
+            json=data
+        )
+        assert res.status_code == 403
+        response_contains_json(res, 'error', 'SECRETARIAT_ONLY')
+
+
+# PUT /cve/:id
+def test_update_cve_record():
+    with open('./src/test/CVE-2021-0003_public.json') as json_file:
+        data = json.load(json_file)
+        res = requests.put(
+            f'{env.AWG_BASE_URL}{CVE_URL}/{curr_cve_id}',
+            headers=utils.BASE_HEADERS,
+            json=data
+        )
+        ok_response_contains(res, curr_cve_id)
+
+
+# PUT /cve/:id
+def test_update_cve_mismatch():
+    """ the cve id in the json body does not match the cve id in the path parameter """
+    with open('./src/test/CVE-2000-0008_public.json') as json_file:
+        data = json.load(json_file)
+        res = requests.put(
+            f'{env.AWG_BASE_URL}{CVE_URL}/{curr_cve_id}',
+            headers=utils.BASE_HEADERS,
+            json=data
+        )
+        assert res.status_code == 400
+        response_contains_json(res, 'error', 'CVEID_MISMATCH')
+
+
+# PUT /cve/:id
+def test_update_cve_reserved_state():
+    """ the cve record cannot be changed to the RESERVED state """
+    with open('./src/test/CVE-2017-5833_reserved.json') as json_file:
+        data = json.load(json_file)
+        res = requests.put(
+            f'{env.AWG_BASE_URL}{CVE_URL}/{reserved_cve_id}',
+            headers=utils.BASE_HEADERS,
+            json=data
+        )
+        assert res.status_code == 400
+        response_contains_json(res, 'error', 'CVE_UNSUPPORTED_RECORD_STATE')
+
+
+# PUT /cve/:id
+def test_update_nonexistent_cveid():
+    """ the cve record cannot be updated because the CVE ID doesn't exist """
+    with open('./src/test/CVE-3000-0001_nonexistent.json') as json_file:
+        data = json.load(json_file)
+        res = requests.put(
+            f'{env.AWG_BASE_URL}{CVE_URL}/{cve_id_dne}',
+            headers=utils.BASE_HEADERS,
+            json=data
+        )
+        assert res.status_code == 403
+        response_contains_json(res, 'error', 'CVEID_DNE')
+
+
+# PUT /cve/:id
+def test_update_nonexistent_cve():
+    """ the cve record cannot be updated because it doesn't exist """
+    with open('./src/test/CVE-2009-0009_reject.json') as json_file:
+        data = json.load(json_file)
+        res = requests.put(
+            f'{env.AWG_BASE_URL}{CVE_URL}/{reject_cve_id}',
+            headers=utils.BASE_HEADERS,
+            json=data
+        )
+        assert res.status_code == 403
+        response_contains_json(res, 'error', 'CVE_RECORD_DNE')
+
+
+# CVE ENDPOINT UTILITIES
+# ==============================================================================
+# these are unique to the `{CVE_URL}` endpoint for the AWG system
+
+
+def update_cve(filename, cveid):
+    with open(f'./src/test/{filename}.json') as json_file:
+        data = json.load(json_file)
+        res = requests.put(
+            f'{env.AWG_BASE_URL}{CVE_URL}/{cveid}',
+            headers=utils.BASE_HEADERS,
+            json=data
+        )
diff --git a/test-http/src/test/org.py b/test-http/src/test/org.py
index 60116302..0f763f06 100644
--- a/test-http/src/test/org.py
+++ b/test-http/src/test/org.py
@@ -15,6 +15,7 @@ from src.utils import (assert_contains, ok_response_contains,
 ORG_URL = '/api/org'
 
 
+# GET /org
 def test_get_all_orgs():
     """ secretariat users can request a list of all organizations """
     res = requests.get(
@@ -25,6 +26,7 @@ def test_get_all_orgs():
     assert len(json.loads(res.content.decode())['organizations']) >= 1
 
 
+# GET /org/:shortname
 def test_get_mitre_cna():
     """ the cve services api contains the mitre cna """
     res = requests.get(
@@ -36,6 +38,7 @@ def test_get_mitre_cna():
     ok_response_contains_json(res, 'short_name', 'mitre')
 
 
+# GET /org/:shortname/id_quota
 def test_get_mitre_id_quota():
     """ the cve services api's mitre cna has a valid id quota """
     res = get_org_id_data('mitre')
@@ -225,6 +228,7 @@ def test_put_update_org_that_does_not_exist():
     response_contains_json(res, 'error', 'ORG_DNE_PARAM')
     assert_contains(res, 'by the shortname parameter does not exist')
 
+
 def test_put_update_mitre_org_nonexistent_user():
     """ cve services api will fail requests from users that don't exist """
     uid = str(uuid.uuid4())
@@ -240,6 +244,7 @@ def test_put_update_mitre_org_nonexistent_user():
     response_contains_json(res, 'error', 'UNAUTHORIZED')
     response_contains_json(res, 'message', 'Unauthorized')
 
+
 def test_put_update_user_username():
     """ services api allows user usernames to be updated by secretariat """
     org, user = create_new_user_with_new_org_by_uuid()
@@ -403,6 +408,13 @@ def test_put_update_user_reset_secret():
 # these are unique to the `{ORG_URL}` endpoint for the AWG system
 
 
+def get_org(cna_short_name):
+    return requests.get(
+        f'{env.AWG_BASE_URL}{ORG_URL}/{cna_short_name}',
+        headers=utils.BASE_HEADERS
+    )
+
+
 def get_org_id_data(cna_short_name):
     return requests.get(
         f'{env.AWG_BASE_URL}{ORG_URL}/{cna_short_name}/id_quota',
@@ -436,6 +448,15 @@ def post_new_org_user(org_short_name, user_name):
     )
 
 
+def create_new_user_with_new_org_by_shortname(org_short_name, user_name):
+    """ create an organization, and a user under that org """
+    org_res = post_new_org(org_short_name, org_short_name)
+    assert org_res.status_code == 200
+    user_res = post_new_org_user(org_short_name, user_name)
+    assert user_res.status_code == 200
+    return org_short_name, user_name
+
+
 def create_new_user_with_new_org_by_uuid():
     """ create an organization, and a user under that org, using uuid """
     user_name = str(uuid.uuid4())
diff --git a/test-http/src/test/org_as_org_admin.py b/test-http/src/test/org_as_org_admin.py
index 3fd0185e..6e85b31a 100644
--- a/test-http/src/test/org_as_org_admin.py
+++ b/test-http/src/test/org_as_org_admin.py
@@ -5,12 +5,14 @@
 import json
 import requests
 import uuid
-from src import env
+from src import env, utils
 from src.test.org import (ORG_URL, create_new_user_with_new_org_by_uuid,
-                                 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
 
 
+# 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(
@@ -21,20 +23,22 @@ def test_org_admin_get_all_orgs(org_admin_headers):
     response_contains_json(res, 'error', 'SECRETARIAT_ONLY')
 
 
+# GET /org/:shortname
 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',
+        f'{env.AWG_BASE_URL}{ORG_URL}/mitre', # the secretariat's org
         headers=org_admin_headers
     )
     assert res.status_code == 403
     response_contains_json(res, 'error', 'NOT_SAME_ORG_OR_SECRETARIAT')
 
 
+# GET /org/:shortname
 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())
-    res = post_new_org(different_org, different_org)
+    different_org = str(uuid.uuid4()) # 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}',
@@ -44,6 +48,7 @@ def test_org_admin_get_another_org(org_admin_headers):
     response_contains_json(res, 'error', 'NOT_SAME_ORG_OR_SECRETARIAT')
 
 
+# GET /org/:shortname
 def test_org_admin_get_own_org(org_admin_headers):
     """ services api allows org admins to get their own org's document """
     org = org_admin_headers["CVE-API-ORG"]
@@ -55,6 +60,32 @@ def test_org_admin_get_own_org(org_admin_headers):
     response_contains(res, org_admin_headers['CVE-API-ORG'])
 
 
+# GET /org/:shortname/id_quota
+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
+        headers=org_admin_headers
+    )
+    assert res.status_code == 403
+    response_contains_json(res, 'error', 'NOT_SAME_ORG_OR_SECRETARIAT')
+
+
+# GET /org/:shortname/id_quota
+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()) # 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',
+        headers=org_admin_headers
+    )
+    assert res.status_code == 403
+    response_contains_json(res, 'error', 'NOT_SAME_ORG_OR_SECRETARIAT')
+
+
+# GET /org/:shortname/id_quota
 def test_org_admin_get_own_id_quota_info(org_admin_headers):
     """ services api allows org admins to get info about their org's quota """
     org = org_admin_headers["CVE-API-ORG"]
@@ -68,6 +99,7 @@ def test_org_admin_get_own_id_quota_info(org_admin_headers):
     assert id_quota <= 100000
 
 
+# GET /org/:shortname/user/:username
 def test_org_admin_get_mitre_user_info(org_admin_headers):
     """ services api prevents org users from viewing secretariat user info """
     res = requests.get(
@@ -78,8 +110,9 @@ def test_org_admin_get_mitre_user_info(org_admin_headers):
     response_contains_json(res, 'error', 'NOT_SAME_ORG_OR_SECRETARIAT')
 
 
+# GET /org/:shortname/user/:username
 def test_org_admin_get_another_org_user_info(org_admin_headers):
-    """ services api prevents org admin users from viewing another org users info """
+    """ services api prevents org admin users from viewing another org user's info """
     org, user = create_new_user_with_new_org_by_uuid()
     res = requests.get(
         f'{env.AWG_BASE_URL}{ORG_URL}/{org}/user/{user}',
@@ -89,6 +122,7 @@ def test_org_admin_get_another_org_user_info(org_admin_headers):
     response_contains_json(res, 'error', 'NOT_SAME_ORG_OR_SECRETARIAT')
 
 
+# GET /org/:shortname/user/:username
 def test_org_admin_get_own_user_info(org_admin_headers):
     """ services api allows org admin to get its own user info """
     org = org_admin_headers['CVE-API-ORG']
@@ -101,6 +135,46 @@ def test_org_admin_get_own_user_info(org_admin_headers):
     response_contains(res, user)
 
 
+# GET /org/:shortname/users
+def test_org_admin_get_mitre_users_info(org_admin_headers):
+    """ services api prevents org users from viewing secretariat users info """
+    res = requests.get(
+        f'{env.AWG_BASE_URL}{ORG_URL}/mitre/users',
+        headers=org_admin_headers
+    )
+    assert res.status_code == 403
+    response_contains_json(res, 'error', 'NOT_SAME_ORG_OR_SECRETARIAT')
+
+
+# GET /org/:shortname/users
+def test_org_admin_get_another_org_users_info(org_admin_headers):
+    """ services api prevents org admin users from viewing all other org's user info """
+    org = str(uuid.uuid4())
+    res = post_new_org(org, org)
+    assert res.status_code == 200
+    res = requests.get(
+        f'{env.AWG_BASE_URL}{ORG_URL}/{org}/users',
+        headers=org_admin_headers
+    )
+    assert res.status_code == 403
+    response_contains_json(res, 'error', 'NOT_SAME_ORG_OR_SECRETARIAT')
+
+
+# GET /org/:shortname/users
+def test_org_admin_get_own_users_info(org_admin_headers):
+    """ services api allows org admin to get its own users info """
+    org = org_admin_headers['CVE-API-ORG']
+    user = org_admin_headers['CVE-API-USER']
+    res = requests.get(
+        f'{env.AWG_BASE_URL}{ORG_URL}/{org}/users',
+        headers=org_admin_headers
+    )
+    assert res.status_code == 200
+    assert len(json.loads(res.content.decode())['users']) >= 1
+    response_contains(res, user)
+
+
+# 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(
@@ -111,6 +185,8 @@ def test_org_admin_cannot_create_another_org(org_admin_headers):
     assert res.status_code == 403
     response_contains_json(res, 'error', 'SECRETARIAT_ONLY')
 
+
+# PUT /org/:shortname
 def test_org_admin_cannot_update_org(org_admin_headers):
     """ services api does not allow org admins to update their own orgs """
     res = requests.post(
@@ -121,6 +197,23 @@ def test_org_admin_cannot_update_org(org_admin_headers):
     assert res.status_code == 403
     response_contains_json(res, 'error', 'SECRETARIAT_ONLY')
 
+
+# POST /org/:shortname/user
+def test_org_admin_cannot_create_user_for_another_org(org_admin_headers):
+    """ services api prevents org admins from creating a user with conflicts in the organization the user belongs to (org in path is diff from org in json body)  """
+    org = str(uuid.uuid4())
+    res = post_new_org(org, org)
+    assert res.status_code == 200
+    res = requests.post(
+        f'{env.AWG_BASE_URL}{ORG_URL}/{org}/user',
+        headers=org_admin_headers,
+        json={'username':'BLARG', 'org_UUID': 'test'}
+    )
+    assert res.status_code == 400
+    response_contains_json(res, 'error', 'SHORTNAME_MISMATCH')
+
+
+# POST /org/:shortname/user
 def test_org_admin_cannot_create_user_for_another_org(org_admin_headers):
     """ services api prevents org admins from creating users for other orgs """
     org = str(uuid.uuid4())
@@ -134,6 +227,278 @@ def test_org_admin_cannot_create_user_for_another_org(org_admin_headers):
     assert res.status_code == 403
     response_contains_json(res, 'error', 'NOT_ORG_ADMIN_OR_SECRETARIAT')
 
+
+# POST /org/:shortname/user
+def test_org_admin_cannot_create_existen_user(org_admin_headers):
+    """ services api prevents org admins from creating existing users """
+    user = str(uuid.uuid4())
+    org = org_admin_headers['CVE-API-ORG']
+    res = post_new_org_user(org, user)
+    assert res.status_code == 200
+    res = requests.post(
+        f'{env.AWG_BASE_URL}{ORG_URL}/{org}/user',
+        headers=org_admin_headers,
+        json={'username':user}
+    )
+    assert res.status_code == 400
+    response_contains_json(res, 'error', 'USER_EXISTS')
+
+
+# PUT /org/:shortname/user/:username
+def test_org_admin_cannot_update_user_org_dne(org_admin_headers):
+    """ services api prevents org admins from updating a user from an org that doesn't exist """
+    user = org_admin_headers['CVE-API-USER']
+    org = str(uuid.uuid4())
+    res = requests.put(
+        f'{env.AWG_BASE_URL}{ORG_URL}/{org}/user/{user}',
+        headers=org_admin_headers
+    )
+    assert res.status_code == 404
+    response_contains_json(res, 'error', 'ORG_DNE_PARAM')
+
+
+# PUT /org/:shortname/user/:username
+def test_org_admin_cannot_update_user_dne(org_admin_headers):
+    """ services api prevents org admins from updating a user that doesn't exist """
+    user = str(uuid.uuid4())
+    org = org_admin_headers['CVE-API-ORG']
+    res = requests.put(
+        f'{env.AWG_BASE_URL}{ORG_URL}/{org}/user/{user}',
+        headers=org_admin_headers
+    )
+    assert res.status_code == 404
+    response_contains_json(res, 'error', 'USER_DNE')
+
+
+# PUT /org/:shortname/user/:username
+def test_org_admin_cannot_update_user_for_another_org(org_admin_headers):
+    """ services api prevents org admins from updating a user from a diff org """
+    org, user = create_new_user_with_new_org_by_uuid()
+    res = requests.put(
+        f'{env.AWG_BASE_URL}{ORG_URL}/{org}/user/{user}',
+        headers=org_admin_headers
+    )
+    assert res.status_code == 403
+    response_contains_json(res, 'error', 'NOT_SAME_USER_OR_SECRETARIAT')
+
+
+# PUT /org/:shortname/user/:username
+def test_org_admin_cannot_update_user_new_shortname_dne(org_admin_headers):
+    """ services api prevents org admins from updating a user's org that doesn't exist """
+    org = org_admin_headers['CVE-API-ORG']
+    user = org_admin_headers['CVE-API-USER']
+    org_shortname = str(uuid.uuid4())
+    res = requests.put(
+        f'{env.AWG_BASE_URL}{ORG_URL}/{org}/user/{user}?org_shortname={org_shortname}',
+        headers=org_admin_headers
+    )
+    assert res.status_code == 404
+    response_contains_json(res, 'error', 'ORG_DNE')
+
+
+# PUT /org/:shortname/user/:username
+def test_org_admin_cannot_update_duplicate_user_with_new_shortname_and_username(org_admin_headers):
+    """ services api prevents org admins from updating a user's org and username if that user already exist """
+    org1 = org_admin_headers['CVE-API-ORG']
+    user1 = org_admin_headers['CVE-API-USER']
+    org2, user2 = create_new_user_with_new_org_by_uuid()
+    res = requests.put(
+        f'{env.AWG_BASE_URL}{ORG_URL}/{org1}/user/{user1}?org_shortname={org2}&new_username={user2}',
+        headers=org_admin_headers
+    )
+    assert res.status_code == 403
+    response_contains_json(res, 'error', 'DUPLICATE_USERNAME')
+
+
+# PUT /org/:shortname/user/:username
+def test_org_admin_cannot_update_duplicate_user_with_new_username(org_admin_headers):
+    """ services api prevents org admins from updating a user's username if that user already exist """
+    org = org_admin_headers['CVE-API-ORG']
+    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
+    assert res.status_code == 200
+    res = requests.put(
+        f'{env.AWG_BASE_URL}{ORG_URL}/{org}/user/{user1}?new_username={user2}',
+        headers=org_admin_headers
+    )
+    assert res.status_code == 403
+    response_contains_json(res, 'error', 'DUPLICATE_USERNAME')
+
+
+# PUT /org/:shortname/user/:username
+def test_org_admin_cannot_update_duplicate_user_with_new_shortname(org_admin_headers):
+    """ services api prevents org admins from updating a user's org if that user already exist """
+    user = org_admin_headers['CVE-API-USER']
+    org1 = org_admin_headers['CVE-API-ORG']
+    org2 = str(uuid.uuid4())
+    res = create_new_user_with_new_org_by_shortname(org2, user) # creating a user with same username as org admin user's username
+    res = requests.put(
+        f'{env.AWG_BASE_URL}{ORG_URL}/{org1}/user/{user}?org_shortname={org2}',
+        headers=org_admin_headers
+    )
+    assert res.status_code == 403
+    response_contains_json(res, 'error', 'DUPLICATE_USERNAME')
+
+
+# PUT /org/:shortname/user/:username
+def test_org_admin_update_same_org_user_state_sn_un(org_admin_headers):
+    """  allows admin users to update a user's active state, org shortname, and user username """
+    org = org_admin_headers['CVE-API-ORG']
+    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 
+    new_shortname = str(uuid.uuid4())   # used in query
+    new_username = str(uuid.uuid4())    # used in query
+    res = post_new_org(new_shortname, new_shortname)  # create new org
+    assert res.status_code == 200
+    res = requests.put(
+        f'{env.AWG_BASE_URL}{ORG_URL}/{org}/user/{user}?org_shortname={new_shortname}&new_username={new_username}&active=false',
+        headers=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'] is not None
+
+
+# PUT /org/:shortname/user/:username
+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']
+    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
+    res = requests.put(
+        f'{env.AWG_BASE_URL}{ORG_URL}/{org}/user/{user}?active_roles.add=admin', # adding role
+        headers=org_admin_headers
+    )
+    assert res.status_code == 200
+    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
+        headers=org_admin_headers
+    )
+    assert res.status_code == 200
+    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&name.surname=s', # updating name
+        headers=org_admin_headers
+    )
+    assert res.status_code == 200
+    assert json.loads(res.content.decode())['updated']['name']['first'] == 't'
+    assert json.loads(res.content.decode())['updated']['name']['last'] == 'e'
+    assert json.loads(res.content.decode())['updated']['name']['middle'] == 's'
+    assert json.loads(res.content.decode())['updated']['name']['suffix'] == 't'
+    assert json.loads(res.content.decode())['updated']['name']['surname'] == 's'
+
+
+# PUT /org/:shortname/user/:username
+def test_org_admin_update_own_user_state_sn_un(org_admin_headers):
+    """  allows admin users to update its own active state, org shortname, and user username """
+    org = org_admin_headers['CVE-API-ORG']
+    user = org_admin_headers['CVE-API-USER']
+    new_shortname = str(uuid.uuid4())   # used in query
+    new_username = str(uuid.uuid4())    # used in query
+    res = post_new_org(new_shortname, new_shortname)  # create new org
+    assert res.status_code == 200
+    res = requests.put(
+        f'{env.AWG_BASE_URL}{ORG_URL}/{org}/user/{user}?org_shortname={new_shortname}&new_username={new_username}&active=false',
+        headers=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'] is not None
+
+
+# PUT /org/:shortname/user/:username
+def test_org_admin_update_own_user_roles_name(org_admin_headers):
+    """  allows admin users to update its own name and remove role """ 
+    org = org_admin_headers['CVE-API-ORG']
+    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
+        headers=org_admin_headers
+    )
+    assert res.status_code == 200
+    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
+        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
+    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
+        headers=utils.BASE_HEADERS
+    )
+    assert res.status_code == 200
+    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&name.surname=s', # updating name
+        headers=org_admin_headers
+    )
+    assert res.status_code == 200
+    assert json.loads(res.content.decode())['updated']['name']['first'] == 't'
+    assert json.loads(res.content.decode())['updated']['name']['last'] == 'e'
+    assert json.loads(res.content.decode())['updated']['name']['middle'] == 's'
+    assert json.loads(res.content.decode())['updated']['name']['suffix'] == 't'
+    assert json.loads(res.content.decode())['updated']['name']['surname'] == 's'
+
+
+# PUT /org/:shortname/user/:username/reset_secret
+def test_org_admin_reset_secret_org_dne(org_admin_headers):
+    org = str(uuid.uuid4())
+    user = str(uuid.uuid4())
+    res = requests.put(
+        f'{env.AWG_BASE_URL}{ORG_URL}/{org}/user/{user}/reset_secret',
+        headers=org_admin_headers
+    )
+    assert res.status_code == 404
+    response_contains_json(res, 'error', 'ORG_DNE_PARAM')
+
+
+# PUT /org/:shortname/user/:username/reset_secret
+def test_org_admin_reset_secret_org_dne(org_admin_headers):
+    org = org_admin_headers['CVE-API-ORG']
+    user = str(uuid.uuid4())
+    res = requests.put(
+        f'{env.AWG_BASE_URL}{ORG_URL}/{org}/user/{user}/reset_secret',
+        headers=org_admin_headers
+    )
+    assert res.status_code == 404
+    response_contains_json(res, 'error', 'USER_DNE')
+
+
+# PUT /org/:shortname/user/:username/reset_secret
+def test_org_admin_reset_diff_org_secret(org_admin_headers):
+    """ services api prevents admin users to reset the secret of users of different org"""
+    org, user = create_new_user_with_new_org_by_uuid()
+    res = requests.put(
+        f'{env.AWG_BASE_URL}{ORG_URL}/{org}/user/{user}/reset_secret',
+        headers=org_admin_headers
+    )
+    assert res.status_code == 403
+    response_contains_json(res, 'error', 'NOT_SAME_USER_OR_SECRETARIAT')
+
+
+# PUT /org/:shortname/user/:username/reset_secret
+def test_org_admin_reset_same_org_secret(org_admin_headers):
+    """ services api allows admin users to reset the secret of users of same org"""
+    org = org_admin_headers['CVE-API-ORG']
+    user = str(uuid.uuid4())
+    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}/reset_secret',
+        headers=org_admin_headers
+    )
+    assert res.status_code == 200
+    response_contains(res, 'API-secret')
+
+
+# PUT /org/:shortname/user/:username/reset_secret
 def test_org_admin_reset_own_secret(org_admin_headers):
     """ services api allows admin users to reset their own secret """
     org = org_admin_headers['CVE-API-ORG']
@@ -144,3 +509,10 @@ def test_org_admin_reset_own_secret(org_admin_headers):
     )
     assert res.status_code == 200
     response_contains(res, 'API-secret')
+
+
+# TODO: test -> requester (reg user) cannot update another user (same or different org)
+# TODO: test -> requester (reg user) cannot update its own: active, roles, org shortname
+# TODO: test -> requester (reg user) can update its own: name, username
+# TODO: test -> requester (reg user) cannot reset user secret of another user (same or different org)
+# TODO: test -> requester (reg user) can reset its own secret
diff --git a/test/unit-tests/org/userTest.js b/test/unit-tests/org/userTest.js
index 567885bc..93743430 100644
--- a/test/unit-tests/org/userTest.js
+++ b/test/unit-tests/org/userTest.js
@@ -224,7 +224,7 @@ describe('Test user functions in Org Controller', () => {
             done(err)
           }
 
-          expect(res).to.have.status(400)
+          expect(res).to.have.status(403)
           expect(res).to.have.property('body').and.to.be.a('object')
           const errObj = error.notOrgAdminOrSecretariat()
           expect(res.body.error).to.equal(errObj.error)