X-Git-Url: https://osm.etsi.org/gitweb/?p=osm%2FNBI.git;a=blobdiff_plain;f=osm_nbi%2Fnbi.py;h=fa2f0432b2c6838fc0a16afc8f3e60a5e611a571;hp=4468f267a5d4808b06c06e128c5fc197ac089b39;hb=56e698aea30098e7cfc0c5e3df9e771a4dd47f64;hpb=db9dc589ca4ddb7fde50cff07c23b2ea863265cc diff --git a/osm_nbi/nbi.py b/osm_nbi/nbi.py index 4468f26..fa2f043 100644 --- a/osm_nbi/nbi.py +++ b/osm_nbi/nbi.py @@ -10,11 +10,13 @@ import logging import logging.handlers import getopt import sys + +from authconn import AuthException +from auth import Authenticator from engine import Engine, EngineException from osm_common.dbbase import DbException from osm_common.fsbase import FsException from osm_common.msgbase import MsgException -from base64 import standard_b64decode from http import HTTPStatus from codecs import getreader from os import environ, path @@ -25,6 +27,7 @@ __author__ = "Alfonso Tierno " __version__ = "0.1.3" version_date = "Apr 2018" database_version = '1.0' +auth_database_version = '1.0' """ North Bound Interface (O: OSM specific; 5,X: SOL005 not implemented yet; O5: SOL005 implemented) @@ -68,15 +71,15 @@ URL: /osm GET POST /ns_lcm_op_occs 5 5 / 5 5 5 TO BE COMPLETED 5 5 - /vnfrs O - / O + /vnf_instances (also vnfrs for compatibility) O + / O /subscriptions 5 5 / 5 X /admin/v1 /tokens O O / O O /users O O - / O O + / O O O O /projects O O / O O /vims_accounts (also vims for compatibility) O O @@ -145,6 +148,7 @@ class Server(object): def __init__(self): self.instance += 1 self.engine = Engine() + self.authenticator = Authenticator() self.valid_methods = { # contains allowed URL and methods "admin": { "v1": { @@ -152,7 +156,7 @@ class Server(object): "": {"METHODS": ("GET", "DELETE")} }, "users": {"METHODS": ("GET", "POST"), - "": {"METHODS": ("GET", "POST", "DELETE")} + "": {"METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT")} }, "projects": {"METHODS": ("GET", "POST"), "": {"METHODS": ("GET", "DELETE")} @@ -196,7 +200,7 @@ class Server(object): "": {"METHODS": ("GET", "PUT", "DELETE")} }, "vnf_packages": {"METHODS": ("GET", "POST"), - "": {"METHODS": ("GET", "DELETE"), "TODO": "PATCH", # GET: vnfPkgInfo + "": {"METHODS": ("GET", "DELETE", "PATCH"), # GET: vnfPkgInfo "package_content": {"METHODS": ("GET", "PUT"), # package "upload_from_uri": {"TODO": "POST"} }, @@ -216,7 +220,7 @@ class Server(object): }, "ns_instances": {"METHODS": ("GET", "POST"), "": {"METHODS": ("GET", "DELETE"), - "scale": {"TODO": "POST"}, + "scale": {"METHODS": "POST"}, "terminate": {"METHODS": "POST"}, "instantiate": {"METHODS": "POST"}, "action": {"METHODS": "POST"}, @@ -228,52 +232,13 @@ class Server(object): "vnfrs": {"METHODS": ("GET"), "": {"METHODS": ("GET")} }, + "vnf_instances": {"METHODS": ("GET"), + "": {"METHODS": ("GET")} + }, } }, } - def _authorization(self): - token = None - user_passwd64 = None - try: - # 1. Get token Authorization bearer - auth = cherrypy.request.headers.get("Authorization") - if auth: - auth_list = auth.split(" ") - if auth_list[0].lower() == "bearer": - token = auth_list[-1] - elif auth_list[0].lower() == "basic": - user_passwd64 = auth_list[-1] - if not token: - if cherrypy.session.get("Authorization"): - # 2. Try using session before request a new token. If not, basic authentication will generate - token = cherrypy.session.get("Authorization") - if token == "logout": - token = None # force Unauthorized response to insert user pasword again - elif user_passwd64 and cherrypy.request.config.get("auth.allow_basic_authentication"): - # 3. Get new token from user password - user = None - passwd = None - try: - user_passwd = standard_b64decode(user_passwd64).decode() - user, _, passwd = user_passwd.partition(":") - except Exception: - pass - outdata = self.engine.new_token(None, {"username": user, "password": passwd}) - token = outdata["id"] - cherrypy.session['Authorization'] = token - # 4. Get token from cookie - # if not token: - # auth_cookie = cherrypy.request.cookie.get("Authorization") - # if auth_cookie: - # token = auth_cookie.value - return self.engine.authorize(token) - except EngineException as e: - if cherrypy.session.get('Authorization'): - del cherrypy.session['Authorization'] - cherrypy.response.headers["WWW-Authenticate"] = 'Bearer realm="{}"'.format(e) - raise - def _format_in(self, kwargs): try: indata = None @@ -400,7 +365,7 @@ class Server(object): session = None try: if cherrypy.request.method == "GET": - session = self._authorization() + session = self.authenticator.authorize() outdata = "Index page" else: raise cherrypy.HTTPError(HTTPStatus.METHOD_NOT_ALLOWED.value, @@ -408,7 +373,7 @@ class Server(object): return self._format_out(outdata, session) - except EngineException as e: + except (EngineException, AuthException) as e: cherrypy.log("index Exception {}".format(e)) cherrypy.response.status = e.http_code.value return self._format_out("Welcome to OSM!", session) @@ -441,19 +406,19 @@ class Server(object): raise NbiException("Expected application/yaml or application/json Content-Type", HTTPStatus.BAD_REQUEST) try: if method == "GET": - session = self._authorization() + session = self.authenticator.authorize() if token_id: - outdata = self.engine.get_token(session, token_id) + outdata = self.authenticator.get_token(session, token_id) else: - outdata = self.engine.get_token_list(session) + outdata = self.authenticator.get_token_list(session) elif method == "POST": try: - session = self._authorization() + session = self.authenticator.authorize() except Exception: session = None if kwargs: indata.update(kwargs) - outdata = self.engine.new_token(session, indata, cherrypy.request.remote) + outdata = self.authenticator.new_token(session, indata, cherrypy.request.remote) session = outdata cherrypy.session['Authorization'] = outdata["_id"] self._set_location_header("admin", "v1", "tokens", outdata["_id"]) @@ -463,9 +428,9 @@ class Server(object): if not token_id and "id" in kwargs: token_id = kwargs["id"] elif not token_id: - session = self._authorization() + session = self.authenticator.authorize() token_id = session["_id"] - outdata = self.engine.del_token(token_id) + outdata = self.authenticator.del_token(token_id) session = None cherrypy.session['Authorization'] = "logout" # cherrypy.response.cookie["Authorization"] = token_id @@ -473,7 +438,7 @@ class Server(object): else: raise NbiException("Method {} not allowed for token".format(method), HTTPStatus.METHOD_NOT_ALLOWED) return self._format_out(outdata, session) - except (NbiException, EngineException, DbException) as e: + except (NbiException, EngineException, DbException, AuthException) as e: cherrypy.log("tokens Exception {}".format(e)) cherrypy.response.status = e.http_code.value problem_details = { @@ -508,7 +473,7 @@ class Server(object): return f elif len(args) == 2 and args[0] == "db-clear": - return self.engine.del_item_list({"project_id": "admin"}, args[1], {}) + return self.engine.del_item_list({"project_id": "admin", "admin": True}, args[1], kwargs) elif args and args[0] == "prune": return self.engine.prune() elif args and args[0] == "login": @@ -641,7 +606,7 @@ class Server(object): return self.token(method, _id, kwargs) # self.engine.load_dbase(cherrypy.request.app.config) - session = self._authorization() + session = self.authenticator.authorize() indata = self._format_in(kwargs) engine_item = item if item == "subscriptions": @@ -657,7 +622,7 @@ class Server(object): engine_item = "nsrs" if item == "ns_lcm_op_occs": engine_item = "nslcmops" - if item == "vnfrs": + if item == "vnfrs" or item == "vnf_instances": engine_item = "vnfrs" if engine_item == "vims": # TODO this is for backward compatibility, it will remove in the future engine_item = "vim_accounts" @@ -694,7 +659,7 @@ class Server(object): outdata = {"id": _id} elif item == "ns_instances_content": _id = self.engine.new_item(rollback, session, engine_item, indata, kwargs, force=force) - self.engine.ns_operation(rollback, session, _id, "instantiate", {}, None) + self.engine.ns_operation(rollback, session, _id, "instantiate", indata, None) self._set_location_header(topic, version, item, _id) outdata = {"id": _id} elif item == "ns_instances" and item2: @@ -727,6 +692,7 @@ class Server(object): cherrypy.response.status = HTTPStatus.ACCEPTED.value elif method in ("PUT", "PATCH"): + outdata = None if not indata and not kwargs: raise NbiException("Nothing to update. Provide payload and/or query string", HTTPStatus.BAD_REQUEST) @@ -735,14 +701,13 @@ class Server(object): cherrypy.request.headers) if not completed: cherrypy.response.headers["Transaction-Id"] = id - cherrypy.response.status = HTTPStatus.NO_CONTENT.value - outdata = None else: - outdata = {"id": self.engine.edit_item(session, engine_item, _id, indata, kwargs, force=force)} + self.engine.edit_item(session, engine_item, _id, indata, kwargs, force=force) + cherrypy.response.status = HTTPStatus.NO_CONTENT.value else: raise NbiException("Method {} not allowed".format(method), HTTPStatus.METHOD_NOT_ALLOWED) return self._format_out(outdata, session, _format) - except (NbiException, EngineException, DbException, FsException, MsgException) as e: + except (NbiException, EngineException, DbException, FsException, MsgException, AuthException) as e: cherrypy.log("Exception {}".format(e)) cherrypy.response.status = e.http_code.value if hasattr(outdata, "close"): # is an open file @@ -801,12 +766,13 @@ def _start_service(): update_dict['server.socket_host'] = v elif k1 in ("server", "test", "auth", "log"): update_dict[k1 + '.' + k2] = v - elif k1 in ("message", "database", "storage"): + elif k1 in ("message", "database", "storage", "authentication"): # k2 = k2.replace('_', '.') - if k2 == "port": + if k2 in ("port", "db_port"): engine_config[k1][k2] = int(v) else: engine_config[k1][k2] = v + except ValueError as e: cherrypy.log.error("Ignoring environ '{}': " + str(e)) except Exception as e: @@ -858,9 +824,11 @@ def _start_service(): logger_module.setLevel(engine_config[k1]["loglevel"]) # TODO add more entries, e.g.: storage cherrypy.tree.apps['/osm'].root.engine.start(engine_config) + cherrypy.tree.apps['/osm'].root.authenticator.start(engine_config) try: cherrypy.tree.apps['/osm'].root.engine.init_db(target_version=database_version) - except EngineException: + cherrypy.tree.apps['/osm'].root.authenticator.init_db(target_version=auth_database_version) + except (EngineException, AuthException): pass # getenv('OSMOPENMANO_TENANT', None)