from validation import ValidationError
from validation import is_valid_uuid # To check that User/Project Names don't look like UUIDs
from base_topic import BaseTopic, EngineException
+from authconn_keystone import AuthconnKeystone
__author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
raise EngineException("username '{}' exists".format(indata["username"]), HTTPStatus.CONFLICT)
# check projects
if not session["force"]:
- for p in indata.get("projects"):
+ for p in indata.get("projects") or []:
# To allow project addressing by Name as well as ID
if not self.db.get_one("projects", {BaseTopic.id_field("projects", p): p}, fail_on_empty=False,
fail_on_more=False):
"""
username = indata.get("username")
if is_valid_uuid(username):
- raise EngineException("username '{}' cannot be a uuid format".format(username),
+ raise EngineException("username '{}' cannot have a uuid format".format(username),
HTTPStatus.UNPROCESSABLE_ENTITY)
# Check that username is not used, regardless keystone already checks this
if "username" in edit_content:
username = edit_content.get("username")
if is_valid_uuid(username):
- raise EngineException("username '{}' cannot be an uuid format".format(username),
+ raise EngineException("username '{}' cannot have an uuid format".format(username),
HTTPStatus.UNPROCESSABLE_ENTITY)
# Check that username is not used, regardless keystone already checks this
"""
project_name = indata.get("name")
if is_valid_uuid(project_name):
- raise EngineException("project name '{}' cannot be an uuid format".format(project_name),
+ raise EngineException("project name '{}' cannot have an uuid format".format(project_name),
HTTPStatus.UNPROCESSABLE_ENTITY)
project_list = self.auth.get_project_list(filter_q={"name": project_name})
class RoleTopicAuth(BaseTopic):
- topic = "roles_operations"
- topic_msg = None # "roles"
+ topic = "roles"
+ topic_msg = None # "roles"
schema_new = roles_new_schema
schema_edit = roles_edit_schema
multiproject = False
BaseTopic.__init__(self, db, fs, msg)
self.auth = auth
self.operations = ops
+ self.topic = "roles_operations" if isinstance(auth, AuthconnKeystone) else "roles"
@staticmethod
def validate_role_definition(operations, role_definitions):
:return: None if ok or raises EngineException with the conflict
"""
roles = self.auth.get_role_list()
- system_admin_role = [role for role in roles
- if role["name"] == "system_admin"][0]
+ system_admin_roles = [role for role in roles if role["name"] == "system_admin"]
- if _id == system_admin_role["_id"]:
+ if system_admin_roles and _id == system_admin_roles[0]["_id"]:
raise EngineException("You cannot delete system_admin role", http_code=HTTPStatus.FORBIDDEN)
@staticmethod
# :return: dictionary, raise exception if not found.
# """
# filter_db = {"_id": _id}
+ # filter_db = { BaseTopic.id_field(self.topic, _id): _id } # To allow role addressing by name
#
# role = self.db.get_one(self.topic, filter_db)
# new_role = dict(role)
:return: dictionary with deleted item _id. It raises EngineException on error: not found, conflict, ...
"""
self.check_conflict_on_del(session, _id, None)
- filter_q = {"_id": _id}
+ # filter_q = {"_id": _id}
+ filter_q = {BaseTopic.id_field(self.topic, _id): _id} # To allow role addressing by name
if not dry_run:
self.auth.delete_role(_id)
v = self.db.del_one(self.topic, filter_q)
from base64 import standard_b64decode
from copy import deepcopy
# from functools import reduce
-from hashlib import sha256
from http import HTTPStatus
-from random import choice as random_choice
from time import time
from os import path
-from base_topic import BaseTopic # To allow project names in project_id
from authconn import AuthException, AuthExceptionUnauthorized
from authconn_keystone import AuthconnKeystone
+from authconn_internal import AuthconnInternal # Comment out for testing&debugging, uncomment when ready
from osm_common import dbmongo
from osm_common import dbmemory
from osm_common.dbbase import DbException
+from uuid import uuid4 # For Role _id with internal authentication backend
+
class Authenticator:
"""
self.next_db_prune_time = 0 # time when next cleaning of expired tokens must be done
self.resources_to_operations_file = None
self.roles_to_operations_file = None
+ self.roles_to_operations_table = None
self.resources_to_operations_mapping = {}
self.operation_to_allowed_roles = {}
self.logger = logging.getLogger("nbi.authenticator")
if config["authentication"]["backend"] == "keystone":
self.backend = AuthconnKeystone(self.config["authentication"])
elif config["authentication"]["backend"] == "internal":
+ self.backend = AuthconnInternal(self.config["authentication"], self.db, self.tokens_cache)
self._internal_tokens_prune()
else:
raise AuthException("Unknown authentication backend: {}"
break
if not self.roles_to_operations_file:
raise AuthException("Invalid permission configuration: roles_to_operations file missing")
+ if not self.roles_to_operations_table: # PROVISIONAL ?
+ self.roles_to_operations_table = "roles_operations" \
+ if config["authentication"]["backend"] == "keystone" \
+ else "roles"
except Exception as e:
raise AuthException(str(e))
# Always reads operation to resource mapping from file (this is static, no need to store it in MongoDB)
# Operations encoding: "<METHOD> <URL>"
# Note: it is faster to rewrite the value than to check if it is already there or not
- if self.config["authentication"]["backend"] == "internal":
- return
+
+ # PCR 28/05/2019 Commented out to allow initialization for internal backend
+ # if self.config["authentication"]["backend"] == "internal":
+ # return
with open(self.resources_to_operations_file, "r") as stream:
resources_to_operations_yaml = yaml.load(stream)
self.operations.append(operation)
self.resources_to_operations_mapping[resource] = operation
- records = self.db.get_list("roles_operations")
+ records = self.db.get_list(self.roles_to_operations_table)
# Loading permissions to MongoDB if there is not any permission.
if not records:
"modified": now,
}
- if self.config["authentication"]["backend"] != "internal" and \
- role_with_operations["name"] != "anonymous":
-
- backend_roles = self.backend.get_role_list(filter_q={"name": role_with_operations["name"]})
-
- if backend_roles:
- backend_id = backend_roles[0]["_id"]
- else:
- backend_id = self.backend.create_role(role_with_operations["name"])
- role_with_operations["_id"] = backend_id
+ if self.config["authentication"]["backend"] == "keystone":
+ if role_with_operations["name"] != "anonymous":
+ backend_roles = self.backend.get_role_list(filter_q={"name": role_with_operations["name"]})
+ if backend_roles:
+ backend_id = backend_roles[0]["_id"]
+ else:
+ backend_id = self.backend.create_role(role_with_operations["name"])
+ role_with_operations["_id"] = backend_id
+ else:
+ role_with_operations["_id"] = str(uuid4())
- self.db.create("roles_operations", role_with_operations)
+ self.db.create(self.roles_to_operations_table, role_with_operations)
if self.config["authentication"]["backend"] != "internal":
self.backend.assign_role_to_user("admin", "admin", "system_admin")
"""
permissions = {oper: [] for oper in self.operations}
- records = self.db.get_list("roles_operations")
+ records = self.db.get_list(self.roles_to_operations_table)
ignore_fields = ["_id", "_admin", "name", "default"]
for record in records:
outdata = self.new_token(None, {"username": user, "password": passwd})
token = outdata["id"]
cherrypy.session['Authorization'] = token
- if self.config["authentication"]["backend"] == "internal":
- return self._internal_authorize(token)
- else:
- if not token:
- raise AuthException("Needed a token or Authorization http header",
- http_code=HTTPStatus.UNAUTHORIZED)
- token_info = self.backend.validate_token(token)
- # TODO add to token info remote host, port
-
- self.check_permissions(token_info, cherrypy.request.path_info,
- cherrypy.request.method)
- return token_info
+
+ if not token:
+ raise AuthException("Needed a token or Authorization http header",
+ http_code=HTTPStatus.UNAUTHORIZED)
+ token_info = self.backend.validate_token(token)
+ # TODO add to token info remote host, port
+
+ self.check_permissions(token_info, cherrypy.request.path_info,
+ cherrypy.request.method)
+ return token_info
+
except AuthException as e:
if not isinstance(e, AuthExceptionUnauthorized):
if cherrypy.session.get('Authorization'):
raise
def new_token(self, session, indata, remote):
- if self.config["authentication"]["backend"] == "internal":
- return self._internal_new_token(session, indata, remote)
- else:
- current_token = None
- if session:
- current_token = session.get("token")
- token_info = self.backend.authenticate(
- user=indata.get("username"),
- password=indata.get("password"),
- token=current_token,
- project=indata.get("project_id")
- )
-
- # 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 not session:
- # token, projects = self.backend.authenticate_with_token(token, project_id)
- #
- # 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_info["_id"],
- "id": token_info["_id"],
- "issued_at": now,
- "expires": token_info.get("expires", now + 3600),
- "project_id": token_info["project_id"],
- "username": token_info.get("username") or session.get("username"),
- "remote_port": remote.port,
- "admin": True if token_info.get("project_name") == "admin" else False # TODO put admin in RBAC
- }
-
- if remote.name:
- new_session["remote_host"] = remote.name
- elif remote.ip:
- new_session["remote_host"] = remote.ip
+ current_token = None
+ if session:
+ # current_token = session.get("token")
+ current_token = session.get("_id") if self.config["authentication"]["backend"] == "keystone" \
+ else session
+ token_info = self.backend.authenticate(
+ user=indata.get("username"),
+ password=indata.get("password"),
+ token=current_token,
+ project=indata.get("project_id")
+ )
- # TODO: check if this can be avoided. Backend may provide enough information
- self.tokens_cache[token_info["_id"]] = new_session
+ now = time()
+ new_session = {
+ "_id": token_info["_id"],
+ "id": token_info["_id"],
+ "issued_at": now,
+ "expires": token_info.get("expires", now + 3600),
+ "project_id": token_info["project_id"],
+ "username": token_info.get("username") or session.get("username"),
+ "remote_port": remote.port,
+ "admin": True if token_info.get("project_name") == "admin" else False # TODO put admin in RBAC
+ }
+
+ if remote.name:
+ new_session["remote_host"] = remote.name
+ elif remote.ip:
+ new_session["remote_host"] = remote.ip
- return deepcopy(new_session)
+ # TODO: check if this can be avoided. Backend may provide enough information
+ self.tokens_cache[token_info["_id"]] = new_session
+
+ return deepcopy(new_session)
def get_token_list(self, session):
if self.config["authentication"]["backend"] == "internal":
return token_value
def del_token(self, token):
- if self.config["authentication"]["backend"] == "internal":
- return self._internal_del_token(token)
- else:
- try:
- self.backend.revoke_token(token)
- del self.tokens_cache[token]
- return "token '{}' deleted".format(token)
- except KeyError:
- raise AuthException("Token '{}' not found".format(token), http_code=HTTPStatus.NOT_FOUND)
+ try:
+ self.backend.revoke_token(token)
+ self.tokens_cache.pop(token, None)
+ return "token '{}' deleted".format(token)
+ except KeyError:
+ raise AuthException("Token '{}' not found".format(token), http_code=HTTPStatus.NOT_FOUND)
def check_permissions(self, session, url, method):
self.logger.info("Session: {}".format(session))
return filtered_key, parameters
- def _internal_authorize(self, token_id):
- try:
- if not token_id:
- raise AuthException("Needed a token or Authorization http header", http_code=HTTPStatus.UNAUTHORIZED)
- # try to get from cache first
- now = time()
- session = self.tokens_cache.get(token_id)
- if session and session["expires"] < now:
- # delete token. MUST be done with care, as another thread maybe already delete it. Do not use del
- self.tokens_cache.pop(token_id, None)
- session = None
- if session:
- return session
-
- # get from database if not in cache
- session = self.db.get_one("tokens", {"_id": token_id})
- if session["expires"] < now:
- raise AuthException("Expired Token or Authorization http header", http_code=HTTPStatus.UNAUTHORIZED)
- self.tokens_cache[token_id] = session
- return session
- except DbException as e:
- if e.http_code == HTTPStatus.NOT_FOUND:
- raise AuthException("Invalid Token or Authorization http header", http_code=HTTPStatus.UNAUTHORIZED)
- else:
- raise
-
- except AuthException:
- if self.config["global"].get("test.user_not_authorized"):
- return {"id": "fake-token-id-for-test",
- "project_id": self.config["global"].get("test.project_not_authorized", "admin"),
- "username": self.config["global"]["test.user_not_authorized"], "admin": True}
- else:
- raise
-
- def _internal_new_token(self, session, indata, remote):
- now = time()
- user_content = None
-
- # Try using username/password
- if indata.get("username"):
- user_rows = self.db.get_list("users", {"username": indata.get("username")})
- if user_rows:
- user_content = user_rows[0]
- salt = user_content["_admin"]["salt"]
- shadow_password = sha256(indata.get("password", "").encode('utf-8') + salt.encode('utf-8')).hexdigest()
- if shadow_password != user_content["password"]:
- user_content = None
- if not user_content:
- raise AuthException("Invalid username/password", http_code=HTTPStatus.UNAUTHORIZED)
- elif session:
- user_rows = self.db.get_list("users", {"username": session["username"]})
- if user_rows:
- user_content = user_rows[0]
- else:
- raise AuthException("Invalid token", http_code=HTTPStatus.UNAUTHORIZED)
- else:
- raise AuthException("Provide credentials: username/password or Authorization Bearer token",
- http_code=HTTPStatus.UNAUTHORIZED)
-
- token_id = ''.join(random_choice('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789')
- for _ in range(0, 32))
- project_id = indata.get("project_id")
- if project_id:
- if project_id != "admin":
- # To allow project names in project_id
- proj = self.db.get_one("projects", {BaseTopic.id_field("projects", project_id): project_id})
- if proj["_id"] not in user_content["projects"] and proj["name"] not in user_content["projects"]:
- raise AuthException("project {} not allowed for this user"
- .format(project_id), http_code=HTTPStatus.UNAUTHORIZED)
- else:
- project_id = user_content["projects"][0]
- if project_id == "admin":
- session_admin = True
- else:
- # To allow project names in project_id
- project = self.db.get_one("projects", {BaseTopic.id_field("projects", project_id): project_id})
- session_admin = project.get("admin", False)
- new_session = {"issued_at": now, "expires": now + 3600,
- "_id": token_id, "id": token_id, "project_id": project_id, "username": user_content["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_cache[token_id] = new_session
- self.db.create("tokens", new_session)
- # check if database must be prune
- self._internal_tokens_prune(now)
- return deepcopy(new_session)
-
def _internal_get_token_list(self, session):
now = time()
token_list = self.db.get_list("tokens", {"username": session["username"], "expires.gt": now})
raise AuthException("needed admin privileges", http_code=HTTPStatus.UNAUTHORIZED)
return token_value
- def _internal_del_token(self, token_id):
- try:
- self.tokens_cache.pop(token_id, None)
- self.db.del_one("tokens", {"_id": token_id})
- return "token '{}' deleted".format(token_id)
- except DbException as e:
- if e.http_code == HTTPStatus.NOT_FOUND:
- raise AuthException("Token '{}' not found".format(token_id), http_code=HTTPStatus.NOT_FOUND)
- else:
- raise
-
def _internal_tokens_prune(self, now=None):
now = now or time()
if not self.next_db_prune_time or self.next_db_prune_time >= now:
--- /dev/null
+# -*- coding: utf-8 -*-
+
+# Copyright 2018 Telefonica S.A.
+# Copyright 2018 ALTRAN InnovaciĆ³n S.L.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact: esousa@whitestack.com or glavado@whitestack.com
+##
+
+"""
+AuthconnInternal implements implements the connector for
+OSM Internal Authentication Backend and leverages the RBAC model
+"""
+
+__author__ = "Pedro de la Cruz Ramos <pdelacruzramos@altran.com>"
+__date__ = "$06-jun-2019 11:16:08$"
+
+from authconn import Authconn, AuthException
+from osm_common.dbbase import DbException
+from base_topic import BaseTopic
+
+import logging
+from time import time
+from http import HTTPStatus
+from uuid import uuid4
+from hashlib import sha256
+from copy import deepcopy
+from random import choice as random_choice
+
+
+class AuthconnInternal(Authconn):
+ def __init__(self, config, db, token_cache):
+ Authconn.__init__(self, config)
+
+ self.logger = logging.getLogger("nbi.authenticator.internal")
+
+ # Get Configuration
+ # self.xxx = config.get("xxx", "default")
+
+ self.db = db
+ self.token_cache = token_cache
+
+ # To be Confirmed
+ self.auth = None
+ self.sess = None
+
+ # def create_token (self, user, password, projects=[], project=None, remote=None):
+ # Not Required
+
+ # def authenticate_with_user_password(self, user, password, project=None, remote=None):
+ # Not Required
+
+ # def authenticate_with_token(self, token, project=None, remote=None):
+ # Not Required
+
+ # def get_user_project_list(self, token):
+ # Not Required
+
+ # def get_user_role_list(self, token):
+ # Not Required
+
+ # def create_user(self, user, password):
+ # Not Required
+
+ # def change_password(self, user, new_password):
+ # Not Required
+
+ # def delete_user(self, user_id):
+ # Not Required
+
+ # def get_user_list(self, filter_q={}):
+ # Not Required
+
+ # def get_project_list(self, filter_q={}):
+ # Not required
+
+ # def create_project(self, project):
+ # Not required
+
+ # def delete_project(self, project_id):
+ # Not required
+
+ # def assign_role_to_user(self, user, project, role):
+ # Not required in Phase 1
+
+ # def remove_role_from_user(self, user, project, role):
+ # Not required in Phase 1
+
+ def validate_token(self, token):
+ """
+ Check if the token is valid.
+
+ :param token: token to validate
+ :return: dictionary with information associated with the token:
+ "_id": token id
+ "project_id": project id
+ "project_name": project name
+ "user_id": user id
+ "username": user name
+ "roles": list with dict containing {name, id}
+ "expires": expiration date
+ If the token is not valid an exception is raised.
+ """
+
+ try:
+ if not token:
+ raise AuthException("Needed a token or Authorization HTTP header", http_code=HTTPStatus.UNAUTHORIZED)
+
+ # try to get from cache first
+ now = time()
+ session = self.token_cache.get(token)
+ if session and session["expires"] < now:
+ # delete token. MUST be done with care, as another thread maybe already delete it. Do not use del
+ self.token_cache.pop(token, None)
+ session = None
+
+ # get from database if not in cache
+ if not session:
+ session = self.db.get_one("tokens", {"_id": token})
+ if session["expires"] < now:
+ raise AuthException("Expired Token or Authorization HTTP header", http_code=HTTPStatus.UNAUTHORIZED)
+
+ # complete token information
+ pid = session["project_id"]
+ prj = self.db.get_one("projects", {BaseTopic.id_field("projects", pid): pid})
+ session["project_id"] = prj["_id"]
+ session["project_name"] = prj["name"]
+ session["user_id"] = self.db.get_one("users", {"username": session["username"]})["_id"]
+
+ # add token roles - PROVISIONAL
+ role_id = self.db.get_one("roles", {"name": "system_admin"})["_id"]
+ session["roles"] = [{"name": "system_admin", "id": role_id}]
+
+ return session
+
+ except DbException as e:
+ if e.http_code == HTTPStatus.NOT_FOUND:
+ raise AuthException("Invalid Token or Authorization HTTP header", http_code=HTTPStatus.UNAUTHORIZED)
+ else:
+ raise
+ except AuthException:
+ if self.config["global"].get("test.user_not_authorized"):
+ return {"id": "fake-token-id-for-test",
+ "project_id": self.config["global"].get("test.project_not_authorized", "admin"),
+ "username": self.config["global"]["test.user_not_authorized"], "admin": True}
+ else:
+ raise
+ except Exception:
+ self.logger.exception("Error during token validation using internal backend")
+ raise AuthException("Error during token validation using internal backend",
+ http_code=HTTPStatus.UNAUTHORIZED)
+
+ def revoke_token(self, token):
+ """
+ Invalidate a token.
+
+ :param token: token to be revoked
+ """
+ try:
+ self.token_cache.pop(token, None)
+ self.db.del_one("tokens", {"_id": token})
+ return True
+ except DbException as e:
+ if e.http_code == HTTPStatus.NOT_FOUND:
+ raise AuthException("Token '{}' not found".format(token), http_code=HTTPStatus.NOT_FOUND)
+ else:
+ # raise
+ msg = "Error during token revocation using internal backend"
+ self.logger.exception(msg)
+ raise AuthException(msg, http_code=HTTPStatus.UNAUTHORIZED)
+
+ def authenticate(self, user, password, project=None, token=None):
+ """
+ Authenticate a user using username/password or token, plus project
+
+ :param user: user: name, id or None
+ :param password: password or None
+ :param project: name, id, or None. If None first found project will be used to get an scope token
+ :param token: previous token to obtain authorization
+ :param remote: remote host information
+ :return: the scoped token info or raises an exception. The token is a dictionary with:
+ _id: token string id,
+ username: username,
+ project_id: scoped_token project_id,
+ project_name: scoped_token project_name,
+ expires: epoch time when it expires,
+ """
+
+ now = time()
+ user_content = None
+
+ try:
+ # Try using username/password
+ if user:
+ user_rows = self.db.get_list("users", {BaseTopic.id_field("users", user): user})
+ if user_rows:
+ user_content = user_rows[0]
+ salt = user_content["_admin"]["salt"]
+ shadow_password = sha256(password.encode('utf-8') + salt.encode('utf-8')).hexdigest()
+ if shadow_password != user_content["password"]:
+ user_content = None
+ if not user_content:
+ raise AuthException("Invalid username/password", http_code=HTTPStatus.UNAUTHORIZED)
+ elif token:
+ user_rows = self.db.get_list("users", {"username": token["username"]})
+ if user_rows:
+ user_content = user_rows[0]
+ else:
+ raise AuthException("Invalid token", http_code=HTTPStatus.UNAUTHORIZED)
+ else:
+ raise AuthException("Provide credentials: username/password or Authorization Bearer token",
+ http_code=HTTPStatus.UNAUTHORIZED)
+
+ token_id = ''.join(random_choice('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789')
+ for _ in range(0, 32))
+ project_id = project
+
+ if project_id:
+ if project_id != "admin":
+ # To allow project names in project_id
+ proj = self.db.get_one("projects", {BaseTopic.id_field("projects", project_id): project_id})
+ if proj["_id"] not in user_content["projects"] and proj["name"] not in user_content["projects"]:
+ raise AuthException("project {} not allowed for this user"
+ .format(project_id), http_code=HTTPStatus.UNAUTHORIZED)
+ else:
+ project_id = user_content["projects"][0]
+
+ if project_id == "admin":
+ token_admin = True
+ else:
+ # To allow project names in project_id
+ proj = self.db.get_one("projects", {BaseTopic.id_field("projects", project_id): project_id})
+ token_admin = proj.get("admin", False)
+
+ new_token = {"issued_at": now, "expires": now + 3600,
+ "_id": token_id, "id": token_id,
+ "project_id": project_id,
+ "username": user_content["username"],
+ "admin": token_admin}
+
+ self.token_cache[token_id] = new_token
+ self.db.create("tokens", new_token)
+ # self._internal_tokens_prune(now) # Belongs to Authenticator - REMOVE?
+ return deepcopy(new_token)
+
+ except Exception as e:
+ msg = "Error during user authentication using internal backend: {}".format(e)
+ self.logger.exception(msg)
+ raise AuthException(msg, http_code=HTTPStatus.UNAUTHORIZED)
+
+ def get_role_list(self):
+ """
+ Get role list.
+
+ :return: returns the list of roles.
+ """
+ try:
+ role_list = self.db.get_list("roles")
+ roles = [{"name": role["name"], "_id": role["_id"]} for role in role_list] # if role.name != "service" ?
+ return roles
+ except Exception:
+ raise AuthException("Error during role listing using internal backend", http_code=HTTPStatus.UNAUTHORIZED)
+
+ def create_role(self, role):
+ """
+ Create a role.
+
+ :param role: role name.
+ :raises AuthconnOperationException: if role creation failed.
+ """
+ # try:
+ # TODO: Check that role name does not exist ?
+ return str(uuid4())
+ # except Exception:
+ # raise AuthconnOperationException("Error during role creation using internal backend")
+ # except Conflict as ex:
+ # self.logger.info("Duplicate entry: %s", str(ex))
+
+ def delete_role(self, role_id):
+ """
+ Delete a role.
+
+ :param role_id: role identifier.
+ :raises AuthconnOperationException: if role deletion failed.
+ """
+ # try:
+ # TODO: Check that role exists ?
+ return True
+ # except Exception:
+ # raise AuthconnOperationException("Error during role deletion using internal backend")
alt_id_field = {
"projects": "name",
"users": "username",
- "roles": "name"
+ "roles": "name",
+ "roles_operations": "name"
}
def __init__(self, db, fs, msg):
@staticmethod
def id_field(topic, value):
"""Returns ID Field for given topic and field value"""
- if topic in ["projects", "users"] and not is_valid_uuid(value):
+ if topic in BaseTopic.alt_id_field.keys() and not is_valid_uuid(value):
return BaseTopic.alt_id_field[topic]
else:
return "_id"
from http import HTTPStatus
from authconn_keystone import AuthconnKeystone
+from authconn_internal import AuthconnInternal
from base_topic import EngineException, versiontuple
from admin_topics import UserTopic, ProjectTopic, VimAccountTopic, WimAccountTopic, SdnTopic
from admin_topics import UserTopicAuth, ProjectTopicAuth, RoleTopicAuth
"sdns": SdnTopic,
"users": UserTopic,
"projects": ProjectTopic,
+ "roles": RoleTopicAuth, # Valid for both internal and keystone authentication backends
"nsis": NsiTopic,
"nsilcmops": NsiLcmOpTopic
# [NEW_TOPIC]: add an entry here
if not self.auth:
if config["authentication"]["backend"] == "keystone":
self.auth = AuthconnKeystone(config["authentication"])
+ else:
+ self.auth = AuthconnInternal(config["authentication"], self.db, dict()) # TO BE CONFIRMED
if not self.operations:
if "resources_to_operations" in config["rbac"]:
resources_to_operations_file = config["rbac"]["resources_to_operations"]
users = self.db.get_one("users", fail_on_empty=False, fail_on_more=False)
if users:
return None
- # raise EngineException("Unauthorized. Database users is not empty", HTTPStatus.UNAUTHORIZED)
user_desc = {"username": "admin", "password": "admin", "projects": ["admin"]}
fake_session = {"project_id": "admin", "username": "admin", "admin": True, "force": True, "public": None}
- roolback_list = []
- _id = self.map_topic["users"].new(roolback_list, fake_session, user_desc)
+ rollback_list = []
+ _id = self.map_topic["users"].new(rollback_list, fake_session, user_desc)
return _id
def create_admin(self):
"""
project_id = self.create_admin_project()
user_id = self.create_admin_user()
- if not project_id and not user_id:
- return None
- else:
+ if project_id or user_id:
return {'project_id': project_id, 'user_id': user_id}
+ else:
+ return None
def upgrade_db(self, current_version, target_version):
if target_version not in self.map_target_version_to_int.keys():
current_version = "1.0"
if current_version in ("1.0", "1.1") and target_version_int >= self.map_target_version_to_int["1.2"]:
- self.db.del_list("roles_operations")
+ table = "roles_operations" if self.config['authentication']['backend'] == "keystone" else "roles"
+ self.db.del_list(table)
version_data = {
"_id": "version",
if db_version != target_version:
self.upgrade_db(db_version, target_version)
- # create user admin if not exist
- if not self.auth:
+ # create admin project&user if they don't exist
+ if self.config['authentication']['backend'] == 'internal' or not self.auth:
self.create_admin()
return