Keystone plugin completion. Pending testing.
Change-Id: Iab8cfb3dc72e8d4e0b38a575603c02ab7ffd85de
Signed-off-by: Eduardo Sousa <esousa@whitestack.com>
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
# 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"]
-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 <esousa@whitestack.com>"
+__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 <eduardosousa@av.it.pt>"
+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
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)
--- /dev/null
+# -*- 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 <esousa@whitestack.com>"
+__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")
--- /dev/null
+# -*- coding: utf-8 -*-
+
+"""
+AuthconnKeystone implements implements the connector for
+Openstack Keystone and leverages the RBAC model, to bring
+it for OSM.
+"""
+
+__author__ = "Eduardo Sousa <esousa@whitestack.com>"
+__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")
-0.1.17
-2018-09-12
+0.1.18
+2018-09-26
loglevel: "DEBUG"
#logfile: /var/log/osm/nbi-message.log
+[authentication]
+backend: "internal"
import getopt
import sys
+from authconn import AuthException
from auth import Authenticator
from engine import Engine, EngineException
from osm_common.dbbase import DbException
__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)
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)
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 = {
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
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:
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)
"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'],
[DEFAULT]
-Depends: python3-cherrypy3, python3-pymongo, python3-yaml, python3-jsonschema
+Depends: python3-cherrypy3, python3-pymongo, python3-yaml, python3-jsonschema, python3-keystoneclient