From 819d34c8315de5c2010743f499f7882b8957dcbf Mon Sep 17 00:00:00 2001 From: Eduardo Sousa Date: Tue, 31 Jul 2018 01:20:02 +0100 Subject: [PATCH] Adding Authentication Connector plugin system Keystone plugin completion. Pending testing. Change-Id: Iab8cfb3dc72e8d4e0b38a575603c02ab7ffd85de Signed-off-by: Eduardo Sousa --- Dockerfile.local | 42 +++-- osm_nbi/auth.py | 178 +++++++++++++++++--- osm_nbi/authconn.py | 225 +++++++++++++++++++++++++ osm_nbi/authconn_keystone.py | 310 +++++++++++++++++++++++++++++++++++ osm_nbi/html_public/version | 4 +- osm_nbi/nbi.cfg | 2 + osm_nbi/nbi.py | 17 +- setup.py | 2 +- stdeb.cfg | 2 +- 9 files changed, 734 insertions(+), 48 deletions(-) create mode 100644 osm_nbi/authconn.py create mode 100644 osm_nbi/authconn_keystone.py diff --git a/Dockerfile.local b/Dockerfile.local index 1ec838a..ba0bf5a 100644 --- a/Dockerfile.local +++ b/Dockerfile.local @@ -9,9 +9,9 @@ WORKDIR /app/osm_nbi ADD . /app RUN apt-get update && apt-get install -y git python3 python3-jsonschema \ - python3-pymongo python3-yaml python3-pip \ + python3-pymongo python3-yaml python3-pip python3-keystoneclient \ && pip3 install pip==9.0.3 \ - && pip3 install aiokafka cherrypy \ + && pip3 install aiokafka cherrypy==18.0.0 keystoneauth1 \ && mkdir -p /app/storage/kafka && mkdir -p /app/log # OSM_COMMON @@ -43,26 +43,36 @@ VOLUME /app/log # The following ENV can be added with "docker run -e xxx' to configure # server -ENV OSMNBI_SOCKET_HOST 0.0.0.0 -ENV OSMNBI_SOCKET_PORT 9999 +ENV OSMNBI_SOCKET_HOST 0.0.0.0 +ENV OSMNBI_SOCKET_PORT 9999 # storage -ENV OSMNBI_STORAGE_PATH /app/storage +ENV OSMNBI_STORAGE_PATH /app/storage # database -ENV OSMNBI_DATABASE_DRIVER mongo -ENV OSMNBI_DATABASE_HOST mongo -ENV OSMNBI_DATABASE_PORT 27017 +ENV OSMNBI_DATABASE_DRIVER mongo +ENV OSMNBI_DATABASE_HOST mongo +ENV OSMNBI_DATABASE_PORT 27017 # web -ENV OSMNBI_STATIC_DIR /app/osm_nbi/html_public +ENV OSMNBI_STATIC_DIR /app/osm_nbi/html_public # logs -ENV OSMNBI_LOG_FILE /app/log -ENV OSMNBI_LOG_LEVEL DEBUG +ENV OSMNBI_LOG_FILE /app/log +ENV OSMNBI_LOG_LEVEL DEBUG # message -ENV OSMNBI_MESSAGE_DRIVER kafka -ENV OSMNBI_MESSAGE_HOST kafka -ENV OSMNBI_MESSAGE_PORT 9092 +ENV OSMNBI_MESSAGE_DRIVER kafka +ENV OSMNBI_MESSAGE_HOST kafka +ENV OSMNBI_MESSAGE_PORT 9092 # logs -ENV OSMNBI_LOG_FILE /app/log/nbi.log -ENV OSMNBI_LOG_LEVEL DEBUG +ENV OSMNBI_LOG_FILE /app/log/nbi.log +ENV OSMNBI_LOG_LEVEL DEBUG +# authentication +ENV OSMNBI_AUTHENTICATION_BACKEND internal +#ENV OSMNBI_AUTHENTICATION_BACKEND keystone +#ENV OSMNBI_AUTHENTICATION_AUTH_URL keystone +#ENV OSMNBI_AUTHENTICATION_AUTH_PORT 5000 +#ENV OSMNBI_AUTHENTICATION_USER_DOMAIN_NAME default +#ENV OSMNBI_AUTHENTICATION_PROJECT_DOMAIN_NAME default +#ENV OSMNBI_AUTHENTICATION_SERVICE_USERNAME nbi +#ENV OSMNBI_AUTHENTICATION_SERVICE_PASSWORD nbi +#ENV OSMNBI_AUTHENTICATION_SERVICE_PROJECT service # Run app.py when the container launches CMD ["python3", "nbi.py"] diff --git a/osm_nbi/auth.py b/osm_nbi/auth.py index 572ab88..4ee9ce2 100644 --- a/osm_nbi/auth.py +++ b/osm_nbi/auth.py @@ -1,25 +1,90 @@ -import cherrypy +# -*- coding: utf-8 -*- + +""" +Authenticator is responsible for authenticating the users, +create the tokens unscoped and scoped, retrieve the role +list inside the projects that they are inserted +""" + +__author__ = "Eduardo Sousa " +__date__ = "$27-jul-2018 23:59:59$" + +import logging from base64 import standard_b64decode +from copy import deepcopy +from functools import reduce from http import HTTPStatus +from time import time +import cherrypy +from authconn import AuthException +from authconn_keystone import AuthconnKeystone from engine import EngineException -__author__ = "Eduardo Sousa " +class Authenticator: + """ + This class should hold all the mechanisms for User Authentication and + Authorization. Initially it should support Openstack Keystone as a + backend through a plugin model where more backends can be added and a + RBAC model to manage permissions on operations. + """ -class AuthenticatorException(Exception): - def __init__(self, message, http_code=HTTPStatus.UNAUTHORIZED): - self.http_code = http_code - Exception.__init__(self, message) + def __init__(self, engine): + """ + Authenticator initializer. Setup the initial state of the object, + while it waits for the config dictionary and database initialization. + Note: engine is only here until all the calls can to it can be replaced. -class Authenticator(object): - def __init__(self, engine): + :param engine: reference to engine object used. + """ super().__init__() self.engine = engine + self.backend = None + self.config = None + self.db = None + self.tokens = dict() + self.logger = logging.getLogger("nbi.authenticator") + + def start(self, config): + """ + Method to configure the Authenticator object. This method should be called + after object creation. It is responsible by initializing the selected backend, + as well as the initialization of the database connection. + + :param config: dictionary containing the relevant parameters for this object. + """ + self.config = config + + try: + if not self.backend: + if config["authentication"]["backend"] == "keystone": + self.backend = AuthconnKeystone(self.config["authentication"]) + elif config["authentication"]["backend"] == "internal": + pass + else: + raise Exception("No authentication backend defined") + if not self.db: + pass + # TODO: Implement database initialization + # NOTE: Database needed to store the mappings + except Exception as e: + raise AuthException(str(e)) + + def init_db(self, target_version='1.0'): + """ + Check if the database has been initialized. If not, create the required tables + and insert the predefined mappings between roles and permissions. + + :param target_version: schema version that should be present in the database. + :return: None if OK, exception if error or version is different. + """ + pass + def authorize(self): token = None user_passwd64 = None @@ -47,29 +112,98 @@ class Authenticator(object): user, _, passwd = user_passwd.partition(":") except Exception: pass - outdata = self.engine.new_token(None, {"username": user, "password": passwd}) + outdata = self.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) + if self.config["authentication"]["backend"] == "internal": + return self.engine.authorize(token) + else: + try: + self.backend.validate_token(token) + return self.tokens[token] + except AuthException: + self.del_token(token) + raise except EngineException as e: if cherrypy.session.get('Authorization'): del cherrypy.session['Authorization'] cherrypy.response.headers["WWW-Authenticate"] = 'Bearer realm="{}"'.format(e) - raise AuthenticatorException(str(e)) + raise AuthException(str(e)) def new_token(self, session, indata, remote): - return self.engine.new_token(session, indata, remote) + if self.config["authentication"]["backend"] == "internal": + return self.engine.new_token(session, indata, remote) + else: + if indata.get("username"): + token, projects = self.backend.authenticate_with_user_password( + indata.get("username"), indata.get("password")) + elif session: + token, projects = self.backend.authenticate_with_token( + session.get("id"), indata.get("project_id")) + else: + raise AuthException("Provide credentials: username/password or Authorization Bearer token", + http_code=HTTPStatus.UNAUTHORIZED) + + if indata.get("project_id"): + project_id = indata.get("project_id") + if project_id not in projects: + raise AuthException("Project {} not allowed for this user".format(project_id), + http_code=HTTPStatus.UNAUTHORIZED) + else: + project_id = projects[0] + + if project_id == "admin": + session_admin = True + else: + session_admin = reduce(lambda x, y: x or (True if y == "admin" else False), + projects, False) + + now = time() + new_session = { + "_id": token, + "id": token, + "issued_at": now, + "expires": now+3600, + "project_id": project_id, + "username": indata.get("username") if not session else session.get("username"), + "remote_port": remote.port, + "admin": session_admin + } + + if remote.name: + new_session["remote_host"] = remote.name + elif remote.ip: + new_session["remote_host"] = remote.ip + + self.tokens[token] = new_session + + return deepcopy(new_session) def get_token_list(self, session): - return self.engine.get_token_list(session) + if self.config["authentication"]["backend"] == "internal": + return self.engine.get_token_list(session) + else: + return [deepcopy(token) for token in self.tokens.values() + if token["username"] == session["username"]] - def get_token(self, session, token_id): - return self.engine.get_token(session, token_id) + def get_token(self, session, token): + if self.config["authentication"]["backend"] == "internal": + return self.engine.get_token(session, token) + else: + token_value = self.tokens.get(token) + if not token_value: + raise EngineException("token not found", http_code=HTTPStatus.NOT_FOUND) + if token_value["username"] != session["username"] and not session["admin"]: + raise EngineException("needed admin privileges", http_code=HTTPStatus.UNAUTHORIZED) + return token_value - def del_token(self, token_id): - return self.engine.del_token(token_id) + def del_token(self, token): + if self.config["authentication"]["backend"] == "internal": + return self.engine.del_token(token) + else: + try: + self.backend.revoke_token(token) + del self.tokens[token] + return "token '{}' deleted".format(token) + except KeyError: + raise EngineException("Token '{}' not found".format(token), http_code=HTTPStatus.NOT_FOUND) diff --git a/osm_nbi/authconn.py b/osm_nbi/authconn.py new file mode 100644 index 0000000..4d28bf8 --- /dev/null +++ b/osm_nbi/authconn.py @@ -0,0 +1,225 @@ +# -*- coding: utf-8 -*- + +""" +Authconn implements an Abstract class for the Auth backend connector +plugins with the definition of the methods to be implemented. +""" + +__author__ = "Eduardo Sousa " +__date__ = "$27-jul-2018 23:59:59$" + +from http import HTTPStatus + + +class AuthException(Exception): + """ + Authentication error. + """ + def __init__(self, message, http_code=HTTPStatus.UNAUTHORIZED): + self.http_code = http_code + Exception.__init__(self, message) + + +class AuthconnException(Exception): + """ + Common and base class Exception for all authconn exceptions. + """ + def __init__(self, message, http_code=HTTPStatus.UNAUTHORIZED): + Exception.__init__(message) + self.http_code = http_code + + +class AuthconnConnectionException(AuthconnException): + """ + Connectivity error with Auth backend. + """ + def __init__(self, message, http_code=HTTPStatus.BAD_GATEWAY): + AuthconnException.__init__(self, message, http_code) + + +class AuthconnNotSupportedException(AuthconnException): + """ + The request is not supported by the Auth backend. + """ + def __init__(self, message, http_code=HTTPStatus.NOT_IMPLEMENTED): + AuthconnException.__init__(self, message, http_code) + + +class AuthconnNotImplementedException(AuthconnException): + """ + The method is not implemented by the Auth backend. + """ + def __init__(self, message, http_code=HTTPStatus.NOT_IMPLEMENTED): + AuthconnException.__init__(self, message, http_code) + + +class AuthconnOperationException(AuthconnException): + """ + The operation executed failed. + """ + def __init__(self, message, http_code=HTTPStatus.INTERNAL_SERVER_ERROR): + AuthconnException.__init__(self, message, http_code) + + +class Authconn: + """ + Abstract base class for all the Auth backend connector plugins. + Each Auth backend connector plugin must be a subclass of + Authconn class. + """ + def __init__(self, config): + """ + Constructor of the Authconn class. + + Note: each subclass + + :param config: configuration dictionary containing all the + necessary configuration parameters. + """ + self.config = config + + def authenticate_with_user_password(self, user, password): + """ + Authenticate a user using username and password. + + :param user: username + :param password: password + :return: an unscoped token that grants access to project list + """ + raise AuthconnNotImplementedException("Should have implemented this") + + def authenticate_with_token(self, token, project=None): + """ + Authenticate a user using a token. Can be used to revalidate the token + or to get a scoped token. + + :param token: a valid token. + :param project: (optional) project for a scoped token. + :return: return a revalidated token, scoped if a project was passed or + the previous token was already scoped. + """ + raise AuthconnNotImplementedException("Should have implemented this") + + def validate_token(self, token): + """ + Check if the token is valid. + + :param token: token to validate + :return: dictionary with information associated with the token. If the + token is not valid, returns None. + """ + raise AuthconnNotImplementedException("Should have implemented this") + + def revoke_token(self, token): + """ + Invalidate a token. + + :param token: token to be revoked + """ + raise AuthconnNotImplementedException("Should have implemented this") + + def get_project_list(self, token): + """ + Get all the projects associated with a user. + + :param token: valid token + :return: list of projects + """ + raise AuthconnNotImplementedException("Should have implemented this") + + def get_role_list(self, token): + """ + Get role list for a scoped project. + + :param token: scoped token. + :return: returns the list of roles for the user in that project. If + the token is unscoped it returns None. + """ + raise AuthconnNotImplementedException("Should have implemented this") + + def create_user(self, user, password): + """ + Create a user. + + :param user: username. + :param password: password. + :raises AuthconnOperationException: if user creation failed. + """ + raise AuthconnNotImplementedException("Should have implemented this") + + def change_password(self, user, new_password): + """ + Change the user password. + + :param user: username. + :param new_password: new password. + :raises AuthconnOperationException: if user password change failed. + """ + raise AuthconnNotImplementedException("Should have implemented this") + + def delete_user(self, user): + """ + Delete user. + + :param user: username. + :raises AuthconnOperationException: if user deletion failed. + """ + raise AuthconnNotImplementedException("Should have implemented this") + + def create_role(self, role): + """ + Create a role. + + :param role: role name. + :raises AuthconnOperationException: if role creation failed. + """ + raise AuthconnNotImplementedException("Should have implemented this") + + def delete_role(self, role): + """ + Delete a role. + + :param role: role name. + :raises AuthconnOperationException: if user deletion failed. + """ + raise AuthconnNotImplementedException("Should have implemented this") + + def create_project(self, project): + """ + Create a project. + + :param project: project name. + :raises AuthconnOperationException: if project creation failed. + """ + raise AuthconnNotImplementedException("Should have implemented this") + + def delete_project(self, project): + """ + Delete a project. + + :param project: project name. + :raises AuthconnOperationException: if project deletion failed. + """ + raise AuthconnNotImplementedException("Should have implemented this") + + def assign_role_to_user(self, user, project, role): + """ + Assigning a role to a user in a project. + + :param user: username. + :param project: project name. + :param role: role name. + :raises AuthconnOperationException: if role assignment failed. + """ + raise AuthconnNotImplementedException("Should have implemented this") + + def remove_role_from_user(self, user, project, role): + """ + Remove a role from a user in a project. + + :param user: username. + :param project: project name. + :param role: role name. + :raises AuthconnOperationException: if role assignment revocation failed. + """ + raise AuthconnNotImplementedException("Should have implemented this") diff --git a/osm_nbi/authconn_keystone.py b/osm_nbi/authconn_keystone.py new file mode 100644 index 0000000..6e33ed6 --- /dev/null +++ b/osm_nbi/authconn_keystone.py @@ -0,0 +1,310 @@ +# -*- coding: utf-8 -*- + +""" +AuthconnKeystone implements implements the connector for +Openstack Keystone and leverages the RBAC model, to bring +it for OSM. +""" + +__author__ = "Eduardo Sousa " +__date__ = "$27-jul-2018 23:59:59$" + +from authconn import Authconn, AuthException, AuthconnOperationException + +import logging +from keystoneauth1 import session +from keystoneauth1.identity import v3 +from keystoneauth1.exceptions.base import ClientException +from keystoneclient.v3 import client +from http import HTTPStatus + + +class AuthconnKeystone(Authconn): + def __init__(self, config): + Authconn.__init__(self, config) + + self.logger = logging.getLogger("nbi.authenticator.keystone") + + self.auth_url = "http://{0}:{1}/v3".format(config.get("auth_url", "keystone"), config.get("auth_port", "5000")) + self.user_domain_name = config.get("user_domain_name", "default") + self.admin_project = config.get("service_project", "service") + self.admin_username = config.get("service_username", "nbi") + self.admin_password = config.get("service_password", "nbi") + self.project_domain_name = config.get("project_domain_name", "default") + + self.auth = v3.Password(user_domain_name=self.user_domain_name, + username=self.admin_username, + password=self.admin_password, + project_domain_name=self.project_domain_name, + project_name=self.admin_project, + auth_url=self.auth_url) + self.sess = session.Session(auth=self.auth) + self.keystone = client.Client(session=self.sess) + + def authenticate_with_user_password(self, user, password): + """ + Authenticate a user using username and password. + + :param user: username + :param password: password + :return: an unscoped token that grants access to project list + """ + try: + user_id = list(filter(lambda x: x.name == user, self.keystone.users.list()))[0].id + project_names = [project.name for project in self.keystone.projects.list(user=user_id)] + + token = self.keystone.get_raw_token_from_identity_service( + auth_url=self.auth_url, + username=user, + password=password, + user_domain_name=self.user_domain_name, + project_domain_name=self.project_domain_name) + + return token["auth_token"], project_names + except ClientException: + self.logger.exception("Error during user authentication using keystone. Method: basic") + raise AuthException("Error during user authentication using Keystone", http_code=HTTPStatus.UNAUTHORIZED) + + def authenticate_with_token(self, token, project=None): + """ + Authenticate a user using a token. Can be used to revalidate the token + or to get a scoped token. + + :param token: a valid token. + :param project: (optional) project for a scoped token. + :return: return a revalidated token, scoped if a project was passed or + the previous token was already scoped. + """ + try: + token_info = self.keystone.tokens.validate(token=token) + projects = self.keystone.projects.list(user=token_info["user"]["id"]) + project_names = [project.name for project in projects] + + token = self.keystone.get_raw_token_from_identity_service( + auth_url=self.auth_url, + token=token, + project_name=project, + user_domain_name=self.user_domain_name, + project_domain_name=self.project_domain_name) + + return token["auth_token"], project_names + except ClientException: + self.logger.exception("Error during user authentication using keystone. Method: bearer") + raise AuthException("Error during user authentication using Keystone", http_code=HTTPStatus.UNAUTHORIZED) + + def validate_token(self, token): + """ + Check if the token is valid. + + :param token: token to validate + :return: dictionary with information associated with the token. If the + token is not valid, returns None. + """ + if not token: + return + + try: + token_info = self.keystone.tokens.validate(token=token) + + return token_info + except ClientException: + self.logger.exception("Error during token validation using keystone") + raise AuthException("Error during token validation using Keystone", http_code=HTTPStatus.UNAUTHORIZED) + + def revoke_token(self, token): + """ + Invalidate a token. + + :param token: token to be revoked + """ + try: + self.keystone.tokens.revoke_token(token=token) + + return True + except ClientException: + self.logger.exception("Error during token revocation using keystone") + raise AuthException("Error during token revocation using Keystone", http_code=HTTPStatus.UNAUTHORIZED) + + def get_project_list(self, token): + """ + Get all the projects associated with a user. + + :param token: valid token + :return: list of projects + """ + try: + token_info = self.keystone.tokens.validate(token=token) + projects = self.keystone.projects.list(user=token_info["user"]["id"]) + project_names = [project.name for project in projects] + + return project_names + except ClientException: + self.logger.exception("Error during user project listing using keystone") + raise AuthException("Error during user project listing using Keystone", http_code=HTTPStatus.UNAUTHORIZED) + + def get_role_list(self, token): + """ + Get role list for a scoped project. + + :param token: scoped token. + :return: returns the list of roles for the user in that project. If + the token is unscoped it returns None. + """ + try: + token_info = self.keystone.tokens.validate(token=token) + roles = self.keystone.roles.list(user=token_info["user"]["id"], project=token_info["project"]["id"]) + + return roles + except ClientException: + self.logger.exception("Error during user role listing using keystone") + raise AuthException("Error during user role listing using Keystone", http_code=HTTPStatus.UNAUTHORIZED) + + def create_user(self, user, password): + """ + Create a user. + + :param user: username. + :param password: password. + :raises AuthconnOperationException: if user creation failed. + """ + try: + result = self.keystone.users.create(user, password=password, domain=self.user_domain_name) + + if not result: + raise ClientException() + except ClientException: + self.logger.exception("Error during user creation using keystone") + raise AuthconnOperationException("Error during user creation using Keystone") + + def change_password(self, user, new_password): + """ + Change the user password. + + :param user: username. + :param new_password: new password. + :raises AuthconnOperationException: if user password change failed. + """ + try: + result = self.keystone.users.update(user, password=new_password) + + if not result: + raise ClientException() + except ClientException: + self.logger.exception("Error during user password update using keystone") + raise AuthconnOperationException("Error during user password update using Keystone") + + def delete_user(self, user): + """ + Delete user. + + :param user: username. + :raises AuthconnOperationException: if user deletion failed. + """ + try: + result = self.keystone.users.delete(user) + + if not result: + raise ClientException() + except ClientException: + self.logger.exception("Error during user deletion using keystone") + raise AuthconnOperationException("Error during user deletion using Keystone") + + def create_role(self, role): + """ + Create a role. + + :param role: role name. + :raises AuthconnOperationException: if role creation failed. + """ + try: + result = self.keystone.roles.create(role, domain=self.user_domain_name) + + if not result: + raise ClientException() + except ClientException: + self.logger.exception("Error during role creation using keystone") + raise AuthconnOperationException("Error during role creation using Keystone") + + def delete_role(self, role): + """ + Delete a role. + + :param role: role name. + :raises AuthconnOperationException: if role deletion failed. + """ + try: + result = self.keystone.roles.delete(role) + + if not result: + raise ClientException() + except ClientException: + self.logger.exception("Error during role deletion using keystone") + raise AuthconnOperationException("Error during role deletion using Keystone") + + def create_project(self, project): + """ + Create a project. + + :param project: project name. + :raises AuthconnOperationException: if project creation failed. + """ + try: + result = self.keystone.project.create(project, self.project_domain_name) + + if not result: + raise ClientException() + except ClientException: + self.logger.exception("Error during project creation using keystone") + raise AuthconnOperationException("Error during project creation using Keystone") + + def delete_project(self, project): + """ + Delete a project. + + :param project: project name. + :raises AuthconnOperationException: if project deletion failed. + """ + try: + result = self.keystone.project.delete(project) + + if not result: + raise ClientException() + except ClientException: + self.logger.exception("Error during project deletion using keystone") + raise AuthconnOperationException("Error during project deletion using Keystone") + + def assign_role_to_user(self, user, project, role): + """ + Assigning a role to a user in a project. + + :param user: username. + :param project: project name. + :param role: role name. + :raises AuthconnOperationException: if role assignment failed. + """ + try: + result = self.keystone.roles.grant(role, user=user, project=project) + + if not result: + raise ClientException() + except ClientException: + self.logger.exception("Error during user role assignment using keystone") + raise AuthconnOperationException("Error during user role assignment using Keystone") + + def remove_role_from_user(self, user, project, role): + """ + Remove a role from a user in a project. + + :param user: username. + :param project: project name. + :param role: role name. + :raises AuthconnOperationException: if role assignment revocation failed. + """ + try: + result = self.keystone.roles.revoke(role, user=user, project=project) + + if not result: + raise ClientException() + except ClientException: + self.logger.exception("Error during user role revocation using keystone") + raise AuthconnOperationException("Error during user role revocation using Keystone") diff --git a/osm_nbi/html_public/version b/osm_nbi/html_public/version index 03db56b..96460cd 100644 --- a/osm_nbi/html_public/version +++ b/osm_nbi/html_public/version @@ -1,3 +1,3 @@ -0.1.17 -2018-09-12 +0.1.18 +2018-09-26 diff --git a/osm_nbi/nbi.cfg b/osm_nbi/nbi.cfg index ffe407d..07956ed 100644 --- a/osm_nbi/nbi.cfg +++ b/osm_nbi/nbi.cfg @@ -73,3 +73,5 @@ port: 9092 loglevel: "DEBUG" #logfile: /var/log/osm/nbi-message.log +[authentication] +backend: "internal" diff --git a/osm_nbi/nbi.py b/osm_nbi/nbi.py index 262e349..e42284f 100644 --- a/osm_nbi/nbi.py +++ b/osm_nbi/nbi.py @@ -11,6 +11,7 @@ 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 @@ -26,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) @@ -371,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) @@ -436,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 = { @@ -705,7 +707,7 @@ class Server(object): 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 @@ -764,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: @@ -821,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) diff --git a/setup.py b/setup.py index f4c9f0d..d1a1033 100644 --- a/setup.py +++ b/setup.py @@ -34,7 +34,7 @@ setup( "git+https://osm.etsi.org/gerrit/osm/common.git@master#egg=osm-common-0.1.4" ], install_requires=[ - 'CherryPy', 'pymongo', 'jsonschema', 'PyYAML', + 'CherryPy', 'pymongo', 'jsonschema', 'PyYAML', 'python-keystoneclient' # 'osm-common', ], setup_requires=['setuptools-version-command'], diff --git a/stdeb.cfg b/stdeb.cfg index 72970ec..f7300ed 100644 --- a/stdeb.cfg +++ b/stdeb.cfg @@ -1,2 +1,2 @@ [DEFAULT] -Depends: python3-cherrypy3, python3-pymongo, python3-yaml, python3-jsonschema +Depends: python3-cherrypy3, python3-pymongo, python3-yaml, python3-jsonschema, python3-keystoneclient -- 2.17.1