Fix allowed methods for administration topics
[osm/NBI.git] / osm_nbi / nbi.py
index 1b3a6ce..e2419cf 100644 (file)
@@ -18,28 +18,30 @@ import cherrypy
 import time
 import json
 import yaml
-import html_out as html
+import osm_nbi.html_out as html
 import logging
 import logging.handlers
 import getopt
 import sys
 
-from authconn import AuthException
-from auth import Authenticator
-from engine import Engine, EngineException
-from subscriptions import SubscriptionThread
-from validation import ValidationError
+from osm_nbi.authconn import AuthException, AuthconnException
+from osm_nbi.auth import Authenticator
+from osm_nbi.engine import Engine, EngineException
+from osm_nbi.subscriptions import SubscriptionThread
+from osm_nbi.validation import ValidationError
 from osm_common.dbbase import DbException
 from osm_common.fsbase import FsException
 from osm_common.msgbase import MsgException
 from http import HTTPStatus
 from codecs import getreader
 from os import environ, path
+from osm_nbi import version as nbi_version, version_date as nbi_version_date
 
 __author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
 
-__version__ = "0.1.3"
-version_date = "Jan 2019"
+__version__ = "0.1.3"    # file version, not NBI version
+version_date = "Aug 2019"
+
 database_version = '1.2'
 auth_database_version = '1.0'
 nbi_server = None           # instance of Server class
@@ -110,6 +112,10 @@ URL: /osm                                                       GET     POST
                 /<id>                                           O                       O       O
             /sdns                                               O       O
                 /<id>                                           O                       O       O
+            /k8sclusters                                        O       O
+                /<id>                                           O                       O       O
+            /k8srepos                                           O       O
+                /<id>                                           O                               O
 
         /nst/v1                                                 O       O
             /netslice_templates_content                         O       O
@@ -210,45 +216,58 @@ valid_url_methods = {
                        },
             "users": {"METHODS": ("GET", "POST"),
                       "ROLE_PERMISSION": "users:",
-                      "<ID>": {"METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT"),
+                      "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
                                "ROLE_PERMISSION": "users:id:"
                                }
                       },
             "projects": {"METHODS": ("GET", "POST"),
                          "ROLE_PERMISSION": "projects:",
-                         "<ID>": {"METHODS": ("GET", "DELETE", "PUT"),
+                         "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
                                   "ROLE_PERMISSION": "projects:id:"}
                          },
             "roles": {"METHODS": ("GET", "POST"),
                       "ROLE_PERMISSION": "roles:",
-                      "<ID>": {"METHODS": ("GET", "POST", "DELETE", "PUT"),
+                      "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
                                "ROLE_PERMISSION": "roles:id:"
                                }
                       },
             "vims": {"METHODS": ("GET", "POST"),
                      "ROLE_PERMISSION": "vims:",
-                     "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT"),
+                     "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
                               "ROLE_PERMISSION": "vims:id:"
                               }
                      },
             "vim_accounts": {"METHODS": ("GET", "POST"),
                              "ROLE_PERMISSION": "vim_accounts:",
-                             "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT"),
+                             "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
                                       "ROLE_PERMISSION": "vim_accounts:id:"
                                       }
                              },
             "wim_accounts": {"METHODS": ("GET", "POST"),
                              "ROLE_PERMISSION": "wim_accounts:",
-                             "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT"),
+                             "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
                                       "ROLE_PERMISSION": "wim_accounts:id:"
                                       }
                              },
             "sdns": {"METHODS": ("GET", "POST"),
                      "ROLE_PERMISSION": "sdn_controllers:",
-                     "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT"),
+                     "<ID>": {"METHODS": ("GET", "DELETE", "PATCH"),
                               "ROLE_PERMISSION": "sdn_controllers:id:"
                               }
                      },
+            "k8sclusters": {"METHODS": ("GET", "POST"),
+                            "ROLE_PERMISSION": "k8sclusters:",
+                            "<ID>": {"METHODS": ("GET", "DELETE", "PATCH", "PUT"),
+                                     "ROLE_PERMISSION": "k8sclusters:id:"
+                                     }
+                            },
+            "k8srepos": {"METHODS": ("GET", "POST"),
+                         "ROLE_PERMISSION": "k8srepos:",
+                         "<ID>": {"METHODS": ("GET", "DELETE"),
+                                  "ROLE_PERMISSION": "k8srepos:id:"
+                                  }
+                         },
+
         }
     },
     "pdu": {
@@ -479,7 +498,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 \
@@ -499,11 +518,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 = {}
@@ -518,7 +537,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"):
@@ -537,7 +556,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
 
@@ -555,7 +574,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")
@@ -598,7 +617,7 @@ class Server(object):
         try:
             if cherrypy.request.method == "GET":
                 token_info = self.authenticator.authorize()
-                outdata = "Index page"
+                outdata = token_info   # Home page
             else:
                 raise cherrypy.HTTPError(HTTPStatus.METHOD_NOT_ALLOWED.value,
                                          "Method {} not allowed for tokens".format(cherrypy.request.method))
@@ -613,13 +632,14 @@ class Server(object):
     @cherrypy.expose
     def version(self, *args, **kwargs):
         # TODO consider to remove and provide version using the static version file
-        global __version__, version_date
         try:
             if cherrypy.request.method != "GET":
                 raise NbiException("Only method GET is allowed", HTTPStatus.METHOD_NOT_ALLOWED)
             elif args or kwargs:
                 raise NbiException("Invalid URL or query string for version", HTTPStatus.METHOD_NOT_ALLOWED)
-            return __version__ + " " + version_date
+            # TODO include version of other modules, pick up from some kafka admin message
+            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 = {
@@ -752,14 +772,14 @@ class Server(object):
             return_text = "<html><pre>{} ->\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 += "</pre></html>\n"
@@ -858,7 +878,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:
@@ -941,8 +962,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
@@ -1079,7 +1099,7 @@ class Server(object):
                     if not delete_in_process:
                         self.engine.del_item(engine_session, engine_topic, _id)
                         cherrypy.response.status = HTTPStatus.NO_CONTENT.value
-                if engine_topic in ("vim_accounts", "wim_accounts", "sdns"):
+                if engine_topic in ("vim_accounts", "wim_accounts", "sdns", "k8sclusters", "k8srepos"):
                     cherrypy.response.status = HTTPStatus.ACCEPTED.value
 
             elif method in ("PUT", "PATCH"):
@@ -1110,7 +1130,7 @@ class Server(object):
             return self._format_out(outdata, token_info, _format)
         except Exception as e:
             if isinstance(e, (NbiException, EngineException, DbException, FsException, MsgException, AuthException,
-                              ValidationError)):
+                              ValidationError, AuthconnException)):
                 http_code_value = cherrypy.response.status = e.http_code.value
                 http_code_name = e.http_code.name
                 cherrypy.log("Exception {}".format(e))
@@ -1253,9 +1273,10 @@ def _start_service():
 
     # load and print version. Ignore possible errors, e.g. file not found
     try:
-        with open("{}/version".format(engine_config["/static"]['tools.staticdir.dir'])) as version_file:
-            version_data = version_file.read()
-            cherrypy.log.error("Starting OSM NBI Version: {}".format(version_data.replace("\n", " ")))
+        backend = engine_config["authentication"]["backend"]
+        nbi_version
+        cherrypy.log.error("Starting OSM NBI Version '{}' with '{}' authentication backend"
+                           .format(nbi_version + " " + nbi_version_date, backend))
     except Exception:
         pass