From 7bfe2675ecaf4cbbaeae2cc3954b96a494875e46 Mon Sep 17 00:00:00 2001 From: tierno Date: Fri, 30 Aug 2019 15:12:47 +0000 Subject: [PATCH 01/16] fix Dockerfile local to use osm_nbi as a module Change-Id: I9f3f39d99ce595d74f227b902d55c0dba0ff5dad Signed-off-by: tierno --- Dockerfile.local | 7 +++++-- osm_nbi/__init__.py | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Dockerfile.local b/Dockerfile.local index 7ac6091..3a31273 100644 --- a/Dockerfile.local +++ b/Dockerfile.local @@ -16,7 +16,7 @@ FROM ubuntu:16.04 # Set the working directory to /app -WORKDIR /app/NBI/osm_nbi +WORKDIR /app/NBI RUN apt-get update && apt-get install -y git python3 python3-jsonschema \ python3-pymongo python3-yaml python3-pip python3-keystoneclient \ @@ -59,6 +59,8 @@ VOLUME /app/log # server ENV OSMNBI_SOCKET_HOST 0.0.0.0 ENV OSMNBI_SOCKET_PORT 9999 +ENV OSMNBI_SERVER_SSL_CERTIFICATE /app/NBI/osm_nbi/http/cert.pem +ENV OSMNBI_SERVER_SSL_PRIVATE_KEY /app/NBI/osm_nbi/http/privkey.pem # storage ENV OSMNBI_STORAGE_PATH /app/storage # database @@ -98,4 +100,5 @@ ENV OSMNBI_PROMETHEUS_PORT 9090 ADD . /app/NBI # Run app.py when the container launches -CMD ["python3", "nbi.py"] +CMD python3 -m osm_nbi.nbi + diff --git a/osm_nbi/__init__.py b/osm_nbi/__init__.py index 98cd10b..2569c9e 100644 --- a/osm_nbi/__init__.py +++ b/osm_nbi/__init__.py @@ -13,4 +13,4 @@ ## version = '6.0.2.post0' -version_date = '2018-08-29' +version_date = '2018-08-30' -- 2.17.1 From 5792d7d7e1ff32ef0edfc432bd86a8fb348622c3 Mon Sep 17 00:00:00 2001 From: tierno Date: Fri, 30 Aug 2019 15:37:12 +0000 Subject: [PATCH 02/16] provide version in json format Change-Id: I69140a3655a0cf95212eae1022d6d34eec30a198 Signed-off-by: tierno --- osm_nbi/__init__.py | 2 +- osm_nbi/html_out.py | 2 ++ osm_nbi/nbi.py | 5 +++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/osm_nbi/__init__.py b/osm_nbi/__init__.py index 2569c9e..1e02f66 100644 --- a/osm_nbi/__init__.py +++ b/osm_nbi/__init__.py @@ -12,5 +12,5 @@ # under the License. ## -version = '6.0.2.post0' +version = '6.0.2.post1' version_date = '2018-08-30' diff --git a/osm_nbi/html_out.py b/osm_nbi/html_out.py index a8823bd..871d502 100644 --- a/osm_nbi/html_out.py +++ b/osm_nbi/html_out.py @@ -143,6 +143,8 @@ def format(data, request, response, toke_info): return else: return html_auth2.format(error=data) + if request.path_info in ("/version", "/system"): + return "
" + yaml.safe_dump(data, explicit_start=False, indent=4, default_flow_style=False) + "
" body = html_body.format(item=request.path_info) if response.status and response.status > 202: body += html_body_error.format(yaml.safe_dump(data, explicit_start=True, indent=4, default_flow_style=False)) diff --git a/osm_nbi/nbi.py b/osm_nbi/nbi.py index 1085ebc..03e8727 100644 --- a/osm_nbi/nbi.py +++ b/osm_nbi/nbi.py @@ -557,7 +557,7 @@ class Server(object): return string of dictionary data according to requested json, yaml, xml. By default json :param data: response to be sent. Can be a dict, text or file :param token_info: Contains among other username and project - :param _format: The format to be set as Content-Type ir data is a file + :param _format: The format to be set as Content-Type if data is a file :return: None """ accept = cherrypy.request.headers.get("Accept") @@ -621,7 +621,8 @@ class Server(object): elif args or kwargs: raise NbiException("Invalid URL or query string for version", HTTPStatus.METHOD_NOT_ALLOWED) # TODO include version of other modules, pick up from some kafka admin message - return "
NBI:\n    version: {}\n    date: {}\n".format(nbi_version, nbi_version_date)
+            osm_nbi_version = {"version": nbi_version, "date": nbi_version_date}
+            return self._format_out(osm_nbi_version)
         except NbiException as e:
             cherrypy.response.status = e.http_code.value
             problem_details = {
-- 
2.17.1


From 260dd6fd42eefaa52ac8abdca295f44f2b8c1947 Mon Sep 17 00:00:00 2001
From: tierno 
Date: Mon, 2 Sep 2019 10:48:56 +0000
Subject: [PATCH 03/16] fix bug 838. Validate action over vdu

Change-Id: I9c45cfcc2e0c088a018e79fc06348dc8e03d3717
Signed-off-by: tierno 
---
 osm_nbi/instance_topics.py | 13 ++++++++++++-
 1 file changed, 12 insertions(+), 1 deletion(-)

diff --git a/osm_nbi/instance_topics.py b/osm_nbi/instance_topics.py
index 4b3f04b..e36a723 100644
--- a/osm_nbi/instance_topics.py
+++ b/osm_nbi/instance_topics.py
@@ -443,6 +443,13 @@ class NsLcmOpTopic(BaseTopic):
                 raise EngineException("Invalid parameter member_vnf_index='{}' is not one of the "
                                       "nsd:constituent-vnfd".format(member_vnf_index))
 
+        def check_valid_vdu(vnfd, vdu_id):
+            for vdud in get_iterable(vnfd.get("vdu")):
+                if vdud["id"] == vdu_id:
+                    return vdud
+            else:
+                raise EngineException("Invalid parameter vdu_id='{}' not present at vnfd:vdu:id".format(vdu_id))
+
         def _check_vnf_instantiation_params(in_vnfd, vnfd):
 
             for in_vdu in get_iterable(in_vnfd.get("vdu")):
@@ -520,7 +527,11 @@ class NsLcmOpTopic(BaseTopic):
                 indata["member_vnf_index"] = indata.pop("vnf_member_index")    # for backward compatibility
             if indata.get("member_vnf_index"):
                 vnfd = check_valid_vnf_member_index(indata["member_vnf_index"])
-                descriptor_configuration = vnfd.get("vnf-configuration", {}).get("config-primitive")
+                if indata.get("vdu_id"):
+                    vdud = check_valid_vdu(vnfd, indata["vdu_id"])
+                    descriptor_configuration = vdud.get("vdu-configuration", {}).get("config-primitive")
+                else:
+                    descriptor_configuration = vnfd.get("vnf-configuration", {}).get("config-primitive")
             else:  # use a NSD
                 descriptor_configuration = nsd.get("ns-configuration", {}).get("config-primitive")
             # check primitive
-- 
2.17.1


From ea97c04d6d6d9cbf61d03bebec7ed17e07919ed3 Mon Sep 17 00:00:00 2001
From: tierno 
Date: Fri, 13 Sep 2019 09:44:42 +0000
Subject: [PATCH 04/16] bug 839: fix error looking for nsi using a nst

Change-Id: Iba0af5eccc16255eb408531fd5b0da3637651274
Signed-off-by: tierno 
---
 osm_nbi/__init__.py          | 4 ++--
 osm_nbi/descriptor_topics.py | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/osm_nbi/__init__.py b/osm_nbi/__init__.py
index 1e02f66..d93de2d 100644
--- a/osm_nbi/__init__.py
+++ b/osm_nbi/__init__.py
@@ -12,5 +12,5 @@
 # under the License.
 ##
 
-version = '6.0.2.post1'
-version_date = '2018-08-30'
+version = '6.0.2.post3'
+version_date = '2018-09-13'
diff --git a/osm_nbi/descriptor_topics.py b/osm_nbi/descriptor_topics.py
index 27b51a1..0c9a3a4 100644
--- a/osm_nbi/descriptor_topics.py
+++ b/osm_nbi/descriptor_topics.py
@@ -854,7 +854,7 @@ class NstTopic(DescriptorTopic):
             return
         # Get Network Slice Template from Database
         _filter = self._get_project_filter(session)
-        _filter["nst-id"] = _id
+        _filter["_admin.nst-id"] = _id
         if self.db.get_list("nsis", _filter):
             raise EngineException("there is at least one Netslice Instance using this descriptor",
                                   http_code=HTTPStatus.CONFLICT)
-- 
2.17.1


From b7e42f04a0e421697ea9107040c4effcf44bbb2e Mon Sep 17 00:00:00 2001
From: tierno 
Date: Tue, 3 Sep 2019 07:24:17 +0000
Subject: [PATCH 05/16] change cherrypy version to 18.1.2

Change-Id: Id9a2e516e253ccf174d2ac7849178da7bdac0576
Signed-off-by: tierno 
---
 Dockerfile.local                | 2 +-
 debian/python3-osm-nbi.postinst | 2 +-
 osm_nbi/__init__.py             | 2 +-
 requirements.txt                | 2 +-
 setup.py                        | 2 +-
 5 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/Dockerfile.local b/Dockerfile.local
index 3a31273..e72aefa 100644
--- a/Dockerfile.local
+++ b/Dockerfile.local
@@ -21,7 +21,7 @@ WORKDIR /app/NBI
 RUN apt-get update && apt-get install -y git python3 python3-jsonschema \
     python3-pymongo python3-yaml python3-pip python3-keystoneclient \
     && pip3 install pip==9.0.3 \
-    && pip3 install aiokafka aiohttp cherrypy==18.0.0 keystoneauth1 requests \
+    && pip3 install aiokafka aiohttp cherrypy==18.1.2 keystoneauth1 requests \
     && mkdir -p /app/storage/kafka && mkdir -p /app/log 
 
 # OSM_COMMON
diff --git a/debian/python3-osm-nbi.postinst b/debian/python3-osm-nbi.postinst
index 5ff9ec7..769c250 100755
--- a/debian/python3-osm-nbi.postinst
+++ b/debian/python3-osm-nbi.postinst
@@ -21,7 +21,7 @@ echo "POST INSTALL OSM-NBI"
 echo "Installing python dependencies via pip..."
 
 pip3 install pip==9.0.3
-pip3 install cherrypy==18.0.0
+pip3 install cherrypy==18.1.2
 pip3 install keystoneauth1
 
 #Creation of log folder
diff --git a/osm_nbi/__init__.py b/osm_nbi/__init__.py
index d93de2d..3c38124 100644
--- a/osm_nbi/__init__.py
+++ b/osm_nbi/__init__.py
@@ -12,5 +12,5 @@
 # under the License.
 ##
 
-version = '6.0.2.post3'
+version = '6.0.2.post4'
 version_date = '2018-09-13'
diff --git a/requirements.txt b/requirements.txt
index 712bfe4..64c7079 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -10,7 +10,7 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
-CherryPy==18.0.0
+CherryPy==18.1.2
 jsonschema
 PyYAML==3.*
 python-keystoneclient
diff --git a/setup.py b/setup.py
index 8946575..ad7cdd5 100644
--- a/setup.py
+++ b/setup.py
@@ -52,7 +52,7 @@ setup(
         'git+https://osm.etsi.org/gerrit/osm/common.git#egg=osm-common'
     ],
     install_requires=[
-        'CherryPy==18.0.0',
+        'CherryPy==18.1.2',
         'osm-common',
         'jsonschema',
         'PyYAML',
-- 
2.17.1


From 982da4e03c39e679919c1cb36fc00e9b8bd5d450 Mon Sep 17 00:00:00 2001
From: tierno 
Date: Tue, 3 Sep 2019 11:51:55 +0000
Subject: [PATCH 06/16] bug 838 fix vnfd obtained from nsr reference

Change-Id: I1b2934ea196d3db19717e68874de6fc59d2c9288
Signed-off-by: tierno 
---
 osm_nbi/__init__.py        |  2 +-
 osm_nbi/instance_topics.py | 23 ++++++++++++++---------
 2 files changed, 15 insertions(+), 10 deletions(-)

diff --git a/osm_nbi/__init__.py b/osm_nbi/__init__.py
index 3c38124..8e72edf 100644
--- a/osm_nbi/__init__.py
+++ b/osm_nbi/__init__.py
@@ -12,5 +12,5 @@
 # under the License.
 ##
 
-version = '6.0.2.post4'
+version = '6.0.2.post5'
 version_date = '2018-09-13'
diff --git a/osm_nbi/instance_topics.py b/osm_nbi/instance_topics.py
index e36a723..d3a3b41 100644
--- a/osm_nbi/instance_topics.py
+++ b/osm_nbi/instance_topics.py
@@ -426,22 +426,27 @@ class NsLcmOpTopic(BaseTopic):
         :param indata: descriptor with the parameters of the operation
         :return: None
         """
-        vnfds = {}
+        vnf_member_index_to_vnfd = {}  # map between vnf_member_index to vnf descriptor.
         vim_accounts = []
         wim_accounts = []
         nsd = nsr["nsd"]
 
         def check_valid_vnf_member_index(member_vnf_index):
-            # TODO change to vnfR
-            for vnf in nsd["constituent-vnfd"]:
-                if member_vnf_index == vnf["member-vnf-index"]:
-                    vnfd_id = vnf["vnfd-id-ref"]
-                    if vnfd_id not in vnfds:
-                        vnfds[vnfd_id] = self.db.get_one("vnfds", {"id": vnfd_id})
-                    return vnfds[vnfd_id]
-            else:
+            # Obtain vnf descriptor. The vnfr is used to get the vnfd._id used for this member_vnf_index
+            if vnf_member_index_to_vnfd.get(member_vnf_index):
+                return vnf_member_index_to_vnfd[member_vnf_index]
+            vnfr = self.db.get_one("vnfrs",
+                                   {"nsr-id-ref": nsr["_id"], "member-vnf-index-ref": member_vnf_index},
+                                   fail_on_empty=False)
+            if not vnfr:
                 raise EngineException("Invalid parameter member_vnf_index='{}' is not one of the "
                                       "nsd:constituent-vnfd".format(member_vnf_index))
+            vnfd = self.db.get_one("vnfds", {"_id": vnfr["vnfd-id"]}, fail_on_empty=False)
+            if not vnfd:
+                raise EngineException("vnfd id={} has been deleted!. Operation cannot be performed".
+                                      format(vnfr["vnfd-id"]))
+            vnf_member_index_to_vnfd[member_vnf_index] = vnfd  # add to cache, avoiding a later look for
+            return vnfd
 
         def check_valid_vdu(vnfd, vdu_id):
             for vdud in get_iterable(vnfd.get("vdu")):
-- 
2.17.1


From 9742f1df203a8f47672fe5714044b06dd9e3ec59 Mon Sep 17 00:00:00 2001
From: delacruzramo 
Date: Wed, 5 Jun 2019 13:06:47 +0200
Subject: [PATCH 07/16] Tests for Authentication framework
 (Projects-Users-Roles)

Change-Id: I8e6cc93ffb9913c4ea3f74679b0a6e87e847de64
Signed-off-by: delacruzramo 
---
 osm_nbi/tests/test.py | 507 ++++++++++++++++++++++++++++++++----------
 1 file changed, 394 insertions(+), 113 deletions(-)

diff --git a/osm_nbi/tests/test.py b/osm_nbi/tests/test.py
index c70e384..dd3c796 100755
--- a/osm_nbi/tests/test.py
+++ b/osm_nbi/tests/test.py
@@ -26,6 +26,7 @@ from time import sleep
 from random import randint
 import os
 from sys import stderr
+from uuid import uuid4
 
 __author__ = "Alfonso Tierno, alfonso.tiernosepulveda@telefonica.com"
 __date__ = "$2018-03-01$"
@@ -454,102 +455,225 @@ class TestUsersProjects:
     @staticmethod
     def run(engine, test_osm, manual_check, test_params=None):
         engine.set_test_name("UserProject")
-        engine.get_autorization()
-        engine.test("Create project non admin", "POST", "/admin/v1/projects", headers_json, {"name": "P1"},
-                    (201, 204), {"Location": "/admin/v1/projects/", "Content-Type": "application/json"}, "json")
-        engine.test("Create project admin", "POST", "/admin/v1/projects", headers_json,
-                    {"name": "Padmin", "admin": True}, (201, 204),
-                    {"Location": "/admin/v1/projects/", "Content-Type": "application/json"}, "json")
-        engine.test("Create project bad format", "POST", "/admin/v1/projects", headers_json, {"name": 1}, (400, 422),
-                    r_header_json, "json")
-        engine.test("Create user with bad project", "POST", "/admin/v1/users", headers_json,
-                    {"username": "U1", "projects": ["P1", "P2", "Padmin"], "password": "pw1"}, 409,
-                    r_header_json, "json")
-        engine.test("Create user with bad project and force", "POST", "/admin/v1/users?FORCE=True", headers_json,
-                    {"username": "U1", "projects": ["P1", "P2", "Padmin"], "password": "pw1"}, 201,
-                    {"Location": "/admin/v1/users/", "Content-Type": "application/json"}, "json")
-        engine.test("Create user 2", "POST", "/admin/v1/users", headers_json,
-                    {"username": "U2", "projects": ["P1"], "password": "pw2"}, 201,
-                    {"Location": "/admin/v1/users/", "Content-Type": "application/json"}, "json")
-        engine.test("Edit user U1, delete  P2 project", "PATCH", "/admin/v1/users/U1", headers_json,
-                    {"projects": {"$'P2'": None}}, 204, None, None)
-        res = engine.test("Check user U1, contains the right projects", "GET", "/admin/v1/users/U1",
-                          headers_json, None, 200, None, json)
-        if res:
-            u1 = res.json()
-            # print(u1)
-            expected_projects = ["P1", "Padmin"]
-            if u1["projects"] != expected_projects:
-                logger.error("User content projects '{}' different than expected '{}'. Edition has not done"
-                             " properly".format(u1["projects"], expected_projects))
-                engine.failed_tests += 1
+        # backend = test_params.get("backend") if test_params else None   # UNUSED
 
-        engine.test("Edit user U1, set Padmin as default project", "PUT", "/admin/v1/users/U1", headers_json,
-                    {"projects": {"$'Padmin'": None, "$+[0]": "Padmin"}}, 204, None, None)
-        res = engine.test("Check user U1, contains the right projects", "GET", "/admin/v1/users/U1",
-                          headers_json, None, 200, None, json)
-        if res:
-            u1 = res.json()
-            # print(u1)
-            expected_projects = ["Padmin", "P1"]
-            if u1["projects"] != expected_projects:
-                logger.error("User content projects '{}' different than expected '{}'. Edition has not done"
-                             " properly".format(u1["projects"], expected_projects))
-                engine.failed_tests += 1
-
-        engine.test("Edit user U1, change password", "PATCH", "/admin/v1/users/U1", headers_json,
-                    {"password": "pw1_new"}, 204, None, None)
+        # Initialisation
+        p1 = p2 = p3 = None
+        padmin = pbad = None
+        u1 = u2 = u3 = u4 = None
 
-        engine.test("Change to project P1 non existing", "POST", "/admin/v1/tokens/", headers_json,
-                    {"project_id": "P1"}, 401, r_header_json, "json")
+        engine.get_autorization()
 
-        res = engine.test("Change to user U1 project P1", "POST", "/admin/v1/tokens", headers_json,
-                          {"username": "U1", "password": "pw1_new", "project_id": "P1"}, (200, 201),
-                          r_header_json, "json")
+        res = engine.test("Create project non admin 1", "POST", "/admin/v1/projects", headers_json, {"name": "P1"},
+                          (201, 204), {"Location": "/admin/v1/projects/", "Content-Type": "application/json"}, "json")
+        p1 = engine.last_id if res else None
+
+        res = engine.test("Create project admin", "POST", "/admin/v1/projects", headers_json,
+                          {"name": "Padmin", "admin": True}, (201, 204),
+                          {"Location": "/admin/v1/projects/", "Content-Type": "application/json"}, "json")
+        padmin = engine.last_id if res else None
+
+        res = engine.test("Create project bad format", "POST", "/admin/v1/projects", headers_json, {"name": 1},
+                          (400, 422), r_header_json, "json")
+        pbad = engine.last_id if res else None
+
+        res = engine.test("Get project admin role", "GET", "/admin/v1/roles?name=project_admin", headers_json, {},
+                          (200), {"Content-Type": "application/json"}, "json")
+        rpa = res.json()[0]["_id"] if res else None
+        res = engine.test("Get project user role", "GET", "/admin/v1/roles?name=project_user", headers_json, {},
+                          (200), {"Content-Type": "application/json"}, "json")
+        rpu = res.json()[0]["_id"] if res else None
+        res = engine.test("Get system admin role", "GET", "/admin/v1/roles?name=system_admin", headers_json, {},
+                          (200), {"Content-Type": "application/json"}, "json")
+        rsa = res.json()[0]["_id"] if res else None
+
+        data = {"username": "U1", "password": "pw1"}
+        p2 = uuid4().hex
+        data["project_role_mappings"] = [
+            {"project": p1, "role": rpa},
+            {"project": p2, "role": rpa},
+            {"project": padmin, "role": rpu}
+        ]
+        rc = 201
+        xhd = {"Location": "/admin/v1/users/", "Content-Type": "application/json"}
+        res = engine.test("Create user with bad project and force", "POST", "/admin/v1/users?FORCE=True", headers_json,
+                          data, rc, xhd, "json")
         if res:
-            response = res.json()
-            engine.set_header({"Authorization": "Bearer {}".format(response["id"])})
-
-        engine.test("Edit user projects non admin", "PUT", "/admin/v1/users/U1", headers_json,
-                    {"projects": {"$'P1'": None}}, 401, r_header_json, "json")
-        engine.test("Add new project non admin", "POST", "/admin/v1/projects", headers_json,
-                    {"name": "P2"}, 401, r_header_json, "json")
-        engine.test("Add new user non admin", "POST", "/admin/v1/users", headers_json,
-                    {"username": "U3", "projects": ["P1"], "password": "pw3"}, 401,
-                    r_header_json, "json")
+            u1 = engine.last_id
+        else:
+            # User is created sometimes even though an exception is raised
+            res = engine.test("Get user U1", "GET", "/admin/v1/users?username=U1", headers_json, {},
+                              (200), {"Content-Type": "application/json"}, "json")
+            u1 = res.json()[0]["_id"] if res else None
+
+        data = {"username": "U2", "password": "pw2"}
+        data["project_role_mappings"] = [{"project": p1, "role": rpa}, {"project": padmin, "role": rsa}]
+        res = engine.test("Create user 2", "POST", "/admin/v1/users", headers_json,
+                          data, 201, {"Location": "/admin/v1/users/", "Content-Type": "application/json"}, "json")
+        u2 = engine.last_id if res else None
+
+        if u1:
+            ftt = "project_role_mappings"
+            xpr = [{"project": p1, "role": rpa}, {"project": padmin, "role": rpu}]
+            data = {ftt: xpr}
+            engine.test("Edit user U1, delete  P2 project", "PATCH", "/admin/v1/users/"+u1, headers_json,
+                        data, 204, None, None)
+            res = engine.test("Check user U1, contains the right projects", "GET", "/admin/v1/users/"+u1,
+                              headers_json, None, 200, None, json)
+            if res:
+                rj = res.json()
+                xpr[0]["project_name"] = "P1"
+                xpr[0]["role_name"] = "project_admin"
+                xpr[1]["project_name"] = "Padmin"
+                xpr[1]["role_name"] = "project_user"
+                ok = True
+                for pr in rj[ftt]:
+                    if pr not in xpr:
+                        ok = False
+                for pr in xpr:
+                    if pr not in rj[ftt]:
+                        ok = False
+                if not ok:
+                    logger.error("User {} '{}' are different than expected '{}'. Edition was not done properly"
+                                 .format(ftt, rj[ftt], xpr))
+                    engine.failed_tests += 1
 
-        res = engine.test("Change to user U1 project Padmin", "POST", "/admin/v1/tokens", headers_json,
-                          {"project_id": "Padmin"}, (200, 201), r_header_json, "json")
-        if res:
-            response = res.json()
-            engine.set_header({"Authorization": "Bearer {}".format(response["id"])})
+        p2 = None   # To prevent deletion attempts
+
+        # Add a test of 'default project' for Keystone?
+
+        if u2:
+            engine.test("Edit user U2, change password", "PUT", "/admin/v1/users/"+u2, headers_json,
+                        {"password": "pw2_new"}, 204, None, None)
+
+        if p1:
+            engine.test("Change to project P1 non existing", "POST", "/admin/v1/tokens/", headers_json,
+                        {"project_id": p1}, 401, r_header_json, "json")
+
+        if u2 and p1:
+            res = engine.test("Change to user U2 project P1", "POST", "/admin/v1/tokens", headers_json,
+                              {"username": "U2", "password": "pw2_new", "project_id": "P1"}, (200, 201),
+                              r_header_json, "json")
+            if res:
+                rj = res.json()
+                engine.set_header({"Authorization": "Bearer {}".format(rj["id"])})
+
+                engine.test("Edit user projects non admin", "PUT", "/admin/v1/users/U1", headers_json,
+                            {"remove_project_role_mappings": [{"project": "P1", "role": None}]},
+                            401, r_header_json, "json")
+
+                res = engine.test("Add new project non admin", "POST", "/admin/v1/projects", headers_json,
+                                  {"name": "P2"}, 401, r_header_json, "json")
+                if res is None or res.status_code == 201:
+                    # The project has been created even though it shouldn't
+                    res = engine.test("Get project P2", "GET", "/admin/v1/projects/P2", headers_json, None,
+                                      200, r_header_json, "json")
+                    p2 = res.json()["_id"] if res else None
+
+                if p1:
+                    data = {"username": "U3", "password": "pw3"}
+                    data["project_role_mappings"] = [{"project": p1, "role": rpu}]
+                    res = engine.test("Add new user non admin", "POST", "/admin/v1/users", headers_json,
+                                      data, 401, r_header_json, "json")
+                    if res is None or res.status_code == 201:
+                        # The user has been created even though it shouldn't
+                        res = engine.test("Get user U3", "GET", "/admin/v1/users/U3", headers_json, None,
+                                          200, r_header_json, "json")
+                        u3 = res.json()["_id"] if res else None
+                else:
+                    u3 = None
+
+                if padmin:
+                    res = engine.test("Change to user U2 project Padmin", "POST", "/admin/v1/tokens", headers_json,
+                                      {"project_id": "Padmin"},   # Caused a Keystone authentication error
+                                      # {"username": "U2", "password": "pw2_new", "project_id": "Padmin"},
+                                      (200, 201), r_header_json, "json")
+                    if res:
+                        rj = res.json()
+                        engine.set_header({"Authorization": "Bearer {}".format(rj["id"])})
+
+                        res = engine.test("Add new project admin", "POST", "/admin/v1/projects", headers_json,
+                                          {"name": "P3"}, (201, 204),
+                                          {"Location": "/admin/v1/projects/", "Content-Type": "application/json"},
+                                          "json")
+                        p3 = engine.last_id if res else None
+
+                        if p1:
+                            data = {"username": "U4", "password": "pw4"}
+                            data["project_role_mappings"] = [{"project": p1, "role": rpa}]
+                            res = engine.test("Add new user admin", "POST", "/admin/v1/users", headers_json,
+                                              data, (201, 204),
+                                              {"Location": "/admin/v1/users/", "Content-Type": "application/json"},
+                                              "json")
+                            u4 = engine.last_id if res else None
+                        else:
+                            u4 = None
+
+                        if u4 and p3:
+                            data = {"project_role_mappings": [{"project": p3, "role": rpa}]}
+                            engine.test("Edit user projects admin", "PUT", "/admin/v1/users/U4", headers_json,
+                                        data, 204, None, None)
+                            # Project is deleted even though it shouldn't - PROVISIONAL?
+                            res = engine.test("Delete project P3 conflict", "DELETE", "/admin/v1/projects/"+p3,
+                                              headers_json, None, 409, None, None)
+                            if res and res.status_code in (200, 204):
+                                p3 = None
+                            if p3:
+                                res = engine.test("Delete project P3 forcing", "DELETE",
+                                                  "/admin/v1/projects/"+p3+"?FORCE=True", headers_json, None, 204,
+                                                  None, None)
+                                if res and res.status_code in (200, 204):
+                                    p3 = None
+
+                        if u2:
+                            res = engine.test("Delete user U2. Conflict deleting own user", "DELETE",
+                                              "/admin/v1/users/"+u2, headers_json, None, 409, r_header_json, "json")
+                            if res is None or res.status_code in (200, 204):
+                                u2 = None
+                        if u4:
+                            res = engine.test("Delete user U4", "DELETE", "/admin/v1/users/"+u4, headers_json, None,
+                                              204, None, None)
+                            if res and res.status_code in (200, 204):
+                                u4 = None
+                        if p3:
+                            res = engine.test("Delete project P3", "DELETE", "/admin/v1/projects/"+p3, headers_json,
+                                              None, 204, None, None)
+                            if res and res.status_code in (200, 204):
+                                p3 = None
+
+                if u3:
+                    res = engine.test("Delete user U3", "DELETE", "/admin/v1/users/"+u3, headers_json, None,
+                                      204, None, None)
+                    if res:
+                        u3 = None
 
-        engine.test("Add new project admin", "POST", "/admin/v1/projects", headers_json, {"name": "P2"},
-                    (201, 204), {"Location": "/admin/v1/projects/", "Content-Type": "application/json"}, "json")
-        engine.test("Add new user U3 admin", "POST", "/admin/v1/users",
-                    headers_json, {"username": "U3", "projects": ["P2"], "password": "pw3"}, (201, 204),
-                    {"Location": "/admin/v1/users/", "Content-Type": "application/json"}, "json")
-        engine.test("Edit user projects admin", "PUT", "/admin/v1/users/U3", headers_json,
-                    {"projects": ["P2"]}, 204, None, None)
-
-        engine.test("Delete project P2 conflict", "DELETE", "/admin/v1/projects/P2", headers_json, None, 409,
-                    r_header_json, "json")
-        engine.test("Delete project P2 forcing", "DELETE", "/admin/v1/projects/P2?FORCE=True", headers_json,
-                    None, 204, None, None)
-
-        engine.test("Delete user U1. Conflict deleting own user", "DELETE", "/admin/v1/users/U1", headers_json,
-                    None, 409, r_header_json, "json")
-        engine.test("Delete user U2", "DELETE", "/admin/v1/users/U2", headers_json, None, 204, None, None)
-        engine.test("Delete user U3", "DELETE", "/admin/v1/users/U3", headers_json, None, 204, None, None)
         # change to admin
         engine.remove_authorization()   # To force get authorization
         engine.get_autorization()
-        engine.test("Delete user U1 by Name", "DELETE", "/admin/v1/users/U1", headers_json, None, 204, None, None)
-        engine.test("Delete project P1 by Name", "DELETE", "/admin/v1/projects/P1", headers_json, None, 204, None, None)
-        engine.test("Delete project Padmin by Name", "DELETE", "/admin/v1/projects/Padmin", headers_json, None, 204,
-                    None, None)
+        if u1:
+            engine.test("Delete user U1", "DELETE", "/admin/v1/users/"+u1, headers_json, None, 204, None, None)
+        if u2:
+            engine.test("Delete user U2", "DELETE", "/admin/v1/users/"+u2, headers_json, None, 204, None, None)
+        if u3:
+            engine.test("Delete user U3", "DELETE", "/admin/v1/users/"+u3, headers_json, None, 204, None, None)
+        if u4:
+            engine.test("Delete user U4", "DELETE", "/admin/v1/users/"+u4, headers_json, None, 204, None, None)
+        if p1:
+            engine.test("Delete project P1", "DELETE", "/admin/v1/projects/"+p1, headers_json, None, 204, None, None)
+        if p2:
+            engine.test("Delete project P2", "DELETE", "/admin/v1/projects/"+p2, headers_json, None, 204, None, None)
+        if p3:
+            engine.test("Delete project P3", "DELETE", "/admin/v1/projects/"+p3, headers_json, None, 204, None, None)
+        if padmin:
+            engine.test("Delete project Padmin", "DELETE", "/admin/v1/projects/"+padmin, headers_json, None, 204,
+                        None, None)
+        if pbad:
+            engine.test("Delete bad project", "DELETE", "/admin/v1/projects/"+pbad, headers_json, None, 204,
+                        None, None)
 
         # BEGIN New Tests - Addressing Projects/Users by Name/ID
+        pid1 = pid2 = None
+        uid1 = uid2 = None
         res = engine.test("Create new project P1", "POST", "/admin/v1/projects", headers_json, {"name": "P1"},
                           201, {"Location": "/admin/v1/projects/", "Content-Type": "application/json"}, "json")
         if res:
@@ -560,39 +684,90 @@ class TestUsersProjects:
         if res:
             pid2 = res.json()["id"]
             # print("# pid =", pid2)
-        res = engine.test("Create new user U1", "POST", "/admin/v1/users", headers_json,
-                          {"username": "U1", "projects": ["P1"], "password": "pw1"}, 201,
+        data = {"username": "U1", "password": "pw1"}
+        data["project_role_mappings"] = [{"project": pid1, "role": rpu}]
+        res = engine.test("Create new user U1", "POST", "/admin/v1/users", headers_json, data, 201,
                           {"Location": "/admin/v1/users/", "Content-Type": "application/json"}, "json")
         if res:
             uid1 = res.json()["id"]
             # print("# uid =", uid1)
-        res = engine.test("Create new user U2", "POST", "/admin/v1/users", headers_json,
-                          {"username": "U2", "projects": ["P2"], "password": "pw2"}, 201,
+        data = {"username": "U2", "password": "pw2"}
+        data["project_role_mappings"] = [{"project": pid2, "role": rpu}]
+        res = engine.test("Create new user U2", "POST", "/admin/v1/users", headers_json, data, 201,
                           {"Location": "/admin/v1/users/", "Content-Type": "application/json"}, "json")
         if res:
             uid2 = res.json()["id"]
             # print("# uid =", uid2)
-        engine.test("Get Project P1 by Name", "GET", "/admin/v1/projects/P1", headers_json, None, 200, None, "json")
-        engine.test("Get Project P1 by ID", "GET", "/admin/v1/projects/"+pid1, headers_json, None, 200, None, "json")
-        engine.test("Get User U1 by Name", "GET", "/admin/v1/users/U1", headers_json, None, 200, None, "json")
-        engine.test("Get User U1 by ID", "GET", "/admin/v1/users/"+uid1, headers_json, None, 200, None, "json")
-        engine.test("Rename Project P1 by Name", "PUT", "/admin/v1/projects/P1", headers_json,
-                    {"name": "P3"}, 204, None, None)
-        engine.test("Rename Project P2 by ID", "PUT", "/admin/v1/projects/"+pid2, headers_json,
-                    {"name": "P4"}, 204, None, None)
-        engine.test("Rename User U1 by Name", "PUT", "/admin/v1/users/U1", headers_json,
-                    {"username": "U3"}, 204, None, None)
-        engine.test("Rename User U2 by ID", "PUT", "/admin/v1/users/"+uid2, headers_json,
-                    {"username": "U4"}, 204, None, None)
-        engine.test("Get Project P1 by new Name", "GET", "/admin/v1/projects/P3", headers_json, None, 200, None, "json")
-        engine.test("Get User U1 by new Name", "GET", "/admin/v1/users/U3", headers_json, None, 200, None, "json")
-        engine.test("Delete User U1 by Name", "DELETE", "/admin/v1/users/U3", headers_json, None, 204, None, None)
-        engine.test("Delete User U2 by ID", "DELETE", "/admin/v1/users/"+uid2, headers_json, None, 204, None, None)
-        engine.test("Delete Project P1 by Name", "DELETE", "/admin/v1/projects/P3", headers_json, None, 204, None,
-                    None)
-        engine.test("Delete Project P2 by ID", "DELETE", "/admin/v1/projects/"+pid2, headers_json, None, 204, None,
-                    None)
+        if pid1:
+            engine.test("Get Project P1 by Name", "GET", "/admin/v1/projects/P1", headers_json, None,
+                        200, None, "json")
+            engine.test("Get Project P1 by ID", "GET", "/admin/v1/projects/"+pid1, headers_json, None,
+                        200, None, "json")
+        if uid1:
+            engine.test("Get User U1 by Name", "GET", "/admin/v1/users/U1", headers_json, None, 200, None, "json")
+            engine.test("Get User U1 by ID", "GET", "/admin/v1/users/"+uid1, headers_json, None, 200, None, "json")
+        if pid1:
+            res = engine.test("Rename Project P1 by Name", "PUT", "/admin/v1/projects/P1", headers_json,
+                              {"name": "P3"}, 204, None, None)
+            if res:
+                engine.test("Get Project P1 by new Name", "GET", "/admin/v1/projects/P3", headers_json, None,
+                            200, None, "json")
+        if pid2:
+            res = engine.test("Rename Project P2 by ID", "PUT", "/admin/v1/projects/"+pid2, headers_json,
+                              {"name": "P4"}, 204, None, None)
+            if res:
+                engine.test("Get Project P2 by new Name", "GET", "/admin/v1/projects/P4", headers_json, None,
+                            200, None, "json")
+
+        if uid1:
+            res = engine.test("Rename User U1 by Name", "PUT", "/admin/v1/users/U1", headers_json,
+                              {"username": "U3"}, 204, None, None)
+            if res:
+                engine.test("Get User U1 by new Name", "GET", "/admin/v1/users/U3", headers_json, None,
+                            200, None, "json")
+
+        if uid2:
+            res = engine.test("Rename User U2 by ID", "PUT", "/admin/v1/users/"+uid2, headers_json,
+                              {"username": "U4"}, 204, None, None)
+            if res:
+                engine.test("Get User U2 by new Name", "GET", "/admin/v1/users/U4", headers_json, None,
+                            200, None, "json")
+        if uid1:
+            res = engine.test("Delete User U1 by Name", "DELETE", "/admin/v1/users/U3", headers_json, None,
+                              204, None, None)
+            if res:
+                uid1 = None
+
+        if uid2:
+            res = engine.test("Delete User U2 by ID", "DELETE", "/admin/v1/users/"+uid2, headers_json, None,
+                              204, None, None)
+            if res:
+                uid2 = None
+
+        if pid1:
+            res = engine.test("Delete Project P1 by Name", "DELETE", "/admin/v1/projects/P3", headers_json, None,
+                              204, None, None)
+            if res:
+                pid1 = None
+
+        if pid2:
+            res = engine.test("Delete Project P2 by ID", "DELETE", "/admin/v1/projects/"+pid2, headers_json, None,
+                              204, None, None)
+            if res:
+                pid2 = None
+
         # END New Tests - Addressing Projects/Users by Name
+
+        # CLEANUP
+        if pid1:
+            engine.test("Delete Project P1", "DELETE", "/admin/v1/projects/"+pid1, headers_json, None, 204, None, None)
+        if pid2:
+            engine.test("Delete Project P2", "DELETE", "/admin/v1/projects/"+pid2, headers_json, None, 204, None, None)
+        if uid1:
+            engine.test("Delete User U1", "DELETE", "/admin/v1/users/"+uid1, headers_json, None, 204, None, None)
+        if uid2:
+            engine.test("Delete User U2", "DELETE", "/admin/v1/users/"+uid2, headers_json, None, 204, None, None)
+
         engine.remove_authorization()   # To finish
 
 
@@ -671,10 +846,11 @@ class TestProjectsDescriptors:
         res = engine.test("List VNFD of admin project", "GET",
                           "/vnfpkgm/v1/vnf_packages?ADMIN={}".format(project_admin_id),
                           headers_json, None, 200, r_header_json, "json")
-        response = res.json()
-        if len(response) != 3:
-            logger.error("Only 3 vnfds should be present for project Padmin. {} listed".format(len(response)))
-            engine.failed_tests += 1
+        if res:
+            response = res.json()
+            if len(response) != 3:
+                logger.error("Only 3 vnfds should be present for project Padmin. {} listed".format(len(response)))
+                engine.failed_tests += 1
 
         # Get Public vnfds
         engine.test("Get VNFD public descriptors", "GET", "/vnfpkgm/v1/vnf_packages/{}".format(vnfd_ids[1]),
@@ -2407,6 +2583,110 @@ class TestNetSliceInstances:
                        headers_yaml, None, 204, None, 0)
 
 
+class TestAuthentication:
+    description = "Test Authentication"
+
+    @staticmethod
+    def run(engine, test_osm, manual_check, test_params=None):
+        engine.set_test_name("Authentication")
+        # backend = test_params.get("backend") if test_params else None   # UNUSED
+
+        admin_project_id = test_project_id = None
+        project_admin_role_id = project_user_role_id = None
+        test_user_id = empty_user_id = None
+        default_role_id = empty_role_id = token_role_id = None
+
+        engine.get_autorization()
+
+        # GET
+        engine.test("Get tokens", "GET", "/admin/v1/tokens", headers_json, {},
+                    (200), {"Content-Type": "application/json"}, "json")
+        engine.test("Get projects", "GET", "/admin/v1/projects", headers_json, {},
+                    (200), {"Content-Type": "application/json"}, "json")
+        engine.test("Get users", "GET", "/admin/v1/users", headers_json, {},
+                    (200), {"Content-Type": "application/json"}, "json")
+        engine.test("Get roles", "GET", "/admin/v1/roles", headers_json, {},
+                    (200), {"Content-Type": "application/json"}, "json")
+        res = engine.test("Get admin project", "GET", "/admin/v1/projects?name=admin", headers_json, {},
+                          (200), {"Content-Type": "application/json"}, "json")
+        admin_project_id = res.json()[0]["_id"] if res else None
+        res = engine.test("Get project admin role", "GET", "/admin/v1/roles?name=project_admin", headers_json, {},
+                          (200), {"Content-Type": "application/json"}, "json")
+        project_admin_role_id = res.json()[0]["_id"] if res else None
+        res = engine.test("Get project user role", "GET", "/admin/v1/roles?name=project_user", headers_json, {},
+                          (200), {"Content-Type": "application/json"}, "json")
+        project_user_role_id = res.json()[0]["_id"] if res else None
+
+        # POST
+        res = engine.test("Create test project", "POST", "/admin/v1/projects", headers_json, {"name": "test"},
+                          (201), {"Location": "/admin/v1/projects/", "Content-Type": "application/json"}, "json")
+        test_project_id = engine.last_id if res else None
+        res = engine.test("Create role without permissions", "POST", "/admin/v1/roles", headers_json, {"name": "empty"},
+                          (201), {"Content-Type": "application/json"}, "json")
+        empty_role_id = engine.last_id if res else None
+        res = engine.test("Create role with default permissions", "POST", "/admin/v1/roles", headers_json,
+                          {"name": "default", "permissions": {"default": True}},
+                          (201), {"Location": "/admin/v1/roles/", "Content-Type": "application/json"}, "json")
+        default_role_id = engine.last_id if res else None
+        res = engine.test("Create role with token permissions", "POST", "/admin/v1/roles", headers_json,
+                          {"name": "tokens", "permissions": {"tokens": True}},   # is default required ?
+                          (201), {"Location": "/admin/v1/roles/", "Content-Type": "application/json"}, "json")
+        token_role_id = engine.last_id if res else None
+        pr = "project-role mappings"
+        res = engine.test("Create user without "+pr, "POST", "/admin/v1/users", headers_json,
+                          {"username": "empty", "password": "empty"},
+                          201, {"Content-Type": "application/json"}, "json")
+        empty_user_id = engine.last_id if res else None
+        if admin_project_id and test_project_id and project_admin_role_id and project_user_role_id:
+            data = {"username": "test", "password": "test"}
+            data["project_role_mappings"] = [
+                {"project": test_project_id, "role": project_admin_role_id},
+                {"project": admin_project_id, "role": project_user_role_id}
+            ]
+            res = engine.test("Create user with "+pr, "POST", "/admin/v1/users", headers_json, data,
+                              (201), {"Content-Type": "application/json"}, "json")
+            test_user_id = engine.last_id if res else None
+
+        # PUT
+        if test_user_id:
+            engine.test("Modify test user's password", "PUT", "/admin/v1/users/"+test_user_id, headers_json,
+                        {"password": "password"},
+                        (204), {}, 0)
+        if empty_user_id and admin_project_id and test_project_id and project_admin_role_id and project_user_role_id:
+            data = {"project_role_mappings": [
+                {"project": test_project_id, "role": project_admin_role_id},
+                {"project": admin_project_id, "role": project_user_role_id}
+            ]}
+            engine.test("Modify empty user's "+pr, "PUT", "/admin/v1/users/"+empty_user_id,
+                        headers_json,
+                        data,
+                        (204), {}, 0)
+
+        # DELETE
+        if empty_user_id:
+            engine.test("Delete empty user", "DELETE", "/admin/v1/users/"+empty_user_id, headers_json, {},
+                        (204), {}, 0)
+        if test_user_id:
+            engine.test("Delete test user", "DELETE", "/admin/v1/users/"+test_user_id, headers_json, {},
+                        (204), {}, 0)
+        if empty_role_id:
+            engine.test("Delete empty role", "DELETE", "/admin/v1/roles/"+empty_role_id, headers_json, {},
+                        (204), {}, 0)
+        if default_role_id:
+            engine.test("Delete default role", "DELETE", "/admin/v1/roles/"+default_role_id, headers_json, {},
+                        (204), {}, 0)
+        if token_role_id:
+            engine.test("Delete token role", "DELETE", "/admin/v1/roles/"+token_role_id, headers_json, {},
+                        (204), {}, 0)
+        if test_project_id:
+            engine.test("Delete test project", "DELETE", "/admin/v1/projects/"+test_project_id, headers_json, {},
+                        (204), {}, 0)
+
+        # END Tests
+
+        engine.remove_authorization()   # To finish
+
+
 if __name__ == "__main__":
     global logger
     test = ""
@@ -2451,6 +2731,7 @@ if __name__ == "__main__":
             "Deploy-Slice-Instance": TestNetSliceInstances,
             "Deploy-SimpleCharm": TestDeploySimpleCharm,
             "Deploy-SimpleCharm2": TestDeploySimpleCharm2,
+            "Authentication": TestAuthentication,
         }
         test_to_do = []
         test_params = {}
-- 
2.17.1


From 32bab47c7fde8ae22795306723f3441ec544fa2b Mon Sep 17 00:00:00 2001
From: delacruzramo 
Date: Fri, 13 Sep 2019 12:24:22 +0200
Subject: [PATCH 08/16] NBI Quotas

Change-Id: I6d9762b3d8eb3610c00355971ac9a0964bc1b212
Signed-off-by: delacruzramo 
---
 osm_nbi/admin_topics.py      | 25 ++++++++++++-------------
 osm_nbi/authconn_keystone.py | 12 +++++++++---
 osm_nbi/base_topic.py        | 31 ++++++++++++++++++++++++++++++-
 osm_nbi/descriptor_topics.py | 23 +++++++++++++----------
 osm_nbi/engine.py            |  8 ++++----
 osm_nbi/instance_topics.py   | 30 ++++++++++++++++++------------
 osm_nbi/validation.py        | 11 +++++++++++
 7 files changed, 97 insertions(+), 43 deletions(-)

diff --git a/osm_nbi/admin_topics.py b/osm_nbi/admin_topics.py
index 5008c60..c198733 100644
--- a/osm_nbi/admin_topics.py
+++ b/osm_nbi/admin_topics.py
@@ -36,8 +36,8 @@ class UserTopic(BaseTopic):
     schema_edit = user_edit_schema
     multiproject = False
 
-    def __init__(self, db, fs, msg):
-        BaseTopic.__init__(self, db, fs, msg)
+    def __init__(self, db, fs, msg, auth):
+        BaseTopic.__init__(self, db, fs, msg, auth)
 
     @staticmethod
     def _get_project_filter(session):
@@ -130,8 +130,8 @@ class ProjectTopic(BaseTopic):
     schema_edit = project_edit_schema
     multiproject = False
 
-    def __init__(self, db, fs, msg):
-        BaseTopic.__init__(self, db, fs, msg)
+    def __init__(self, db, fs, msg, auth):
+        BaseTopic.__init__(self, db, fs, msg, auth)
 
     @staticmethod
     def _get_project_filter(session):
@@ -394,8 +394,8 @@ class UserTopicAuth(UserTopic):
     schema_edit = user_edit_schema
 
     def __init__(self, db, fs, msg, auth):
-        UserTopic.__init__(self, db, fs, msg)
-        self.auth = auth
+        UserTopic.__init__(self, db, fs, msg, auth)
+        # self.auth = auth
 
     def check_conflict_on_new(self, session, indata):
         """
@@ -707,8 +707,8 @@ class ProjectTopicAuth(ProjectTopic):
     schema_edit = project_edit_schema
 
     def __init__(self, db, fs, msg, auth):
-        ProjectTopic.__init__(self, db, fs, msg)
-        self.auth = auth
+        ProjectTopic.__init__(self, db, fs, msg, auth)
+        # self.auth = auth
 
     def check_conflict_on_new(self, session, indata):
         """
@@ -749,7 +749,7 @@ class ProjectTopicAuth(ProjectTopic):
                 raise EngineException("You cannot rename project 'admin'", http_code=HTTPStatus.CONFLICT)
 
             # Check that project name is not used, regardless keystone already checks this
-            if self.auth.get_project_list(filter_q={"name": project_name}):
+            if project_name and self.auth.get_project_list(filter_q={"name": project_name}):
                 raise EngineException("project '{}' is already used".format(project_name), HTTPStatus.CONFLICT)
 
     def check_conflict_on_del(self, session, _id, db_content):
@@ -888,8 +888,7 @@ class ProjectTopicAuth(ProjectTopic):
             self.check_conflict_on_edit(session, content, indata, _id=_id)
             self.format_on_edit(content, indata)
 
-            if "name" in indata:
-                content["name"] = indata["name"]
+            deep_update_rfc7396(content, indata)
             self.auth.update_project(content["_id"], content)
         except ValidationError as e:
             raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
@@ -903,8 +902,8 @@ class RoleTopicAuth(BaseTopic):
     multiproject = False
 
     def __init__(self, db, fs, msg, auth, ops):
-        BaseTopic.__init__(self, db, fs, msg)
-        self.auth = auth
+        BaseTopic.__init__(self, db, fs, msg, auth)
+        # self.auth = auth
         self.operations = ops
         # self.topic = "roles_operations" if isinstance(auth, AuthconnKeystone) else "roles"
 
diff --git a/osm_nbi/authconn_keystone.py b/osm_nbi/authconn_keystone.py
index a6cf0f0..f32cfe9 100644
--- a/osm_nbi/authconn_keystone.py
+++ b/osm_nbi/authconn_keystone.py
@@ -420,7 +420,8 @@ class AuthconnKeystone(Authconn):
             projects = [{
                 "name": project.name,
                 "_id": project.id,
-                "_admin": project.to_dict().get("_admin", {})  # TODO: REVISE
+                "_admin": project.to_dict().get("_admin", {}),  # TODO: REVISE
+                "quotas": project.to_dict().get("quotas", {}),  # TODO: REVISE
             } for project in projects]
 
             if filter_q and filter_q.get("_id"):
@@ -443,7 +444,9 @@ class AuthconnKeystone(Authconn):
         """
         try:
             result = self.keystone.projects.create(project_info["name"], self.project_domain_name,
-                                                   _admin=project_info["_admin"])
+                                                   _admin=project_info["_admin"],
+                                                   quotas=project_info.get("quotas", {})
+                                                   )
             return result.id
         except ClientException as e:
             # self.logger.exception("Error during project creation using keystone: {}".format(e))
@@ -478,7 +481,10 @@ class AuthconnKeystone(Authconn):
         :return: None
         """
         try:
-            self.keystone.projects.update(project_id, name=project_info["name"], _admin=project_info["_admin"])
+            self.keystone.projects.update(project_id, name=project_info["name"],
+                                          _admin=project_info["_admin"],
+                                          quotas=project_info.get("quotas", {})
+                                          )
         except ClientException as e:
             # self.logger.exception("Error during project update using keystone: {}".format(e))
             raise AuthconnOperationException("Error during project update using Keystone: {}".format(e))
diff --git a/osm_nbi/base_topic.py b/osm_nbi/base_topic.py
index 9688f60..c22833b 100644
--- a/osm_nbi/base_topic.py
+++ b/osm_nbi/base_topic.py
@@ -57,6 +57,8 @@ class BaseTopic:
     schema_edit = None  # to_override
     multiproject = True  # True if this Topic can be shared by several projects. Then it contains _admin.projects_read
 
+    default_quota = 500
+
     # Alternative ID Fields for some Topics
     alt_id_field = {
         "projects": "name",
@@ -64,11 +66,12 @@ class BaseTopic:
         "roles": "name"
     }
 
-    def __init__(self, db, fs, msg):
+    def __init__(self, db, fs, msg, auth):
         self.db = db
         self.fs = fs
         self.msg = msg
         self.logger = logging.getLogger("nbi.engine")
+        self.auth = auth
 
     @staticmethod
     def id_field(topic, value):
@@ -84,6 +87,29 @@ class BaseTopic:
             return {}
         return indata
 
+    def check_quota(self, session):
+        """
+        Check whether topic quota is exceeded by the given project
+        Used by relevant topics' 'new' function to decide whether or not creation of the new item should be allowed
+        :param projects: projects (tuple) for which quota should be checked
+        :param override: boolean. If true, don't raise ValidationError even though quota be exceeded
+        :return: None
+        :raise:
+            DbException if project not found
+            ValidationError if quota exceeded and not overridden
+        """
+        if session["force"] or session["admin"]:
+            return
+        projects = session["project_id"]
+        for project in projects:
+            proj = self.auth.get_project(project)
+            pid = proj["_id"]
+            quota = proj.get("quotas", {}).get(self.topic, self.default_quota)
+            count = self.db.count(self.topic, {"_admin.projects_read": pid})
+            if count >= quota:
+                name = proj["name"]
+                raise ValidationError("{} quota ({}) exceeded for project {} ({})".format(self.topic, quota, name, pid))
+
     def _validate_input_new(self, input, force=False):
         """
         Validates input user content for a new entry. It uses jsonschema. Some overrides will use pyangbind
@@ -349,6 +375,9 @@ class BaseTopic:
              op_id: operation id if this is asynchronous, None otherwise
         """
         try:
+            if self.multiproject:
+                self.check_quota(session)
+
             content = self._remove_envelop(indata)
 
             # Override descriptor with query string kwargs
diff --git a/osm_nbi/descriptor_topics.py b/osm_nbi/descriptor_topics.py
index 0c9a3a4..d01dc13 100644
--- a/osm_nbi/descriptor_topics.py
+++ b/osm_nbi/descriptor_topics.py
@@ -33,8 +33,8 @@ __author__ = "Alfonso Tierno "
 
 class DescriptorTopic(BaseTopic):
 
-    def __init__(self, db, fs, msg):
-        BaseTopic.__init__(self, db, fs, msg)
+    def __init__(self, db, fs, msg, auth):
+        BaseTopic.__init__(self, db, fs, msg, auth)
 
     def check_conflict_on_edit(self, session, final_content, edit_content, _id):
         super().check_conflict_on_edit(session, final_content, edit_content, _id)
@@ -121,6 +121,9 @@ class DescriptorTopic(BaseTopic):
         """
 
         try:
+            # Check Quota
+            self.check_quota(session)
+
             # _remove_envelop
             if indata:
                 if "userDefinedData" in indata:
@@ -394,8 +397,8 @@ class VnfdTopic(DescriptorTopic):
     topic = "vnfds"
     topic_msg = "vnfd"
 
-    def __init__(self, db, fs, msg):
-        DescriptorTopic.__init__(self, db, fs, msg)
+    def __init__(self, db, fs, msg, auth):
+        DescriptorTopic.__init__(self, db, fs, msg, auth)
 
     @staticmethod
     def _remove_envelop(indata=None):
@@ -652,8 +655,8 @@ class NsdTopic(DescriptorTopic):
     topic = "nsds"
     topic_msg = "nsd"
 
-    def __init__(self, db, fs, msg):
-        DescriptorTopic.__init__(self, db, fs, msg)
+    def __init__(self, db, fs, msg, auth):
+        DescriptorTopic.__init__(self, db, fs, msg, auth)
 
     @staticmethod
     def _remove_envelop(indata=None):
@@ -791,8 +794,8 @@ class NstTopic(DescriptorTopic):
     topic = "nsts"
     topic_msg = "nst"
 
-    def __init__(self, db, fs, msg):
-        DescriptorTopic.__init__(self, db, fs, msg)
+    def __init__(self, db, fs, msg, auth):
+        DescriptorTopic.__init__(self, db, fs, msg, auth)
 
     @staticmethod
     def _remove_envelop(indata=None):
@@ -866,8 +869,8 @@ class PduTopic(BaseTopic):
     schema_new = pdu_new_schema
     schema_edit = pdu_edit_schema
 
-    def __init__(self, db, fs, msg):
-        BaseTopic.__init__(self, db, fs, msg)
+    def __init__(self, db, fs, msg, auth):
+        BaseTopic.__init__(self, db, fs, msg, auth)
 
     @staticmethod
     def format_on_new(content, project_id=None, make_public=False):
diff --git a/osm_nbi/engine.py b/osm_nbi/engine.py
index 8233c90..67a9233 100644
--- a/osm_nbi/engine.py
+++ b/osm_nbi/engine.py
@@ -148,13 +148,13 @@ class Engine(object):
             self.write_lock = Lock()
             # create one class per topic
             for topic, topic_class in self.map_from_topic_to_class.items():
-                if self.auth and topic_class in (UserTopicAuth, ProjectTopicAuth):
-                    self.map_topic[topic] = topic_class(self.db, self.fs, self.msg, self.auth)
-                elif self.auth and topic_class == RoleTopicAuth:
+                # if self.auth and topic_class in (UserTopicAuth, ProjectTopicAuth):
+                #     self.map_topic[topic] = topic_class(self.db, self.fs, self.msg, self.auth)
+                if self.auth and topic_class == RoleTopicAuth:
                     self.map_topic[topic] = topic_class(self.db, self.fs, self.msg, self.auth,
                                                         self.operations)
                 else:
-                    self.map_topic[topic] = topic_class(self.db, self.fs, self.msg)
+                    self.map_topic[topic] = topic_class(self.db, self.fs, self.msg, self.auth)
             
             self.map_topic["pm_jobs"] = PmJobsTopic(config["prometheus"].get("host"), config["prometheus"].get("port"))
         except (DbException, FsException, MsgException) as e:
diff --git a/osm_nbi/instance_topics.py b/osm_nbi/instance_topics.py
index d3a3b41..8c084e4 100644
--- a/osm_nbi/instance_topics.py
+++ b/osm_nbi/instance_topics.py
@@ -33,8 +33,8 @@ class NsrTopic(BaseTopic):
     topic_msg = "ns"
     schema_new = ns_instantiate
 
-    def __init__(self, db, fs, msg):
-        BaseTopic.__init__(self, db, fs, msg)
+    def __init__(self, db, fs, msg, auth):
+        BaseTopic.__init__(self, db, fs, msg, auth)
 
     def _check_descriptor_dependencies(self, session, descriptor):
         """
@@ -182,6 +182,9 @@ class NsrTopic(BaseTopic):
         """
 
         try:
+            step = "checking quotas"
+            self.check_quota(session)
+
             step = "validating input parameters"
             ns_request = self._remove_envelop(indata)
             # Override descriptor with query string kwargs
@@ -391,8 +394,8 @@ class VnfrTopic(BaseTopic):
     topic = "vnfrs"
     topic_msg = None
 
-    def __init__(self, db, fs, msg):
-        BaseTopic.__init__(self, db, fs, msg)
+    def __init__(self, db, fs, msg, auth):
+        BaseTopic.__init__(self, db, fs, msg, auth)
 
     def delete(self, session, _id, dry_run=False):
         raise EngineException("Method delete called directly", HTTPStatus.INTERNAL_SERVER_ERROR)
@@ -415,8 +418,8 @@ class NsLcmOpTopic(BaseTopic):
         "terminate": None,
     }
 
-    def __init__(self, db, fs, msg):
-        BaseTopic.__init__(self, db, fs, msg)
+    def __init__(self, db, fs, msg, auth):
+        BaseTopic.__init__(self, db, fs, msg, auth)
 
     def _check_ns_operation(self, session, nsr, operation, indata):
         """
@@ -845,9 +848,9 @@ class NsiTopic(BaseTopic):
     topic = "nsis"
     topic_msg = "nsi"
 
-    def __init__(self, db, fs, msg):
-        BaseTopic.__init__(self, db, fs, msg)
-        self.nsrTopic = NsrTopic(db, fs, msg)
+    def __init__(self, db, fs, msg, auth):
+        BaseTopic.__init__(self, db, fs, msg, auth)
+        self.nsrTopic = NsrTopic(db, fs, msg, auth)
 
     @staticmethod
     def _format_ns_request(ns_request):
@@ -1006,6 +1009,9 @@ class NsiTopic(BaseTopic):
         """
 
         try:
+            step = "checking quotas"
+            self.check_quota(session)
+
             step = ""
             slice_request = self._remove_envelop(indata)
             # Override descriptor with query string kwargs
@@ -1169,9 +1175,9 @@ class NsiLcmOpTopic(BaseTopic):
         "terminate": None
     }
     
-    def __init__(self, db, fs, msg):
-        BaseTopic.__init__(self, db, fs, msg)
-        self.nsi_NsLcmOpTopic = NsLcmOpTopic(self.db, self.fs, self.msg)
+    def __init__(self, db, fs, msg, auth):
+        BaseTopic.__init__(self, db, fs, msg, auth)
+        self.nsi_NsLcmOpTopic = NsLcmOpTopic(self.db, self.fs, self.msg, self.auth)
 
     def _check_nsi_operation(self, session, nsir, operation, indata):
         """
diff --git a/osm_nbi/validation.py b/osm_nbi/validation.py
index 904abbd..6e43be5 100644
--- a/osm_nbi/validation.py
+++ b/osm_nbi/validation.py
@@ -631,6 +631,7 @@ user_edit_schema = {
 }
 
 # PROJECTS
+topics_with_quota = ["vnfds", "nsds", "nsts", "pdus", "nsrs", "nsis", "vim_accounts", "wim_accounts", "sdns"]
 project_new_schema = {
     "$schema": "http://json-schema.org/draft-04/schema#",
     "title": "New project schema for administrators",
@@ -638,6 +639,11 @@ project_new_schema = {
     "properties": {
         "name": shortname_schema,
         "admin": bool_schema,
+        "quotas": {
+            "type": "object",
+            "properties": {topic: integer0_schema for topic in topics_with_quota},
+            "additionalProperties": False
+        },
     },
     "required": ["name"],
     "additionalProperties": False
@@ -649,6 +655,11 @@ project_edit_schema = {
     "properties": {
         "admin": bool_schema,
         "name": shortname_schema,     # To allow Project Name modification
+        "quotas": {
+            "type": "object",
+            "properties": {topic: {"oneOf": [integer0_schema, null_schema]} for topic in topics_with_quota},
+            "additionalProperties": False
+        },
     },
     "additionalProperties": False,
     "minProperties": 1
-- 
2.17.1


From 9ebbf855f902fb7a9864427a364fb102ce792fc0 Mon Sep 17 00:00:00 2001
From: tierno 
Date: Tue, 3 Sep 2019 14:58:09 +0000
Subject: [PATCH 09/16] bug when migrating from old user format containing
 project instead project_role_mappings

Change-Id: I9cb853b2eecef2c69661e85f4bbba4b153811559
Signed-off-by: tierno 
---
 osm_nbi/auth.py | 28 +++++++++++++++++++++++-----
 1 file changed, 23 insertions(+), 5 deletions(-)

diff --git a/osm_nbi/auth.py b/osm_nbi/auth.py
index 3047c9f..36b6dc5 100644
--- a/osm_nbi/auth.py
+++ b/osm_nbi/auth.py
@@ -257,13 +257,31 @@ class Authenticator:
 
         # Create admin project&user if required
         pid = self.create_admin_project()
-        self.create_admin_user(pid)
+        user_id = self.create_admin_user(pid)
 
-        # self.backend.update_user({"_id": "admin",
-        #                           "add_project_role_mappings": {"project": "admin", "role": "system_admin"}})
-        if self.config["authentication"]["backend"] == "keystone":
+        # try to assign system_admin role to user admin if not any user has this role
+        if not user_id:
             try:
-                self.backend.assign_role_to_user("admin", "admin", "system_admin")
+                users = self.backend.get_user_list()
+                roles = self.backend.get_role_list({"name": "system_admin"})
+                role_id = roles[0]["_id"]
+                user_with_system_admin = False
+                user_admin_id = None
+                for user in users:
+                    if not user_admin_id:
+                        user_admin_id = user["_id"]
+                    if user["username"] == "admin":
+                        user_admin_id = user["_id"]
+                    for prm in user.get("project_role_mappings", ()):
+                        if prm["role"] == role_id:
+                            user_with_system_admin = True
+                            break
+                    if user_with_system_admin:
+                        break
+                if not user_with_system_admin:
+                    self.backend.update_user({"_id": user_admin_id,
+                                              "add_project_role_mappings": [{"project": pid, "role": role_id}]})
+                    self.logger.info("Added role system admin to user='{}' project=admin".format(user_admin_id))
             except Exception:
                 pass
 
-- 
2.17.1


From 16db949804909ad4e68bea83cc546124eaf562a0 Mon Sep 17 00:00:00 2001
From: delacruzramo 
Date: Wed, 25 Sep 2019 12:01:59 +0200
Subject: [PATCH 10/16] Tests for NBI Quotas

Change-Id: Ib5c2b5afb3ca0ee31f3c0bf98ed16f7b157861b6
Signed-off-by: delacruzramo 
---
 osm_nbi/tests/test.py | 427 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 427 insertions(+)

diff --git a/osm_nbi/tests/test.py b/osm_nbi/tests/test.py
index dd3c796..c9a37d8 100755
--- a/osm_nbi/tests/test.py
+++ b/osm_nbi/tests/test.py
@@ -27,6 +27,7 @@ from random import randint
 import os
 from sys import stderr
 from uuid import uuid4
+import re
 
 __author__ = "Alfonso Tierno, alfonso.tiernosepulveda@telefonica.com"
 __date__ = "$2018-03-01$"
@@ -82,6 +83,8 @@ headers_text = {"Accept": "text/plain,application/yaml"}
 r_header_zip = {"Content-type": "application/zip"}
 headers_zip = {"Accept": "application/zip,application/yaml"}
 headers_zip_yaml = {"Accept": "application/yaml", "Content-type": "application/zip"}
+headers_zip_json = {"Accept": "application/json", "Content-type": "application/zip"}
+headers_txt_json = {"Accept": "application/json", "Content-type": "text/plain"}
 r_headers_yaml_location_vnfd = {"Location": "/vnfpkgm/v1/vnf_packages_content/", "Content-Type": "application/yaml"}
 r_headers_yaml_location_nsd = {"Location": "/nsd/v1/ns_descriptors_content/", "Content-Type": "application/yaml"}
 r_headers_yaml_location_nst = {"Location": "/nst/v1/netslice_templates_content", "Content-Type": "application/yaml"}
@@ -2687,6 +2690,429 @@ class TestAuthentication:
         engine.remove_authorization()   # To finish
 
 
+class TestNbiQuotas():
+    description = "Test NBI Quotas"
+
+    @staticmethod
+    def run(engine, test_osm, manual_check, test_params=None):
+        engine.set_test_name("NBI-Quotas_")
+        # backend = test_params.get("backend") if test_params else None   # UNUSED
+
+        test_username = "test-nbi-quotas"
+        test_password = "test-nbi-quotas"
+        test_project = "test-nbi-quotas"
+
+        test_vim = "test-nbi-quotas"
+        test_wim = "test-nbi-quotas"
+        test_sdn = "test-nbi-quotas"
+
+        test_user_id = None
+        test_project_id = None
+
+        test_vim_ids = []
+        test_wim_ids = []
+        test_sdn_ids = []
+        test_vnfd_ids = []
+        test_nsd_ids = []
+        test_nst_ids = []
+        test_pdu_ids = []
+        test_nsr_ids = []
+        test_nsi_ids = []
+
+        # Save admin access data
+        admin_username = engine.user
+        admin_password = engine.password
+        admin_project = engine.project
+
+        # Get admin access
+        engine.get_autorization()
+        admin_token = engine.last_id
+
+        # Check that test project,user do not exist
+        res1 = engine.test("Check that test project doesn't exist", "GET", "/admin/v1/projects/"+test_project,
+                           headers_json, {}, (404), {}, True)
+        res2 = engine.test("Check that test user doesn't exist", "GET", "/admin/v1/users/"+test_username,
+                           headers_json, {}, (404), {}, True)
+        if None in [res1, res2]:
+            engine.remove_authorization()
+            logger.error("Test project and/or user already exist")
+            return
+
+        # Create test project&user
+        res = engine.test("Create test project", "POST", "/admin/v1/projects", headers_json,
+                          {"name": test_username,
+                           "quotas": {
+                               "vnfds": 2,
+                               "nsds": 2,
+                               "nsts": 1,
+                               "pdus": 1,
+                               "nsrs": 2,
+                               "nsis": 1,
+                               "vim_accounts": 1,
+                               "wim_accounts": 1,
+                               "sdns": 1,
+                           }
+                           },
+                          (201), r_header_json, "json")
+        test_project_id = engine.last_id if res else None
+        res = engine.test("Create test user", "POST", "/admin/v1/users", headers_json,
+                          {"username": test_username, "password": test_password,
+                           "project_role_mappings": [{"project": test_project, "role": "project_admin"}]},
+                          (201), r_header_json, "json")
+        test_user_id = engine.last_id if res else None
+
+        if test_project_id and test_user_id:
+
+            # Get user access
+            engine.token = None
+            engine.user = test_username
+            engine.password = test_password
+            engine.project = test_project
+            engine.get_autorization()
+            user_token = engine.last_id
+
+            # Create test VIM
+            res = engine.test("Create test VIM", "POST", "/admin/v1/vim_accounts", headers_json,
+                              {"name": test_vim,
+                               "vim_type": "openvim",
+                               "vim_user": test_username,
+                               "vim_password": test_password,
+                               "vim_tenant_name": test_project,
+                               "vim_url": "https://0.0.0.0:0/v0.0",
+                               },
+                              (202), r_header_json, "json")
+            test_vim_ids += [engine.last_id if res else None]
+
+            res = engine.test("Try to create second test VIM", "POST", "/admin/v1/vim_accounts", headers_json,
+                              {"name": test_vim + "_2",
+                               "vim_type": "openvim",
+                               "vim_user": test_username,
+                               "vim_password": test_password,
+                               "vim_tenant_name": test_project,
+                               "vim_url": "https://0.0.0.0:0/v0.0",
+                               },
+                              (422), r_header_json, "json")
+            test_vim_ids += [engine.last_id if res is None else None]
+
+            res = engine.test("Try to create second test VIM with FORCE",
+                              "POST", "/admin/v1/vim_accounts?FORCE", headers_json,
+                              {"name": test_vim + "_3",
+                               "vim_type": "openvim",
+                               "vim_user": test_username,
+                               "vim_password": test_password,
+                               "vim_tenant_name": test_project,
+                               "vim_url": "https://0.0.0.0:0/v0.0",
+                               },
+                              (202), r_header_json, "json")
+            test_vim_ids += [engine.last_id if res else None]
+
+            if test_vim_ids[0]:
+
+                # Download descriptor files (if required)
+                test_dir = "/tmp/"+test_username+"/"
+                test_url = "https://osm-download.etsi.org/ftp/osm-6.0-six/7th-hackfest/packages/"
+                vnfd_filenames = ["slice_hackfest_vnfd.tar.gz", "slice_hackfest_middle_vnfd.tar.gz"]
+                nsd_filenames = ["slice_hackfest_nsd.tar.gz", "slice_hackfest_middle_nsd.tar.gz"]
+                nst_filenames = ["slice_hackfest_nstd.yaml"]
+                pdu_filenames = ["PDU_router.yaml"]
+                desc_filenames = vnfd_filenames + nsd_filenames + nst_filenames + pdu_filenames
+                if not os.path.exists(test_dir):
+                    os.makedirs(test_dir)
+                for filename in desc_filenames:
+                    if not os.path.exists(test_dir+filename):
+                        res = requests.get(test_url+filename)
+                        if res.status_code < 300:
+                            with open(test_dir+filename, "wb") as file:
+                                file.write(res.content)
+
+                if all([os.path.exists(test_dir+p) for p in desc_filenames]):
+
+                    # Test VNFD Quotas
+                    res = engine.test("Create test VNFD #1", "POST", "/vnfpkgm/v1/vnf_packages_content",
+                                      headers_zip_json, "@b"+test_dir+vnfd_filenames[0],
+                                      (201), r_header_json, "json")
+                    test_vnfd_ids += [engine.last_id if res else None]
+                    res = engine.test("Create test VNFD #2", "POST", "/vnfpkgm/v1/vnf_packages_content",
+                                      headers_zip_json, "@b"+test_dir+vnfd_filenames[1],
+                                      (201), r_header_json, "json")
+                    test_vnfd_ids += [engine.last_id if res else None]
+                    res = engine.test("Try to create extra test VNFD", "POST",
+                                      "/vnfpkgm/v1/vnf_packages_content",
+                                      headers_zip_json, "@b"+test_dir+vnfd_filenames[0],
+                                      (422), r_header_json, "json")
+                    test_vnfd_ids += [engine.last_id if res is None else None]
+                    res = engine.test("Try to create extra test VNFD with FORCE",
+                                      "POST", "/vnfpkgm/v1/vnf_packages_content?FORCE",
+                                      headers_zip_json, "@b"+test_dir+vnfd_filenames[0],
+                                      (201), r_header_json, "json")
+                    test_vnfd_ids += [engine.last_id if res else None]
+
+                    # Remove extra VNFDs to prevent further errors
+                    for i in [2, 3]:
+                        if test_vnfd_ids[i]:
+                            res = engine.test("Delete test VNFD #" + str(i), "DELETE",
+                                              "/vnfpkgm/v1/vnf_packages_content/"+test_vnfd_ids[i]+"?FORCE",
+                                              headers_json, {}, (204), {}, 0)
+                            if res:
+                                test_vnfd_ids[i] = None
+
+                    if test_vnfd_ids[0] and test_vnfd_ids[1]:
+
+                        # Test NSD Quotas
+                        res = engine.test("Create test NSD #1", "POST", "/nsd/v1/ns_descriptors_content",
+                                          headers_zip_json, "@b"+test_dir+nsd_filenames[0],
+                                          (201), r_header_json, "json")
+                        test_nsd_ids += [engine.last_id if res else None]
+                        res = engine.test("Create test NSD #2", "POST", "/nsd/v1/ns_descriptors_content",
+                                          headers_zip_json, "@b"+test_dir+nsd_filenames[1],
+                                          (201), r_header_json, "json")
+                        test_nsd_ids += [engine.last_id if res else None]
+                        res = engine.test("Try to create extra test NSD", "POST", "/nsd/v1/ns_descriptors_content",
+                                          headers_zip_json, "@b"+test_dir+nsd_filenames[0],
+                                          (422), r_header_json, "json")
+                        test_nsd_ids += [engine.last_id if res is None else None]
+                        res = engine.test("Try to create extra test NSD with FORCE",
+                                          "POST", "/nsd/v1/ns_descriptors_content?FORCE",
+                                          headers_zip_json, "@b"+test_dir+nsd_filenames[0],
+                                          (201), r_header_json, "json")
+                        test_nsd_ids += [engine.last_id if res else None]
+
+                        # Remove extra NSDs to prevent further errors
+                        for i in [2, 3]:
+                            if test_nsd_ids[i]:
+                                res = engine.test("Delete test NSD #" + str(i), "DELETE",
+                                                  "/nsd/v1/ns_descriptors_content/"+test_nsd_ids[i]+"?FORCE",
+                                                  headers_json, {}, (204), {}, 0)
+                                if res:
+                                    test_nsd_ids[i] = None
+
+                        if test_nsd_ids[0] and test_nsd_ids[1]:
+
+                            # Test NSR Quotas
+                            res = engine.test("Create test NSR #1", "POST", "/nslcm/v1/ns_instances_content",
+                                              headers_json,
+                                              {"nsName": test_username+"_1",
+                                               "nsdId": test_nsd_ids[0],
+                                               "vimAccountId": test_vim_ids[0],
+                                               },
+                                              (201), r_header_json, "json")
+                            test_nsr_ids += [engine.last_id if res else None]
+                            res = engine.test("Create test NSR #2", "POST", "/nslcm/v1/ns_instances_content",
+                                              headers_json,
+                                              {"nsName": test_username+"_2",
+                                               "nsdId": test_nsd_ids[1],
+                                               "vimAccountId": test_vim_ids[0],
+                                               },
+                                              (201), r_header_json, "json")
+                            test_nsr_ids += [engine.last_id if res else None]
+                            res = engine.test("Try to create extra test NSR", "POST", "/nslcm/v1/ns_instances_content",
+                                              headers_json,
+                                              {"nsName": test_username+"_3",
+                                               "nsdId": test_nsd_ids[0],
+                                               "vimAccountId": test_vim_ids[0],
+                                               },
+                                              (422), r_header_json, "json")
+                            test_nsr_ids += [engine.last_id if res is None else None]
+                            res = engine.test("Try to create test NSR with FORCE", "POST",
+                                              "/nslcm/v1/ns_instances_content?FORCE", headers_json,
+                                              {"nsName": test_username+"_4",
+                                               "nsdId": test_nsd_ids[0],
+                                               "vimAccountId": test_vim_ids[0],
+                                               },
+                                              (201), r_header_json, "json")
+                            test_nsr_ids += [engine.last_id if res else None]
+
+                            # Test NST Quotas
+                            res = engine.test("Create test NST", "POST", "/nst/v1/netslice_templates_content",
+                                              headers_txt_json, "@b"+test_dir+nst_filenames[0],
+                                              (201), r_header_json, "json")
+                            test_nst_ids += [engine.last_id if res else None]
+                            res = engine.test("Try to create extra test NST", "POST",
+                                              "/nst/v1/netslice_templates_content",
+                                              headers_txt_json, "@b"+test_dir+nst_filenames[0],
+                                              (422), r_header_json, "json")
+                            test_nst_ids += [engine.last_id if res is None else None]
+                            res = engine.test("Try to create extra test NST with FORCE", "POST",
+                                              "/nst/v1/netslice_templates_content?FORCE",
+                                              headers_txt_json, "@b"+test_dir+nst_filenames[0],
+                                              (201), r_header_json, "json")
+                            test_nst_ids += [engine.last_id if res else None]
+
+                            if test_nst_ids[0]:
+                                # Remove NSR Quota
+                                engine.set_header({"Authorization": "Bearer {}".format(admin_token)})
+                                res = engine.test("Remove NSR Quota", "PUT", "/admin/v1/projects/"+test_project_id,
+                                                  headers_json,
+                                                  {"quotas": {"nsrs": None}},
+                                                  (204), {}, 0)
+                                engine.set_header({"Authorization": "Bearer {}".format(user_token)})
+                                if res:
+                                    # Test NSI Quotas
+                                    res = engine.test("Create test NSI", "POST",
+                                                      "/nsilcm/v1/netslice_instances_content", headers_json,
+                                                      {"nsiName": test_username,
+                                                       "nstId": test_nst_ids[0],
+                                                       "vimAccountId": test_vim_ids[0],
+                                                       },
+                                                      (201), r_header_json, "json")
+                                    test_nsi_ids += [engine.last_id if res else None]
+                                    res = engine.test("Try to create extra test NSI", "POST",
+                                                      "/nsilcm/v1/netslice_instances_content", headers_json,
+                                                      {"nsiName": test_username,
+                                                       "nstId": test_nst_ids[0],
+                                                       "vimAccountId": test_vim_ids[0],
+                                                       },
+                                                      (400), r_header_json, "json")
+                                    test_nsi_ids += [engine.last_id if res is None else None]
+                                    res = engine.test("Try to create extra test NSI with FORCE", "POST",
+                                                      "/nsilcm/v1/netslice_instances_content?FORCE", headers_json,
+                                                      {"nsiName": test_username,
+                                                       "nstId": test_nst_ids[0],
+                                                       "vimAccountId": test_vim_ids[0],
+                                                       },
+                                                      (201), r_header_json, "json")
+                                    test_nsi_ids += [engine.last_id if res else None]
+
+                    # Test PDU Quotas
+                    with open(test_dir+pdu_filenames[0], "rb") as file:
+                        pdu_text = re.sub(r"ip-address: *\[[^\]]*\]", "ip-address: '0.0.0.0'",
+                                          file.read().decode("utf-8"))
+                    with open(test_dir+pdu_filenames[0], "wb") as file:
+                        file.write(pdu_text.encode("utf-8"))
+                    res = engine.test("Create test PDU", "POST", "/pdu/v1/pdu_descriptors",
+                                      headers_yaml, "@b"+test_dir+pdu_filenames[0],
+                                      (201), r_header_yaml, "yaml")
+                    test_pdu_ids += [engine.last_id if res else None]
+                    res = engine.test("Try to create extra test PDU", "POST", "/pdu/v1/pdu_descriptors",
+                                      headers_yaml, "@b"+test_dir+pdu_filenames[0],
+                                      (422), r_header_yaml, "yaml")
+                    test_pdu_ids += [engine.last_id if res is None else None]
+                    res = engine.test("Try to create extra test PDU with FORCE", "POST",
+                                      "/pdu/v1/pdu_descriptors?FORCE",
+                                      headers_yaml, "@b"+test_dir+pdu_filenames[0],
+                                      (201), r_header_yaml, "yaml")
+                    test_pdu_ids += [engine.last_id if res else None]
+
+                    # Cleanup
+                    for i, id in enumerate(test_nsi_ids):
+                        if id:
+                            engine.test("Delete test NSI #"+str(i), "DELETE",
+                                        "/nsilcm/v1/netslice_instances_content/"+id+"?FORCE",
+                                        headers_json, {}, (204), {}, 0)
+                    for i, id in enumerate(test_nsr_ids):
+                        if id:
+                            engine.test("Delete test NSR #"+str(i), "DELETE",
+                                        "/nslcm/v1/ns_instances_content/"+id+"?FORCE",
+                                        headers_json, {}, (204), {}, 0)
+                    for i, id in enumerate(test_nst_ids):
+                        if id:
+                            engine.test("Delete test NST #"+str(i), "DELETE",
+                                        "/nst/v1/netslice_templates_content/"+id+"?FORCE",
+                                        headers_json, {}, (204), {}, 0)
+                    for i, id in enumerate(test_nsd_ids):
+                        if id:
+                            engine.test("Delete test NSD #"+str(i), "DELETE",
+                                        "/nsd/v1/ns_descriptors_content/"+id+"?FORCE",
+                                        headers_json, {}, (204), {}, 0)
+                    for i, id in enumerate(test_vnfd_ids):
+                        if id:
+                            engine.test("Delete test VNFD #"+str(i), "DELETE",
+                                        "/vnfpkgm/v1/vnf_packages_content/"+id+"?FORCE",
+                                        headers_json, {}, (204), {}, 0)
+                    for i, id in enumerate(test_pdu_ids):
+                        if id:
+                            engine.test("Delete test PDU #"+str(i), "DELETE",
+                                        "/pdu/v1/pdu_descriptors/"+id+"?FORCE",
+                                        headers_json, {}, (204), {}, 0)
+
+                    # END Test NBI Quotas
+
+            # Test WIM Quotas
+            res = engine.test("Create test WIM", "POST", "/admin/v1/wim_accounts", headers_json,
+                              {"name": test_wim,
+                               "wim_type": "onos",
+                               "wim_url": "https://0.0.0.0:0/v0.0",
+                               },
+                              (202), r_header_json, "json")
+            test_wim_ids += [engine.last_id if res else None]
+            res = engine.test("Try to create second test WIM", "POST", "/admin/v1/wim_accounts", headers_json,
+                              {"name": test_wim + "_2",
+                               "wim_type": "onos",
+                               "wim_url": "https://0.0.0.0:0/v0.0",
+                               },
+                              (422), r_header_json, "json")
+            test_wim_ids += [engine.last_id if res is None else None]
+            res = engine.test("Try to create second test WIM with FORCE", "POST", "/admin/v1/wim_accounts?FORCE",
+                              headers_json,
+                              {"name": test_wim + "_3",
+                               "wim_type": "onos",
+                               "wim_url": "https://0.0.0.0:0/v0.0",
+                               },
+                              (202), r_header_json, "json")
+            test_wim_ids += [engine.last_id if res else None]
+
+            # Test SDN Quotas
+            res = engine.test("Create test SDN", "POST", "/admin/v1/sdns", headers_json,
+                              {"name": test_sdn,
+                               "type": "onos",
+                               "ip": "0.0.0.0",
+                               "port": 9999,
+                               "dpid": "00:00:00:00:00:00:00:00",
+                               },
+                              (202), r_header_json, "json")
+            test_sdn_ids += [engine.last_id if res else None]
+            res = engine.test("Try to create second test SDN", "POST", "/admin/v1/sdns", headers_json,
+                              {"name": test_sdn + "_2",
+                               "type": "onos",
+                               "ip": "0.0.0.0",
+                               "port": 9999,
+                               "dpid": "00:00:00:00:00:00:00:00",
+                               },
+                              (422), r_header_json, "json")
+            test_sdn_ids += [engine.last_id if res is None else None]
+            res = engine.test("Try to create second test SDN with FORCE", "POST", "/admin/v1/sdns?FORCE", headers_json,
+                              {"name": test_sdn + "_3",
+                               "type": "onos",
+                               "ip": "0.0.0.0",
+                               "port": 9999,
+                               "dpid": "00:00:00:00:00:00:00:00",
+                               },
+                              (202), r_header_json, "json")
+            test_sdn_ids += [engine.last_id if res else None]
+
+            # Cleanup
+            for i, id in enumerate(test_vim_ids):
+                if id:
+                    engine.test("Delete test VIM #"+str(i), "DELETE", "/admin/v1/vim_accounts/"+id+"?FORCE",
+                                headers_json, {}, (202), {}, 0)
+            for i, id in enumerate(test_wim_ids):
+                if id:
+                    engine.test("Delete test WIM #"+str(i), "DELETE", "/admin/v1/wim_accounts/"+id+"?FORCE",
+                                headers_json, {}, (202), {}, 0)
+            for i, id in enumerate(test_sdn_ids):
+                if id:
+                    engine.test("Delete test SDN #"+str(i), "DELETE", "/admin/v1/sdns/"+id+"?FORCE",
+                                headers_json, {}, (202), {}, 0)
+
+            # Release user access
+            engine.remove_authorization()
+
+        # Cleanup
+        engine.user = admin_username
+        engine.password = admin_password
+        engine.project = admin_project
+        engine.get_autorization()
+        if test_user_id:
+            engine.test("Delete test user", "DELETE", "/admin/v1/users/"+test_user_id+"?FORCE",
+                        headers_json, {}, (204), {}, 0)
+        if test_project_id:
+            engine.test("Delete test project", "DELETE", "/admin/v1/projects/"+test_project_id+"?FORCE",
+                        headers_json, {}, (204), {}, 0)
+        engine.remove_authorization()
+
+    # END class TestNbiQuotas
+
+
 if __name__ == "__main__":
     global logger
     test = ""
@@ -2732,6 +3158,7 @@ if __name__ == "__main__":
             "Deploy-SimpleCharm": TestDeploySimpleCharm,
             "Deploy-SimpleCharm2": TestDeploySimpleCharm2,
             "Authentication": TestAuthentication,
+            "NBI-Quotas": TestNbiQuotas,
         }
         test_to_do = []
         test_params = {}
-- 
2.17.1


From 029405dc71e09d30e7660fa68f799737d017acbf Mon Sep 17 00:00:00 2001
From: delacruzramo 
Date: Thu, 26 Sep 2019 10:52:56 +0200
Subject: [PATCH 11/16] Bug 853 - Fix permissions

Change-Id: Ie95624fe6cbea3cd8ee45709eb76cac51cf981f6
Signed-off-by: delacruzramo 
---
 osm_nbi/admin_topics.py | 35 +++++++++++++++++++++++++----------
 osm_nbi/auth.py         | 32 +++++++++++++++++++++++---------
 osm_nbi/nbi.py          |  6 +++---
 3 files changed, 51 insertions(+), 22 deletions(-)

diff --git a/osm_nbi/admin_topics.py b/osm_nbi/admin_topics.py
index c198733..71dfa3c 100644
--- a/osm_nbi/admin_topics.py
+++ b/osm_nbi/admin_topics.py
@@ -545,8 +545,8 @@ class UserTopicAuth(UserTopic):
         """
         # Allow _id to be a name or uuid
         filter_q = {self.id_field(self.topic, _id): _id}
-        users = self.auth.get_user_list(filter_q)
-
+        # users = self.auth.get_user_list(filter_q)
+        users = self.list(session, filter_q)   # To allow default filtering (Bug 853)
         if len(users) == 1:
             return users[0]
         elif len(users) > 1:
@@ -676,9 +676,11 @@ class UserTopicAuth(UserTopic):
         :param filter_q: filter of data to be applied
         :return: The list, it can be empty if no one match the filter.
         """
-        users = self.auth.get_user_list(filter_q)
-
-        return users
+        user_list = self.auth.get_user_list(filter_q)
+        if not session["allow_show_user_project_role"]:
+            # Bug 853 - Default filtering
+            user_list = [usr for usr in user_list if usr["username"] == session["username"]]
+        return user_list
 
     def delete(self, session, _id, dry_run=False):
         """
@@ -827,8 +829,8 @@ class ProjectTopicAuth(ProjectTopic):
         """
         # Allow _id to be a name or uuid
         filter_q = {self.id_field(self.topic, _id): _id}
-        projects = self.auth.get_project_list(filter_q=filter_q)
-
+        # projects = self.auth.get_project_list(filter_q=filter_q)
+        projects = self.list(session, filter_q)   # To allow default filtering (Bug 853)
         if len(projects) == 1:
             return projects[0]
         elif len(projects) > 1:
@@ -844,7 +846,13 @@ class ProjectTopicAuth(ProjectTopic):
         :param filter_q: filter of data to be applied
         :return: The list, it can be empty if no one match the filter.
         """
-        return self.auth.get_project_list(filter_q)
+        project_list = self.auth.get_project_list(filter_q)
+        if not session["allow_show_user_project_role"]:
+            # Bug 853 - Default filtering
+            user = self.auth.get_user(session["username"])
+            projects = [prm["project"] for prm in user["project_role_mappings"]]
+            project_list = [proj for proj in project_list if proj["_id"] in projects]
+        return project_list
 
     def delete(self, session, _id, dry_run=False):
         """
@@ -1071,7 +1079,8 @@ class RoleTopicAuth(BaseTopic):
         :return: dictionary, raise exception if not found.
         """
         filter_q = {BaseTopic.id_field(self.topic, _id): _id}
-        roles = self.auth.get_role_list(filter_q)
+        # roles = self.auth.get_role_list(filter_q)
+        roles = self.list(session, filter_q)   # To allow default filtering (Bug 853)
         if not roles:
             raise AuthconnNotFoundException("Not found any role with filter {}".format(filter_q))
         elif len(roles) > 1:
@@ -1086,7 +1095,13 @@ class RoleTopicAuth(BaseTopic):
         :param filter_q: filter of data to be applied
         :return: The list, it can be empty if no one match the filter.
         """
-        return self.auth.get_role_list(filter_q)
+        role_list = self.auth.get_role_list(filter_q)
+        if not session["allow_show_user_project_role"]:
+            # Bug 853 - Default filtering
+            user = self.auth.get_user(session["username"])
+            roles = [prm["role"] for prm in user["project_role_mappings"]]
+            role_list = [role for role in role_list if role["_id"] in roles]
+        return role_list
 
     def new(self, rollback, session, indata=None, kwargs=None, headers=None):
         """
diff --git a/osm_nbi/auth.py b/osm_nbi/auth.py
index 36b6dc5..8bb479d 100644
--- a/osm_nbi/auth.py
+++ b/osm_nbi/auth.py
@@ -45,8 +45,8 @@ from osm_nbi.authconn_internal import AuthconnInternal   # Comment out for testi
 from osm_common import dbmongo
 from osm_common import dbmemory
 from osm_common.dbbase import DbException
+from osm_nbi.validation import is_valid_uuid
 from itertools import chain
-
 from uuid import uuid4
 
 
@@ -320,7 +320,7 @@ class Authenticator:
 
         self.operation_to_allowed_roles = permissions
 
-    def authorize(self, role_permission=None, query_string_operations=None):
+    def authorize(self, role_permission=None, query_string_operations=None, item_id=None):
         token = None
         user_passwd64 = None
         try:
@@ -358,8 +358,10 @@ class Authenticator:
             # TODO add to token info remote host, port
 
             if role_permission:
-                self.check_permissions(token_info, cherrypy.request.method, role_permission,
-                                       query_string_operations)
+                RBAC_auth = self.check_permissions(token_info, cherrypy.request.method, role_permission,
+                                                   query_string_operations, item_id)
+                token_info["allow_show_user_project_role"] = RBAC_auth
+
             return token_info
         except AuthException as e:
             if not isinstance(e, AuthExceptionUnauthorized):
@@ -427,7 +429,7 @@ class Authenticator:
         except KeyError:
             raise AuthException("Token '{}' not found".format(token), http_code=HTTPStatus.NOT_FOUND)
 
-    def check_permissions(self, token_info, method, role_permission=None, query_string_operations=None):
+    def check_permissions(self, token_info, method, role_permission=None, query_string_operations=None, item_id=None):
         """
         Checks that operation has permissions to be done, base on the assigned roles to this user project
         :param token_info: Dictionary that contains "roles" with a list of assigned roles.
@@ -437,7 +439,9 @@ class Authenticator:
         :param role_permission: role permission name of the operation required
         :param query_string_operations: list of possible admin query strings provided by user. It is checked that the
             assigned role allows this query string for this method
-        :return: None if granted, exception if not allowed
+        :param item_id: item identifier if included in the URL, None otherwise
+        :return: True if access granted by permission rules, False if access granted by default rules (Bug 853)
+        :raises: AuthExceptionUnauthorized if access denied
         """
 
         roles_required = self.operation_to_allowed_roles[role_permission]
@@ -451,19 +455,29 @@ class Authenticator:
                 break
 
         if "anonymous" in roles_required:
-            return
+            return True
         operation_allowed = False
         for role in roles_allowed:
             if role in roles_required:
                 operation_allowed = True
                 # if query_string operations, check if this role allows it
                 if not query_string_operations:
-                    return
+                    return True
                 for query_string_operation in query_string_operations:
                     if role not in self.operation_to_allowed_roles[query_string_operation]:
                         break
                 else:
-                    return
+                    return True
+
+        # Bug 853 - Final Solution
+        # User/Project/Role whole listings are filtered elsewhere
+        # uid, pid, rid = ("user_id", "project_id", "id") if is_valid_uuid(id) else ("username", "project_name", "name")
+        uid = "user_id" if is_valid_uuid(item_id) else "username"
+        if (role_permission in ["projects:get", "projects:id:get", "roles:get", "roles:id:get", "users:get"]) \
+                or (role_permission == "users:id:get" and item_id == token_info[uid]):
+            # or (role_permission == "projects:id:get" and item_id == token_info[pid]) \
+            # or (role_permission == "roles:id:get" and item_id in [role[rid] for role in token_info["roles"]]):
+            return False
 
         if not operation_allowed:
             raise AuthExceptionUnauthorized("Access denied: lack of permissions.")
diff --git a/osm_nbi/nbi.py b/osm_nbi/nbi.py
index 03e8727..665df75 100644
--- a/osm_nbi/nbi.py
+++ b/osm_nbi/nbi.py
@@ -861,7 +861,8 @@ class Server(object):
             method: show, list, delete, write
         """
         admin_query = {"force": False, "project_id": (token_info["project_id"], ), "username": token_info["username"],
-                       "admin": token_info["admin"], "public": None}
+                       "admin": token_info["admin"], "public": None,
+                       "allow_show_user_project_role": token_info["allow_show_user_project_role"]}
         if kwargs:
             # FORCE
             if "FORCE" in kwargs:
@@ -944,8 +945,7 @@ class Server(object):
             query_string_operations = self._extract_query_string_operations(kwargs, method)
             if main_topic == "admin" and topic == "tokens":
                 return self.token(method, _id, kwargs)
-
-            token_info = self.authenticator.authorize(role_permission, query_string_operations)
+            token_info = self.authenticator.authorize(role_permission, query_string_operations, _id)
             engine_session = self._manage_admin_query(token_info, kwargs, method, _id)
             indata = self._format_in(kwargs)
             engine_topic = topic
-- 
2.17.1


From 2125a31711a94d2c21c24fe4aa768a2f3823f3cb Mon Sep 17 00:00:00 2001
From: delacruzramo 
Date: Mon, 30 Sep 2019 15:50:52 +0200
Subject: [PATCH 12/16] Fix project_user role permissions

Change-Id: If10f6b77d468535516dcd8bdffb712b98cec2c61
Signed-off-by: delacruzramo 
---
 osm_nbi/resources_to_operations.yml |  2 +-
 osm_nbi/roles_to_operations.yml     | 65 ++++++++++++++++-------------
 2 files changed, 36 insertions(+), 31 deletions(-)

diff --git a/osm_nbi/resources_to_operations.yml b/osm_nbi/resources_to_operations.yml
index c2fb289..0c39fe3 100644
--- a/osm_nbi/resources_to_operations.yml
+++ b/osm_nbi/resources_to_operations.yml
@@ -341,7 +341,7 @@ resources_to_operations:
   "GET /nsilcm/v1/netslice_instances_content": "slice_instances:get"
   "GET /nsilcm/v1/netslice_instances": "slice_instances:get"
 
-  "POST /nsilcm/v1/netslice_instances_content": "slice_instances:content:get"
+  "POST /nsilcm/v1/netslice_instances_content": "slice_instances:content:post"
 
   "GET /nsilcm/v1/netslice_instances_content/": "slice_instances:id:get"
   "GET /nsilcm/v1/netslice_instances/": "slice_instances:id:get"
diff --git a/osm_nbi/roles_to_operations.yml b/osm_nbi/roles_to_operations.yml
index 916f09e..cdb99d6 100644
--- a/osm_nbi/roles_to_operations.yml
+++ b/osm_nbi/roles_to_operations.yml
@@ -53,6 +53,10 @@ roles:
 # NOTE 2: The default  tag can be overridden by using more specific tags
 #         with a different value.
 #
+# The 'force', 'public' and 'set_project' operation tags (respectively allowing/denying
+# the use of the query-strings FORCE, PUBLIC and SET_PROJECT), take by default the
+# value specified by the tag 'default' (false if not specified).
+#
 # The node  tag is defined by using an internal node of the tree, i.e.
 # "nsds", "users:id". A node  tag will affect all the nodes and leafs
 # beneath it. It can be used to override a default  tag.
@@ -82,6 +86,7 @@ roles:
     permissions:
         default:  false
         admin:    false
+        force:    true
         tokens:   true
         users:    true
         projects: true
@@ -89,52 +94,52 @@ roles:
 
   - name: "project_admin"
     permissions:
-        default: true
-        # Users
-        users:post:      false
-        users:id:patch:  false
-        users:id:delete: false
-        users:id:put:    false
-        # Projects
+        default:  true
+        admin:    false
+        users:    false
         projects: false
-        # Roles
         roles:    false
 
   - name: "project_user"
     permissions:
-        default: true
-        # NS Instances
-        ns_instances: false
-        ns_instances:get: true
-        # VNF Instances
-        vnf_instances: false
-        # Users
-        users: false
-        users:id:get: true
-        users:id:put: true
-        users:id:patch: true
-        # Projects
+        default:     false
+        admin:       false
+        force:       false
+        public:      true
+        set_project: true
+        vnf_packages:    true
+        ns_descriptors:  true
+        slice_templates: true
+        ns_instances:    true
+        vnf_instances:   true
+        slice_instances: true
+        users:    false
         projects: false
+        roles:    false
         # VIMs
-        vims: false
-        vims:get: true
+        vims:        false
+        vims:get:    true
         vims:id:get: true
         # VIM Accounts
-        vim_accounts: false
-        vim_accounts:get: true
+        vim_accounts:        false
+        vim_accounts:get:    true
         vim_accounts:id:get: true
         # SDN Controllers
-        sdn_controllers: false
-        sdn_controllers:get: true
+        sdn_controllers:        false
+        sdn_controllers:get:    true
         sdn_controllers:id:get: true
         # WIMs
-        wims: false
-        wims:get: true
+        wims:        false
+        wims:get:    true
         wims:id:get: true
         # WIM Accounts
-        wim_accounts: false
-        wim_accounts:get: true
+        wim_accounts:        false
+        wim_accounts:get:    true
         wim_accounts:id:get: true
+        # PDUs
+        pduds:        false
+        pduds:get:    true
+        pduds:id:get: true
 
   - name: "anonymous"
     permissions:
-- 
2.17.1


From b19cadcf9ff2169f34f6db79b866bad91f9c8aed Mon Sep 17 00:00:00 2001
From: delacruzramo 
Date: Tue, 8 Oct 2019 10:18:02 +0200
Subject: [PATCH 13/16] Adapt NBI to YAML V5

Change-Id: Ia768b60b0304d99a24bbfab2795922d54ca9ca0c
Signed-off-by: delacruzramo 
---
 osm_nbi/auth.py              |  2 +-
 osm_nbi/descriptor_topics.py |  2 +-
 osm_nbi/engine.py            |  2 +-
 osm_nbi/nbi.py               | 16 ++++++++--------
 osm_nbi/tests/test.py        |  7 ++++---
 5 files changed, 15 insertions(+), 14 deletions(-)

diff --git a/osm_nbi/auth.py b/osm_nbi/auth.py
index 8bb479d..dc3d386 100644
--- a/osm_nbi/auth.py
+++ b/osm_nbi/auth.py
@@ -214,7 +214,7 @@ class Authenticator:
         # Loading permissions to MongoDB if there is not any permission.
         if not records or (len(records) == 1 and records[0]["name"] == "admin"):
             with open(self.roles_to_operations_file, "r") as stream:
-                roles_to_operations_yaml = yaml.load(stream)
+                roles_to_operations_yaml = yaml.load(stream, Loader=yaml.Loader)
 
             role_names = []
             for role_with_operations in roles_to_operations_yaml["roles"]:
diff --git a/osm_nbi/descriptor_topics.py b/osm_nbi/descriptor_topics.py
index d01dc13..95ced0d 100644
--- a/osm_nbi/descriptor_topics.py
+++ b/osm_nbi/descriptor_topics.py
@@ -264,7 +264,7 @@ class DescriptorTopic(BaseTopic):
                 indata = json.load(content)
             else:
                 error_text = "Invalid yaml format "
-                indata = yaml.load(content)
+                indata = yaml.load(content, Loader=yaml.SafeLoader)
 
             current_desc["_admin"]["storage"] = storage
             current_desc["_admin"]["onboardingState"] = "ONBOARDED"
diff --git a/osm_nbi/engine.py b/osm_nbi/engine.py
index 67a9233..e36f5c6 100644
--- a/osm_nbi/engine.py
+++ b/osm_nbi/engine.py
@@ -137,7 +137,7 @@ class Engine(object):
                         raise EngineException("Invalid permission configuration: resources_to_operations file missing")
 
                 with open(resources_to_operations_file, 'r') as f:
-                    resources_to_operations = yaml.load(f)
+                    resources_to_operations = yaml.load(f, Loader=yaml.Loader)
 
                 self.operations = []
 
diff --git a/osm_nbi/nbi.py b/osm_nbi/nbi.py
index 665df75..8f995f5 100644
--- a/osm_nbi/nbi.py
+++ b/osm_nbi/nbi.py
@@ -481,7 +481,7 @@ class Server(object):
                         cherrypy.request.headers.pop("Content-File-MD5", None)
                     elif "application/yaml" in cherrypy.request.headers["Content-Type"]:
                         error_text = "Invalid yaml format "
-                        indata = yaml.load(cherrypy.request.body)
+                        indata = yaml.load(cherrypy.request.body, Loader=yaml.SafeLoader)
                         cherrypy.request.headers.pop("Content-File-MD5", None)
                     elif "application/binary" in cherrypy.request.headers["Content-Type"] or \
                          "application/gzip" in cherrypy.request.headers["Content-Type"] or \
@@ -501,11 +501,11 @@ class Server(object):
                         #                          "Only 'Content-Type' of type 'application/json' or
                         # 'application/yaml' for input format are available")
                         error_text = "Invalid yaml format "
-                        indata = yaml.load(cherrypy.request.body)
+                        indata = yaml.load(cherrypy.request.body, Loader=yaml.SafeLoader)
                         cherrypy.request.headers.pop("Content-File-MD5", None)
                 else:
                     error_text = "Invalid yaml format "
-                    indata = yaml.load(cherrypy.request.body)
+                    indata = yaml.load(cherrypy.request.body, Loader=yaml.SafeLoader)
                     cherrypy.request.headers.pop("Content-File-MD5", None)
             if not indata:
                 indata = {}
@@ -520,7 +520,7 @@ class Server(object):
                         kwargs[k] = None
                     elif format_yaml:
                         try:
-                            kwargs[k] = yaml.load(v)
+                            kwargs[k] = yaml.load(v, Loader=yaml.SafeLoader)
                         except Exception:
                             pass
                     elif k.endswith(".gt") or k.endswith(".lt") or k.endswith(".gte") or k.endswith(".lte"):
@@ -539,7 +539,7 @@ class Server(object):
                             v[index] = None
                         elif format_yaml:
                             try:
-                                v[index] = yaml.load(v[index])
+                                v[index] = yaml.load(v[index], Loader=yaml.SafeLoader)
                             except Exception:
                                 pass
 
@@ -755,14 +755,14 @@ class Server(object):
             return_text = "
{} ->\n".format(main_topic)
             try:
                 if cherrypy.request.method == 'POST':
-                    to_send = yaml.load(cherrypy.request.body)
+                    to_send = yaml.load(cherrypy.request.body, Loader=yaml.SafeLoader)
                     for k, v in to_send.items():
                         self.engine.msg.write(main_topic, k, v)
                         return_text += "  {}: {}\n".format(k, v)
                 elif cherrypy.request.method == 'GET':
                     for k, v in kwargs.items():
-                        self.engine.msg.write(main_topic, k, yaml.load(v))
-                        return_text += "  {}: {}\n".format(k, yaml.load(v))
+                        self.engine.msg.write(main_topic, k, yaml.load(v), Loader=yaml.SafeLoader)
+                        return_text += "  {}: {}\n".format(k, yaml.load(v), Loader=yaml.SafeLoader)
             except Exception as e:
                 return_text += "Error: " + str(e)
             return_text += "
\n" diff --git a/osm_nbi/tests/test.py b/osm_nbi/tests/test.py index c9a37d8..3033a34 100755 --- a/osm_nbi/tests/test.py +++ b/osm_nbi/tests/test.py @@ -1342,7 +1342,7 @@ class TestDeploy: ns_data.update(self.ns_params) if test_params and test_params.get("ns-config"): if isinstance(test_params["ns-config"], str): - ns_data.update(yaml.load(test_params["ns-config"])) + ns_data.update(yaml.load(test_params["ns-config"]), Loader=yaml.Loader) else: ns_data.update(test_params["ns-config"]) self.instantiate(engine, ns_data) @@ -1833,7 +1833,8 @@ class TestDeployHackfest3Charmed3(TestDeployHackfest3Charmed): parameter: "$[0]": default-value: "" - """) + """, + Loader=yaml.Loader) } self.ns_params = { "additionalParamsForVnf": [ @@ -2135,7 +2136,7 @@ class TestDeployHnfd(TestDeployHackfest3Charmed): "vimAccountId": self.vim_id} if test_params and test_params.get("ns-config"): if isinstance(test_params["ns-config"], str): - ns_data.update(yaml.load(test_params["ns-config"])) + ns_data.update(yaml.load(test_params["ns-config"]), Loader=yaml.Loader) else: ns_data.update(test_params["ns-config"]) -- 2.17.1 From 5f1a6d31a48eeba7ef799738ccfe7d6497f61fc9 Mon Sep 17 00:00:00 2001 From: Felipe Vicens Date: Wed, 16 Oct 2019 20:13:53 +0200 Subject: [PATCH 14/16] Fix IM installation in NBI Change-Id: I10287cdb30aac7496eb28b82a06027d196a26a5b Signed-off-by: Felipe Vicens --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 27bc184..5654da5 100644 --- a/tox.ini +++ b/tox.ini @@ -15,7 +15,7 @@ [tox] envlist = py3 -toxworkdir={homedir}/.tox +toxworkdir={toxinidir}/.tox [testenv] basepython = python3 -- 2.17.1 From c96fd6946c24dacce83fe5a14f95ab88ac54bb38 Mon Sep 17 00:00:00 2001 From: K Sai Kiran Date: Wed, 16 Oct 2019 17:50:53 +0530 Subject: [PATCH 15/16] Bug 864 - NBI publishes created key to kakfa after editing nsd/vnfd. Change-Id: Ib4ab08dcb3cada34bb3f95468315f37e52486fcf Signed-off-by: K Sai Kiran --- osm_nbi/descriptor_topics.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osm_nbi/descriptor_topics.py b/osm_nbi/descriptor_topics.py index 95ced0d..58efa55 100644 --- a/osm_nbi/descriptor_topics.py +++ b/osm_nbi/descriptor_topics.py @@ -139,6 +139,7 @@ class DescriptorTopic(BaseTopic): self.format_on_new(content, session["project_id"], make_public=session["public"]) _id = self.db.create(self.topic, content) rollback.append({"topic": self.topic, "_id": _id}) + self._send_msg("created", {"_id": _id}) return _id, None except ValidationError as e: raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY) @@ -284,7 +285,7 @@ class DescriptorTopic(BaseTopic): self.fs.dir_rename(temp_folder, _id) indata["_id"] = _id - self._send_msg("created", indata) + self._send_msg("edited", indata) # TODO if descriptor has changed because kwargs update content and remove cached zip # TODO if zip is not present creates one -- 2.17.1 From 15a1f68badbe4865b7abb3cb9ac816ed963d4b63 Mon Sep 17 00:00:00 2001 From: tierno Date: Wed, 16 Oct 2019 09:00:13 +0000 Subject: [PATCH 16/16] bug 864 fix operation (created/edited) published to kafka Change-Id: Ia96573a0f79da88d2d893101cc9daed0f46bbc7a Signed-off-by: tierno --- osm_nbi/__init__.py | 4 ++-- osm_nbi/admin_topics.py | 6 +++--- osm_nbi/base_topic.py | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osm_nbi/__init__.py b/osm_nbi/__init__.py index 8e72edf..1be799c 100644 --- a/osm_nbi/__init__.py +++ b/osm_nbi/__init__.py @@ -12,5 +12,5 @@ # under the License. ## -version = '6.0.2.post5' -version_date = '2018-09-13' +version = '6.0.3.post15' +version_date = '2018-10-17' diff --git a/osm_nbi/admin_topics.py b/osm_nbi/admin_topics.py index 71dfa3c..b41e457 100644 --- a/osm_nbi/admin_topics.py +++ b/osm_nbi/admin_topics.py @@ -530,7 +530,7 @@ class UserTopicAuth(UserTopic): rollback.append({"topic": self.topic, "_id": _id}) # del content["password"] - # self._send_msg("create", content) + # self._send_msg("created", content) return _id, None except ValidationError as e: raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY) @@ -814,7 +814,7 @@ class ProjectTopicAuth(ProjectTopic): self.format_on_new(content, project_id=session["project_id"], make_public=session["public"]) _id = self.auth.create_project(content) rollback.append({"topic": self.topic, "_id": _id}) - # self._send_msg("create", content) + # self._send_msg("created", content) return _id, None except ValidationError as e: raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY) @@ -1127,7 +1127,7 @@ class RoleTopicAuth(BaseTopic): content["_id"] = rid # _id = self.db.create(self.topic, content) rollback.append({"topic": self.topic, "_id": rid}) - # self._send_msg("create", content) + # self._send_msg("created", content) return rid, None except ValidationError as e: raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY) diff --git a/osm_nbi/base_topic.py b/osm_nbi/base_topic.py index c22833b..70c8dff 100644 --- a/osm_nbi/base_topic.py +++ b/osm_nbi/base_topic.py @@ -389,7 +389,7 @@ class BaseTopic: rollback.append({"topic": self.topic, "_id": _id}) if op_id: content["op_id"] = op_id - self._send_msg("create", content) + self._send_msg("created", content) return _id, op_id except ValidationError as e: raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY) @@ -510,7 +510,7 @@ class BaseTopic: if op_id: indata["op_id"] = op_id indata["_id"] = _id - self._send_msg("edit", indata) + self._send_msg("edited", indata) return op_id except ValidationError as e: raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY) -- 2.17.1