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
+from osm_common.dbbase import deep_update_rfc7396
+from authconn import AuthconnNotFoundException, AuthconnConflictException
+# from authconn_keystone import AuthconnKeystone
__author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
if content.get("password"):
content["password"] = sha256(content["password"].encode('utf-8') + salt.encode('utf-8')).hexdigest()
if content.get("project_role_mappings"):
- projects = [mapping[0] for mapping in content["project_role_mappings"]]
+ projects = [mapping["project"] for mapping in content["project_role_mappings"]]
if content.get("projects"):
content["projects"] += projects
if "projects" in indata.keys():
# convert to new format project_role_mappings
+ role = self.auth.get_role_list({"name": "project_admin"})
+ if not role:
+ role = self.auth.get_role_list()
+ if not role:
+ raise AuthconnNotFoundException("Can't find default role for user '{}'".format(username))
+ rid = role[0]["_id"]
if not indata.get("project_role_mappings"):
indata["project_role_mappings"] = []
for project in indata["projects"]:
- indata["project_role_mappings"].append({"project": project, "role": "project_user"})
+ pid = self.auth.get_project(project)["_id"]
+ prm = {"project": pid, "role": rid}
+ if prm not in indata["project_role_mappings"]:
+ indata["project_role_mappings"].append(prm)
# raise EngineException("Format invalid: the keyword 'projects' is not allowed for keystone authentication",
# HTTPStatus.BAD_REQUEST)
"""
if db_content["username"] == session["username"]:
raise EngineException("You cannot delete your own login user ", http_code=HTTPStatus.CONFLICT)
-
- # @staticmethod
- # def format_on_new(content, project_id=None, make_public=False):
- # """
- # Modifies content descriptor to include _id.
- #
- # NOTE: No password salt required because the authentication backend
- # should handle these security concerns.
- #
- # :param content: descriptor to be modified
- # :param make_public: if included it is generated as public for reading.
- # :return: None, but content is modified
- # """
- # BaseTopic.format_on_new(content, make_public=False)
- # content["_id"] = content["username"]
- # content["password"] = content["password"]
-
- # @staticmethod
- # def format_on_edit(final_content, edit_content):
- # """
- # Modifies final_content descriptor to include the modified date.
- #
- # NOTE: No password salt required because the authentication backend
- # should handle these security concerns.
- #
- # :param final_content: final descriptor generated
- # :param edit_content: alterations to be include
- # :return: None, but final_content is modified
- # """
- # BaseTopic.format_on_edit(final_content, edit_content)
- # if "password" in edit_content:
- # final_content["password"] = edit_content["password"]
- # else:
- # final_content["project_role_mappings"] = edit_content["project_role_mappings"]
+ # TODO: Check that user is not logged in ? How? (Would require listing current tokens)
@staticmethod
def format_on_show(content):
"""
project_role_mappings = []
- for project in content["projects"]:
- for role in project["roles"]:
- project_role_mappings.append({"project": project["_id"],
- "project_name": project["name"],
- "role": role["_id"],
- "role_name": role["name"]})
-
- del content["projects"]
+ if "projects" in content:
+ for project in content["projects"]:
+ for role in project["roles"]:
+ project_role_mappings.append({"project": project["_id"],
+ "project_name": project["name"],
+ "role": role["_id"],
+ "role_name": role["name"]})
+ del content["projects"]
content["project_role_mappings"] = project_role_mappings
return content
:param indata: data to be inserted
:param kwargs: used to override the indata descriptor
:param headers: http request headers
- :return: _id: identity of the inserted data.
+ :return: _id: identity of the inserted data, operation _id (None)
"""
try:
content = BaseTopic._remove_envelop(indata)
content = self._validate_input_new(content, session["force"])
self.check_conflict_on_new(session, content)
# self.format_on_new(content, session["project_id"], make_public=session["public"])
- _id = self.auth.create_user(content["username"], content["password"])["_id"]
-
- if "project_role_mappings" in content.keys():
- for mapping in content["project_role_mappings"]:
- self.auth.assign_role_to_user(_id, mapping["project"], mapping["role"])
+ now = time()
+ content["_admin"] = {"created": now, "modified": now}
+ prms = []
+ for prm in content.get("project_role_mappings", []):
+ proj = self.auth.get_project(prm["project"], not session["force"])
+ role = self.auth.get_role(prm["role"], not session["force"])
+ pid = proj["_id"] if proj else None
+ rid = role["_id"] if role else None
+ prl = {"project": pid, "role": rid}
+ if prl not in prms:
+ prms.append(prl)
+ content["project_role_mappings"] = prms
+ # _id = self.auth.create_user(content["username"], content["password"])["_id"]
+ _id = self.auth.create_user(content)["_id"]
rollback.append({"topic": self.topic, "_id": _id})
# del content["password"]
# self._send_msg("create", content)
- return _id
+ return _id, None
except ValidationError as e:
raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
self.check_conflict_on_edit(session, content, indata, _id=_id)
# self.format_on_edit(content, indata)
- if "password" in indata or "username" in indata:
- self.auth.update_user(_id, new_name=indata.get("username"), new_password=indata.get("password"))
- if not indata.get("remove_project_role_mappings") and not indata.get("add_project_role_mappings") and \
- not indata.get("project_role_mappings"):
+ if not ("password" in indata or "username" in indata or indata.get("remove_project_role_mappings") or
+ indata.get("add_project_role_mappings") or indata.get("project_role_mappings") or
+ indata.get("projects") or indata.get("add_projects")):
return _id
- if indata.get("project_role_mappings") and \
- (indata.get("remove_project_role_mappings") or indata.get("add_project_role_mappings")):
+ if indata.get("project_role_mappings") \
+ and (indata.get("remove_project_role_mappings") or indata.get("add_project_role_mappings")):
raise EngineException("Option 'project_role_mappings' is incompatible with 'add_project_role_mappings"
"' or 'remove_project_role_mappings'", http_code=HTTPStatus.BAD_REQUEST)
- user = self.show(session, _id)
- original_mapping = user["project_role_mappings"]
+ if indata.get("projects") or indata.get("add_projects"):
+ role = self.auth.get_role_list({"name": "project_admin"})
+ if not role:
+ role = self.auth.get_role_list()
+ if not role:
+ raise AuthconnNotFoundException("Can't find a default role for user '{}'"
+ .format(content["username"]))
+ rid = role[0]["_id"]
+ if "add_project_role_mappings" not in indata:
+ indata["add_project_role_mappings"] = []
+ for proj in indata.get("projects", []) + indata.get("add_projects", []):
+ indata["add_project_role_mappings"].append({"project": proj, "role": rid})
+
+ # user = self.show(session, _id) # Already in 'content'
+ original_mapping = content["project_role_mappings"]
mappings_to_add = []
mappings_to_remove = []
mappings_to_remove.remove(mapping)
break # do not add, it is already at user
else:
- mappings_to_add.append(to_add)
+ pid = self.auth.get_project(to_add["project"])["_id"]
+ rid = self.auth.get_role(to_add["role"])["_id"]
+ mappings_to_add.append({"project": pid, "role": rid})
# set
if indata.get("project_role_mappings"):
for mapping in original_mapping:
if to_set["project"] in (mapping["project"], mapping["project_name"]) and \
to_set["role"] in (mapping["role"], mapping["role_name"]):
-
if mapping in mappings_to_remove: # do not remove
mappings_to_remove.remove(mapping)
break # do not add, it is already at user
else:
- mappings_to_add.append(to_set)
+ pid = self.auth.get_project(to_set["project"])["_id"]
+ rid = self.auth.get_role(to_set["role"])["_id"]
+ mappings_to_add.append({"project": pid, "role": rid})
for mapping in original_mapping:
for to_set in indata["project_role_mappings"]:
if to_set["project"] in (mapping["project"], mapping["project_name"]) and \
if mapping not in mappings_to_remove: # do not remove
mappings_to_remove.append(mapping)
- for mapping in mappings_to_remove:
- self.auth.remove_role_from_user(
- _id,
- mapping["project"],
- mapping["role"]
- )
-
- for mapping in mappings_to_add:
- self.auth.assign_role_to_user(
- _id,
- mapping["project"],
- mapping["role"]
- )
-
- return "_id"
+ self.auth.update_user({"_id": _id, "username": indata.get("username"), "password": indata.get("password"),
+ "add_project_role_mappings": mappings_to_add,
+ "remove_project_role_mappings": mappings_to_remove
+ })
+
+ # return _id
except ValidationError as e:
raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
:return: dictionary with deleted item _id. It raises EngineException on error: not found, conflict, ...
"""
# Allow _id to be a name or uuid
- filter_q = {self.id_field(self.topic, _id): _id}
- user_list = self.auth.get_user_list(filter_q)
- if not user_list:
- raise EngineException("User '{}' not found".format(_id), http_code=HTTPStatus.NOT_FOUND)
- _id = user_list[0]["_id"]
- self.check_conflict_on_del(session, _id, user_list[0])
+ user = self.auth.get_user(_id)
+ uid = user["_id"]
+ self.check_conflict_on_del(session, uid, user)
if not dry_run:
- v = self.auth.delete_user(_id)
+ v = self.auth.delete_user(uid)
return v
return None
"""
project_name = edit_content.get("name")
- if project_name:
+ if project_name != final_content["name"]: # It is a true renaming
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)
+ if final_content["name"] == "admin":
+ raise EngineException("You cannot rename project 'admin'", http_code=HTTPStatus.CONFLICT)
+
# Check that project name is not used, regardless keystone already checks this
if self.auth.get_project_list(filter_q={"name": project_name}):
raise EngineException("project '{}' is already used".format(project_name), HTTPStatus.CONFLICT)
:param db_content: The database content of this item _id
:return: None if ok or raises EngineException with the conflict
"""
- # projects = self.auth.get_project_list()
- # current_project = [project for project in projects
- # if project["name"] in session["project_id"]][0]
- # TODO check that any user is using this project, raise CONFLICT exception
- if _id == session["project_id"]:
+
+ def check_rw_projects(topic, title, id_field):
+ for desc in self.db.get_list(topic):
+ if _id in desc["_admin"]["projects_read"] + desc["_admin"]["projects_write"]:
+ raise EngineException("Project '{}' ({}) is being used by {} '{}'"
+ .format(db_content["name"], _id, title, desc[id_field]), HTTPStatus.CONFLICT)
+
+ if _id in session["project_id"]:
raise EngineException("You cannot delete your own project", http_code=HTTPStatus.CONFLICT)
+ if db_content["name"] == "admin":
+ raise EngineException("You cannot delete project 'admin'", http_code=HTTPStatus.CONFLICT)
+
+ # If any user is using this project, raise CONFLICT exception
+ if not session["force"]:
+ for user in self.auth.get_user_list():
+ if _id in [proj["_id"] for proj in user.get("projects", [])]:
+ raise EngineException("Project '{}' ({}) is being used by user '{}'"
+ .format(db_content["name"], _id, user["username"]), HTTPStatus.CONFLICT)
+
+ # If any VNFD, NSD, NST, PDU, etc. is using this project, raise CONFLICT exception
+ if not session["force"]:
+ check_rw_projects("vnfds", "VNF Descriptor", "id")
+ check_rw_projects("nsds", "NS Descriptor", "id")
+ check_rw_projects("nsts", "NS Template", "id")
+ check_rw_projects("pdus", "PDU Descriptor", "name")
+
def new(self, rollback, session, indata=None, kwargs=None, headers=None):
"""
Creates a new entry into the authentication backend.
:param indata: data to be inserted
:param kwargs: used to override the indata descriptor
:param headers: http request headers
- :return: _id: identity of the inserted data.
+ :return: _id: identity of the inserted data, operation _id (None)
"""
try:
content = BaseTopic._remove_envelop(indata)
content = self._validate_input_new(content, session["force"])
self.check_conflict_on_new(session, content)
self.format_on_new(content, project_id=session["project_id"], make_public=session["public"])
- _id = self.auth.create_project(content["name"])
+ _id = self.auth.create_project(content)
rollback.append({"topic": self.topic, "_id": _id})
# self._send_msg("create", content)
- return _id
+ return _id, None
except ValidationError as e:
raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
:return: dictionary with deleted item _id. It raises EngineException on error: not found, conflict, ...
"""
# Allow _id to be a name or uuid
- filter_q = {self.id_field(self.topic, _id): _id}
- project_list = self.auth.get_project_list(filter_q)
- if not project_list:
- raise EngineException("Project '{}' not found".format(_id), http_code=HTTPStatus.NOT_FOUND)
- _id = project_list[0]["_id"]
- self.check_conflict_on_del(session, _id, project_list[0])
+ proj = self.auth.get_project(_id)
+ pid = proj["_id"]
+ self.check_conflict_on_del(session, pid, proj)
if not dry_run:
- v = self.auth.delete_project(_id)
+ v = self.auth.delete_project(pid)
return v
return None
if not content:
content = self.show(session, _id)
self.check_conflict_on_edit(session, content, indata, _id=_id)
- # self.format_on_edit(content, indata)
+ self.format_on_edit(content, indata)
if "name" in indata:
- self.auth.update_project(content["_id"], indata["name"])
+ content["name"] = indata["name"]
+ self.auth.update_project(content["_id"], content)
except ValidationError as e:
raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
BaseTopic.__init__(self, db, fs, msg)
self.auth = auth
self.operations = ops
- self.topic = "roles_operations" if isinstance(auth, AuthconnKeystone) else "roles"
+ # self.topic = "roles_operations" if isinstance(auth, AuthconnKeystone) else "roles"
@staticmethod
def validate_role_definition(operations, role_definitions):
:return: None or raises EngineException
"""
# check name not exists
- if self.db.get_one(self.topic, {"name": indata.get("name")}, fail_on_empty=False, fail_on_more=False):
- raise EngineException("role name '{}' exists".format(indata["name"]), HTTPStatus.CONFLICT)
+ name = indata["name"]
+ # if self.db.get_one(self.topic, {"name": indata.get("name")}, fail_on_empty=False, fail_on_more=False):
+ if self.auth.get_role_list({"name": name}):
+ raise EngineException("role name '{}' exists".format(name), HTTPStatus.CONFLICT)
def check_conflict_on_edit(self, session, final_content, edit_content, _id):
"""
# check name not exists
if "name" in edit_content:
role_name = edit_content["name"]
- if self.db.get_one(self.topic, {"name": role_name, "_id.ne": _id}, fail_on_empty=False, fail_on_more=False):
+ # if self.db.get_one(self.topic, {"name":role_name,"_id.ne":_id}, fail_on_empty=False, fail_on_more=False):
+ roles = self.auth.get_role_list({"name": role_name})
+ if roles and roles[0][BaseTopic.id_field("roles", _id)] != _id:
raise EngineException("role name '{}' exists".format(role_name), HTTPStatus.CONFLICT)
def check_conflict_on_del(self, session, _id, db_content):
:param db_content: The database content of this item _id
:return: None if ok or raises EngineException with the conflict
"""
- roles = self.auth.get_role_list()
- system_admin_roles = [role for role in roles if role["name"] == "system_admin"]
+ role = self.auth.get_role(_id)
+ if role["name"] in ["system_admin", "project_admin"]:
+ raise EngineException("You cannot delete role '{}'".format(role["name"]), http_code=HTTPStatus.FORBIDDEN)
- if system_admin_roles and _id == system_admin_roles[0]["_id"]:
- raise EngineException("You cannot delete system_admin role", http_code=HTTPStatus.FORBIDDEN)
+ # If any user is using this role, raise CONFLICT exception
+ for user in self.auth.get_user_list():
+ if _id in [prl["_id"] for proj in user.get("projects", []) for prl in proj.get("roles", [])]:
+ raise EngineException("Role '{}' ({}) is being used by user '{}'"
+ .format(role["name"], _id, user["username"]), HTTPStatus.CONFLICT)
@staticmethod
- def format_on_new(content, project_id=None, make_public=False):
+ def format_on_new(content, project_id=None, make_public=False): # TO BE REMOVED ?
"""
Modifies content descriptor to include _admin
:param edit_content: alterations to be include
:return: None, but final_content is modified
"""
- final_content["_admin"]["modified"] = time()
+ if "_admin" in final_content:
+ final_content["_admin"]["modified"] = time()
if "permissions" not in final_content:
final_content["permissions"] = {}
final_content["permissions"]["admin"] = False
return None
- # @staticmethod
- # def format_on_show(content):
- # """
- # Modifies the content of the role information to separate the role
- # metadata from the role definition. Eases the reading process of the
- # role definition.
- #
- # :param definition: role definition to be processed
- # """
- # content["_id"] = str(content["_id"])
- #
- # def show(self, session, _id):
- # """
- # Get complete information on an topic
- #
- # :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
- # :param _id: server internal id
- # :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)
- # self.format_on_show(new_role)
- #
- # return new_role
-
- # def list(self, session, filter_q=None):
- # """
- # Get a list of the topic that matches a filter
- #
- # :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
- # :param filter_q: filter of data to be applied
- # :return: The list, it can be empty if no one match the filter.
- # """
- # if not filter_q:
- # filter_q = {}
- #
- # if ":" in filter_q:
- # filter_q["root"] = filter_q[":"]
- #
- # for key in filter_q.keys():
- # if key == "name":
- # continue
- # filter_q[key] = filter_q[key] in ["True", "true"]
- #
- # roles = self.db.get_list(self.topic, filter_q)
- # new_roles = []
- #
- # for role in roles:
- # new_role = dict(role)
- # self.format_on_show(new_role)
- # new_roles.append(new_role)
- #
- # return new_roles
+ def show(self, session, _id):
+ """
+ Get complete information on an topic
+
+ :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
+ :param _id: server internal id
+ :return: dictionary, raise exception if not found.
+ """
+ filter_q = {BaseTopic.id_field(self.topic, _id): _id}
+ roles = self.auth.get_role_list(filter_q)
+ if not roles:
+ raise AuthconnNotFoundException("Not found any role with filter {}".format(filter_q))
+ elif len(roles) > 1:
+ raise AuthconnConflictException("Found more than one role with filter {}".format(filter_q))
+ return roles[0]
+
+ def list(self, session, filter_q=None):
+ """
+ Get a list of the topic that matches a filter
+
+ :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
+ :param filter_q: filter of data to be applied
+ :return: The list, it can be empty if no one match the filter.
+ """
+ return self.auth.get_role_list(filter_q)
def new(self, rollback, session, indata=None, kwargs=None, headers=None):
"""
:param indata: data to be inserted
:param kwargs: used to override the indata descriptor
:param headers: http request headers
- :return: _id: identity of the inserted data.
+ :return: _id: identity of the inserted data, operation _id (None)
"""
try:
content = self._remove_envelop(indata)
content = self._validate_input_new(content, session["force"])
self.check_conflict_on_new(session, content)
self.format_on_new(content, project_id=session["project_id"], make_public=session["public"])
- role_name = content["name"]
- role_id = self.auth.create_role(role_name)
- content["_id"] = role_id
- _id = self.db.create(self.topic, content)
- rollback.append({"topic": self.topic, "_id": _id})
+ # role_name = content["name"]
+ rid = self.auth.create_role(content)
+ content["_id"] = rid
+ # _id = self.db.create(self.topic, content)
+ rollback.append({"topic": self.topic, "_id": rid})
# self._send_msg("create", content)
- return _id
+ return rid, None
except ValidationError as e:
raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
:param dry_run: make checking but do not delete
:return: dictionary with deleted item _id. It raises EngineException on error: not found, conflict, ...
"""
- self.check_conflict_on_del(session, _id, None)
+ filter_q = {BaseTopic.id_field(self.topic, _id): _id}
+ roles = self.auth.get_role_list(filter_q)
+ if not roles:
+ raise AuthconnNotFoundException("Not found any role with filter {}".format(filter_q))
+ elif len(roles) > 1:
+ raise AuthconnConflictException("Found more than one role with filter {}".format(filter_q))
+ rid = roles[0]["_id"]
+ self.check_conflict_on_del(session, rid, None)
# filter_q = {"_id": _id}
- filter_q = {BaseTopic.id_field(self.topic, _id): _id} # To allow role addressing by name
+ # 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)
+ v = self.auth.delete_role(rid)
+ # v = self.db.del_one(self.topic, filter_q)
return v
return None
:param content:
:return: _id: identity of the inserted data.
"""
- _id = super().edit(session, _id, indata, kwargs, content)
- if indata.get("name"):
- self.auth.update_role(_id, name=indata.get("name"))
+ if kwargs:
+ self._update_input_with_kwargs(indata, kwargs)
+ try:
+ indata = self._validate_input_edit(indata, force=session["force"])
+ if not content:
+ content = self.show(session, _id)
+ deep_update_rfc7396(content, indata)
+ self.check_conflict_on_edit(session, content, indata, _id=_id)
+ self.format_on_edit(content, indata)
+ self.auth.update_role(content)
+ except ValidationError as e:
+ raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
from osm_common.dbbase import DbException
from itertools import chain
-from uuid import uuid4 # For Role _id with internal authentication backend
+from uuid import uuid4
class Authenticator:
self.tokens_cache = dict()
self.next_db_prune_time = 0 # time when next cleaning of expired tokens must be done
self.roles_to_operations_file = None
- self.roles_to_operations_table = None
+ # self.roles_to_operations_table = None
self.resources_to_operations_mapping = {}
self.operation_to_allowed_roles = {}
self.logger = logging.getLogger("nbi.authenticator")
.format(config["database"]["driver"]))
if not self.backend:
if config["authentication"]["backend"] == "keystone":
- self.backend = AuthconnKeystone(self.config["authentication"])
+ self.backend = AuthconnKeystone(self.config["authentication"], self.db, self.tokens_cache)
elif config["authentication"]["backend"] == "internal":
self.backend = AuthconnInternal(self.config["authentication"], self.db, self.tokens_cache)
self._internal_tokens_prune()
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"
-
# load role_permissions
def load_role_permissions(method_dict):
for k in method_dict:
except DbException as e:
raise AuthException(str(e), http_code=e.http_code)
+ def create_admin_project(self):
+ """
+ Creates a new project 'admin' into database if it doesn't exist. Useful for initialization.
+ :return: _id identity of the 'admin' project
+ """
+
+ # projects = self.db.get_one("projects", fail_on_empty=False, fail_on_more=False)
+ project_desc = {"name": "admin"}
+ projects = self.backend.get_project_list(project_desc)
+ if projects:
+ return projects[0]["_id"]
+ now = time()
+ project_desc["_id"] = str(uuid4())
+ project_desc["_admin"] = {"created": now, "modified": now}
+ pid = self.backend.create_project(project_desc)
+ self.logger.info("Project '{}' created at database".format(project_desc["name"]))
+ return pid
+
+ def create_admin_user(self, project_id):
+ """
+ Creates a new user admin/admin into database if database is empty. Useful for initialization
+ :return: _id identity of the inserted data, or None
+ """
+ # users = self.db.get_one("users", fail_on_empty=False, fail_on_more=False)
+ users = self.backend.get_user_list()
+ if users:
+ return None
+ # user_desc = {"username": "admin", "password": "admin", "projects": [project_id]}
+ now = time()
+ user_desc = {"username": "admin", "password": "admin", "_admin": {"created": now, "modified": now}}
+ if project_id:
+ pid = project_id
+ else:
+ # proj = self.db.get_one("projects", {"name": "admin"}, fail_on_empty=False, fail_on_more=False)
+ proj = self.backend.get_project_list({"name": "admin"})
+ pid = proj[0]["_id"] if proj else None
+ # role = self.db.get_one("roles", {"name": "system_admin"}, fail_on_empty=False, fail_on_more=False)
+ roles = self.backend.get_role_list({"name": "system_admin"})
+ if pid and roles:
+ user_desc["project_role_mappings"] = [{"project": pid, "role": roles[0]["_id"]}]
+ uid = self.backend.create_user(user_desc)
+ self.logger.info("User '{}' created at database".format(user_desc["username"]))
+ return uid
+
def init_db(self, target_version='1.0'):
"""
Check if the database has been initialized, with at least one user. If not, create the required tables
:return: None if OK, exception if error or version is different.
"""
- # PCR 28/05/2019 Commented out to allow initialization for internal backend
- # if self.config["authentication"]["backend"] == "internal":
- # return
-
- records = self.db.get_list(self.roles_to_operations_table)
+ records = self.backend.get_role_list()
# Loading permissions to MongoDB if there is not any permission.
- if not records:
+ if not records or (len(records) == 1 and records[0]["name"] == "admin"):
with open(self.roles_to_operations_file, "r") as stream:
roles_to_operations_yaml = yaml.load(stream)
"modified": now,
}
- 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(self.roles_to_operations_table, role_with_operations)
+ # self.db.create(self.roles_to_operations_table, role_with_operations)
+ self.backend.create_role(role_with_operations)
self.logger.info("Role '{}' created at database".format(role_with_operations["name"]))
- if self.config["authentication"]["backend"] != "internal":
- self.backend.assign_role_to_user("admin", "admin", "system_admin")
+ # Create admin project&user if required
+ pid = self.create_admin_project()
+ self.create_admin_user(pid)
+
+ if self.config["authentication"]["backend"] == "keystone":
+ try:
+ self.backend.assign_role_to_user("admin", "admin", "system_admin")
+ except Exception:
+ pass
self.load_operation_to_allowed_roles()
"""
permissions = {oper: [] for oper in self.role_permissions}
- records = self.db.get_list(self.roles_to_operations_table)
+ # records = self.db.get_list(self.roles_to_operations_table)
+ records = self.backend.get_role_list()
ignore_fields = ["_id", "_admin", "name", "default"]
for record in records:
+ if not record.get("permissions"):
+ continue
record_permissions = {oper: record["permissions"].get("default", False) for oper in self.role_permissions}
operations_joined = [(oper, value) for oper, value in record["permissions"].items()
if oper not in ignore_fields]
__date__ = "$27-jul-2018 23:59:59$"
from http import HTTPStatus
+from base_topic import BaseTopic
class AuthException(Exception):
Each Auth backend connector plugin must be a subclass of
Authconn class.
"""
- def __init__(self, config):
+ def __init__(self, config, db, token_cache):
"""
Constructor of the Authconn class.
"""
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.
"""
raise AuthconnNotImplementedException("Should have implemented this")
- def get_user_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_user_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):
+ def create_user(self, user_info):
"""
Create a user.
- :param user: username.
- :param password: password.
+ :param user_info: full user info.
:raises AuthconnOperationException: if user creation failed.
"""
raise AuthconnNotImplementedException("Should have implemented this")
- def update_user(self, user, new_name=None, new_password=None):
+ def update_user(self, user_info):
"""
Change the user name and/or password.
- :param user: username or user_id
- :param new_name: new name
- :param new_password: new password.
- :raises AuthconnOperationException: if change failed.
+ :param user_info: user info modifications
+ :raises AuthconnNotImplementedException: if function not implemented
"""
raise AuthconnNotImplementedException("Should have implemented this")
:return: returns a list of users.
"""
- def create_role(self, role):
+ def get_user(self, id, fail=True):
+ filt = {BaseTopic.id_field("users", id): id}
+ users = self.get_user_list(filt)
+ if not users:
+ if fail:
+ raise AuthconnNotFoundException("User with {} not found".format(filt), http_code=HTTPStatus.NOT_FOUND)
+ else:
+ return None
+ return users[0]
+
+ def create_role(self, role_info):
"""
Create a role.
- :param role: role name.
+ :param role_info: full role info.
:raises AuthconnOperationException: if role creation failed.
"""
raise AuthconnNotImplementedException("Should have implemented this")
"""
raise AuthconnNotImplementedException("Should have implemented this")
- def update_role(self, role, new_name):
+ def get_role(self, id, fail=True):
+ filt = {BaseTopic.id_field("roles", id): id}
+ roles = self.get_role_list(filt)
+ if not roles:
+ if fail:
+ raise AuthconnNotFoundException("Role with {} not found".format(filt))
+ else:
+ return None
+ return roles[0]
+
+ def update_role(self, role_info):
"""
- Change the name of a role
- :param role: role name or id to be changed
- :param new_name: new name
+ Change the information of a role
+ :param role_info: full role info
:return: None
"""
raise AuthconnNotImplementedException("Should have implemented this")
- def create_project(self, project):
+ def create_project(self, project_info):
"""
Create a project.
- :param project: project name.
+ :param project_info: full project info.
:return: the internal id of the created project
:raises AuthconnOperationException: if project creation failed.
"""
"""
raise AuthconnNotImplementedException("Should have implemented this")
- def update_project(self, project_id, new_name):
+ def get_project(self, id, fail=True):
+ filt = {BaseTopic.id_field("projects", id): id}
+ projs = self.get_project_list(filt)
+ if not projs:
+ if fail:
+ raise AuthconnNotFoundException("project with {} not found".format(filt))
+ else:
+ return None
+ return projs[0]
+
+ def update_project(self, project_id, project_info):
"""
- Change the name of a project
+ Change the information of a project
:param project_id: project to be changed
- :param new_name: new name
+ :param project_info: full project info
:return: None
"""
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")
__author__ = "Pedro de la Cruz Ramos <pdelacruzramos@altran.com>"
__date__ = "$06-jun-2019 11:16:08$"
-from authconn import Authconn, AuthException
+from authconn import Authconn, AuthException # , AuthconnOperationException
from osm_common.dbbase import DbException
from base_topic import BaseTopic
import logging
+import re
from time import time
from http import HTTPStatus
from uuid import uuid4
class AuthconnInternal(Authconn):
def __init__(self, config, db, token_cache):
- Authconn.__init__(self, config)
+ Authconn.__init__(self, config, db, token_cache)
self.logger = logging.getLogger("nbi.authenticator.internal")
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.
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_info:
- user_rows = self.db.get_list("users", {"username": token_info["username"]})
- if user_rows:
- user_content = user_rows[0]
- else:
- raise AuthException("Invalid token", http_code=HTTPStatus.UNAUTHORIZED)
+ # 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_info:
+ user_rows = self.db.get_list("users", {"username": token_info["username"]})
+ if user_rows:
+ user_content = user_rows[0]
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))
-
- # TODO when user contained project_role_mappings with project_id,project_ name this checking to
- # database will not be needed
- if not project:
- project = user_content["projects"][0]
-
- # To allow project names in project_id
- proj = self.db.get_one("projects", {BaseTopic.id_field("projects", project): project})
- 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),
- http_code=HTTPStatus.UNAUTHORIZED)
-
- # TODO remove admin, this vill be used by roles RBAC
- if proj["name"] == "admin":
- token_admin = True
- else:
- token_admin = proj.get("admin", False)
-
- # TODO add token roles - PROVISIONAL. Get this list from user_content["project_role_mappings"]
- role_id = self.db.get_one("roles", {"name": "system_admin"})["_id"]
- roles_list = [{"name": "system_admin", "id": role_id}]
-
- new_token = {"issued_at": now,
- "expires": now + 3600,
- "_id": token_id,
- "id": token_id,
- "project_id": proj["_id"],
- "project_name": proj["name"],
- "username": user_content["username"],
- "user_id": user_content["_id"],
- "admin": token_admin,
- "roles": roles_list,
- }
-
- self.token_cache[token_id] = new_token
- self.db.create("tokens", new_token)
- 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):
+ 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))
+
+ # projects = user_content.get("projects", [])
+ prm_list = user_content.get("project_role_mappings", [])
+
+ if not project:
+ project = prm_list[0]["project"] if prm_list else None
+ if not project:
+ raise AuthException("can't find a default project for this user", http_code=HTTPStatus.UNAUTHORIZED)
+
+ projects = [prm["project"] for prm in prm_list]
+
+ proj = self.db.get_one("projects", {BaseTopic.id_field("projects", project): project})
+ project_name = proj["name"]
+ project_id = proj["_id"]
+ if project_name not in projects and project_id not in projects:
+ raise AuthException("project {} not allowed for this user".format(project),
+ http_code=HTTPStatus.UNAUTHORIZED)
+
+ # TODO remove admin, this vill be used by roles RBAC
+ if project_name == "admin":
+ token_admin = True
+ else:
+ token_admin = proj.get("admin", False)
+
+ # add token roles
+ roles = []
+ roles_list = []
+ for prm in prm_list:
+ if prm["project"] in [project_id, project_name]:
+ role = self.db.get_one("roles", {BaseTopic.id_field("roles", prm["role"]): prm["role"]})
+ rid = role["_id"]
+ if rid not in roles:
+ rnm = role["name"]
+ roles.append(rid)
+ roles_list.append({"name": rnm, "id": rid})
+ if not roles_list:
+ rid = self.db.get_one("roles", {"name": "project_admin"})["_id"]
+ roles_list = [{"name": "project_admin", "id": rid}]
+
+ new_token = {"issued_at": now,
+ "expires": now + 3600,
+ "_id": token_id,
+ "id": token_id,
+ "project_id": proj["_id"],
+ "project_name": proj["name"],
+ "username": user_content["username"],
+ "user_id": user_content["_id"],
+ "admin": token_admin,
+ "roles": roles_list,
+ }
+
+ self.token_cache[token_id] = new_token
+ self.db.create("tokens", new_token)
+ return deepcopy(new_token)
+
+ def get_role_list(self, filter_q={}):
"""
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)
+ return self.db.get_list("roles", filter_q)
- def create_role(self, role):
+ def create_role(self, role_info):
"""
Create a role.
- :param role: role name.
+ :param role_info: full role info.
+ :return: returns the role id.
: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))
+ rid = str(uuid4())
+ role_info["_id"] = rid
+ rid = self.db.create("roles", role_info)
+ return rid
def delete_role(self, role_id):
"""
:param role_id: role identifier.
:raises AuthconnOperationException: if role deletion failed.
"""
- # try:
- # TODO: Check that role exists ?
+ return self.db.del_one("roles", {"_id": role_id})
+
+ def update_role(self, role_info):
+ """
+ Update a role.
+
+ :param role_info: full role info.
+ :return: returns the role name and id.
+ :raises AuthconnOperationException: if user creation failed.
+ """
+ rid = role_info["_id"]
+ self.db.set_one("roles", {"_id": rid}, role_info) # CONFIRM
+ return {"_id": rid, "name": role_info["name"]}
+
+ def create_user(self, user_info):
+ """
+ Create a user.
+
+ :param user_info: full user info.
+ :return: returns the username and id of the user.
+ """
+ BaseTopic.format_on_new(user_info, make_public=False)
+ salt = uuid4().hex
+ user_info["_admin"]["salt"] = salt
+ if "password" in user_info:
+ user_info["password"] = sha256(user_info["password"].encode('utf-8') + salt.encode('utf-8')).hexdigest()
+ # "projects" are not stored any more
+ if "projects" in user_info:
+ del user_info["projects"]
+ self.db.create("users", user_info)
+ return {"username": user_info["username"], "_id": user_info["_id"]}
+
+ def update_user(self, user_info):
+ """
+ Change the user name and/or password.
+
+ :param user_info: user info modifications
+ """
+ uid = user_info["_id"]
+ user_data = self.db.get_one("users", {BaseTopic.id_field("users", uid): uid})
+ BaseTopic.format_on_edit(user_data, user_info)
+ # User Name
+ usnm = user_info.get("username")
+ if usnm:
+ user_data["username"] = usnm
+ # If password is given and is not already encripted
+ pswd = user_info.get("password")
+ if pswd and (len(pswd) != 64 or not re.match('[a-fA-F0-9]*', pswd)): # TODO: Improve check?
+ salt = uuid4().hex
+ if "_admin" not in user_data:
+ user_data["_admin"] = {}
+ user_data["_admin"]["salt"] = salt
+ user_data["password"] = sha256(pswd.encode('utf-8') + salt.encode('utf-8')).hexdigest()
+ # Project-Role Mappings
+ # TODO: Check that user_info NEVER includes "project_role_mappings"
+ if "project_role_mappings" not in user_data:
+ user_data["project_role_mappings"] = []
+ for prm in user_info.get("add_project_role_mappings", []):
+ user_data["project_role_mappings"].append(prm)
+ for prm in user_info.get("remove_project_role_mappings", []):
+ for pidf in ["project", "project_name"]:
+ for ridf in ["role", "role_name"]:
+ try:
+ user_data["project_role_mappings"].remove({"role": prm[ridf], "project": prm[pidf]})
+ except KeyError:
+ pass
+ except ValueError:
+ pass
+ self.db.set_one("users", {BaseTopic.id_field("users", uid): uid}, user_data) # CONFIRM
+
+ def delete_user(self, user_id):
+ """
+ Delete user.
+
+ :param user_id: user identifier.
+ :raises AuthconnOperationException: if user deletion failed.
+ """
+ self.db.del_one("users", {"_id": user_id})
return True
- # except Exception:
- # raise AuthconnOperationException("Error during role deletion using internal backend")
+
+ def get_user_list(self, filter_q=None):
+ """
+ Get user list.
+
+ :param filter_q: dictionary to filter user list by name (username is also admited) and/or _id
+ :return: returns a list of users.
+ """
+ filt = filter_q or {}
+ if "name" in filt:
+ filt["username"] = filt["name"]
+ del filt["name"]
+ users = self.db.get_list("users", filt)
+ for user in users:
+ projects = []
+ projs_with_roles = []
+ prms = user.get("project_role_mappings", [])
+ for prm in prms:
+ if prm["project"] not in projects:
+ projects.append(prm["project"])
+ for project in projects:
+ roles = []
+ roles_for_proj = []
+ for prm in prms:
+ if prm["project"] == project and prm["role"] not in roles:
+ role = prm["role"]
+ roles.append(role)
+ rl = self.db.get_one("roles", {BaseTopic.id_field("roles", role): role})
+ roles_for_proj.append({"name": rl["name"], "_id": rl["_id"], "id": rl["_id"]})
+ try:
+ pr = self.db.get_one("projects", {BaseTopic.id_field("projects", project): project})
+ projs_with_roles.append({"name": pr["name"], "_id": pr["_id"], "id": pr["_id"],
+ "roles": roles_for_proj})
+ except Exception as e:
+ self.logger.exception("Error during user listing using internal backend: {}".format(e))
+ user["projects"] = projs_with_roles
+ if "project_role_mappings" in user:
+ del user["project_role_mappings"]
+ return users
+
+ def get_project_list(self, filter_q={}):
+ """
+ Get role list.
+
+ :return: returns the list of projects.
+ """
+ return self.db.get_list("projects", filter_q)
+
+ def create_project(self, project_info):
+ """
+ Create a project.
+
+ :param project: full project info.
+ :return: the internal id of the created project
+ :raises AuthconnOperationException: if project creation failed.
+ """
+ pid = self.db.create("projects", project_info)
+ return pid
+
+ def delete_project(self, project_id):
+ """
+ Delete a project.
+
+ :param project_id: project identifier.
+ :raises AuthconnOperationException: if project deletion failed.
+ """
+ filter_q = {BaseTopic.id_field("projects", project_id): project_id}
+ r = self.db.del_one("projects", filter_q)
+ return r
+
+ def update_project(self, project_id, project_info):
+ """
+ Change the name of a project
+
+ :param project_id: project to be changed
+ :param project_info: full project info
+ :return: None
+ :raises AuthconnOperationException: if project update failed.
+ """
+ self.db.set_one("projects", {BaseTopic.id_field("projects", project_id): project_id}, project_info)
class AuthconnKeystone(Authconn):
- def __init__(self, config):
- Authconn.__init__(self, config)
+ def __init__(self, config, db, token_cache):
+ Authconn.__init__(self, config, db, token_cache)
self.logger = logging.getLogger("nbi.authenticator.keystone")
raise AuthException("Error during user authentication using Keystone: {}".format(e),
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]
- #
- # new_token = self.keystone.get_raw_token_from_identity_service(
- # auth_url=self.auth_url,
- # token=token,
- # project_name=project,
- # project_id=None,
- # user_domain_name=self.user_domain_name,
- # project_domain_name=self.project_domain_name)
- #
- # return new_token["auth_token"], project_names
- # except ClientException as e:
- # # self.logger.exception("Error during user authentication using keystone. Method: bearer: {}".format(e))
- # raise AuthException("Error during user authentication using Keystone: {}".format(e),
- # http_code=HTTPStatus.UNAUTHORIZED)
-
def validate_token(self, token):
"""
Check if the token is valid.
raise AuthException("Error during token revocation using Keystone: {}".format(e),
http_code=HTTPStatus.UNAUTHORIZED)
- def get_user_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 as e:
- # self.logger.exception("Error during user project listing using keystone: {}".format(e))
- raise AuthException("Error during user project listing using Keystone: {}".format(e),
- http_code=HTTPStatus.UNAUTHORIZED)
-
- def get_user_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_info = self.keystone.roles.list(user=token_info["user"]["id"], project=token_info["project"]["id"])
-
- roles = [role.name for role in roles_info]
-
- return roles
- except ClientException as e:
- # self.logger.exception("Error during user role listing using keystone: {}".format(e))
- raise AuthException("Error during user role listing using Keystone: {}".format(e),
- http_code=HTTPStatus.UNAUTHORIZED)
-
- def create_user(self, user, password):
+ def create_user(self, user_info):
"""
Create a user.
- :param user: username.
- :param password: password.
+ :param user_info: full user info.
:raises AuthconnOperationException: if user creation failed.
:return: returns the id of the user in keystone.
"""
try:
- new_user = self.keystone.users.create(user, password=password, domain=self.user_domain_name)
+ new_user = self.keystone.users.create(user_info["username"], password=user_info["password"],
+ domain=self.user_domain_name, _admin=user_info["_admin"])
+ if "project_role_mappings" in user_info.keys():
+ for mapping in user_info["project_role_mappings"]:
+ self.assign_role_to_user(new_user.id, mapping["project"], mapping["role"])
return {"username": new_user.name, "_id": new_user.id}
except Conflict as e:
# self.logger.exception("Error during user creation using keystone: {}".format(e))
# self.logger.exception("Error during user creation using keystone: {}".format(e))
raise AuthconnOperationException("Error during user creation using Keystone: {}".format(e))
- def update_user(self, user, new_name=None, new_password=None):
+ def update_user(self, user_info):
"""
Change the user name and/or password.
- :param user: username or user_id
- :param new_name: new name
- :param new_password: new password.
+ :param user_info: user info modifications
:raises AuthconnOperationException: if change failed.
"""
try:
+ user = user_info.get("_id") or user_info.get("username")
if is_valid_uuid(user):
- user_id = user
+ user_obj_list = [self.keystone.users.get(user)]
else:
user_obj_list = self.keystone.users.list(name=user)
- if not user_obj_list:
- raise AuthconnNotFoundException("User '{}' not found".format(user))
- user_id = user_obj_list[0].id
-
- self.keystone.users.update(user_id, password=new_password, name=new_name)
+ if not user_obj_list:
+ raise AuthconnNotFoundException("User '{}' not found".format(user))
+ user_obj = user_obj_list[0]
+ user_id = user_obj.id
+ if user_info.get("password") or user_info.get("username") \
+ or user_info.get("add_project_role_mappings") or user_info.get("remove_project_role_mappings"):
+ self.keystone.users.update(user_id, password=user_info.get("password"), name=user_info.get("username"),
+ _admin={"created": user_obj._admin["created"], "modified": time.time()})
+ for mapping in user_info.get("remove_project_role_mappings", []):
+ self.remove_role_from_user(user_id, mapping["project"], mapping["role"])
+ for mapping in user_info.get("add_project_role_mappings", []):
+ self.assign_role_to_user(user_id, mapping["project"], mapping["role"])
except ClientException as e:
# self.logger.exception("Error during user password/name update using keystone: {}".format(e))
- raise AuthconnOperationException("Error during user password/name update using Keystone: {}".format(e))
+ raise AuthconnOperationException("Error during user update using Keystone: {}".format(e))
def delete_user(self, user_id):
"""
:raises AuthconnOperationException: if user deletion failed.
"""
try:
- # users = self.keystone.users.list()
- # user_obj = [user for user in users if user.id == user_id][0]
- # result, _ = self.keystone.users.delete(user_obj)
-
result, detail = self.keystone.users.delete(user_id)
if result.status_code != 204:
raise ClientException("error {} {}".format(result.status_code, detail))
-
return True
except ClientException as e:
# self.logger.exception("Error during user deletion using keystone: {}".format(e))
users = [{
"username": user.name,
"_id": user.id,
- "id": user.id
+ "id": user.id,
+ "_admin": user.to_dict().get("_admin", {}) # TODO: REVISE
} for user in users if user.name != self.admin_username]
if filter_q and filter_q.get("_id"):
roles = [{
"name": role.name,
- "_id": role.id
+ "_id": role.id,
+ "_admin": role.to_dict().get("_admin", {}),
+ "permissions": role.to_dict().get("permissions", {})
} for role in roles_list if role.name != "service"]
if filter_q and filter_q.get("_id"):
raise AuthException("Error during user role listing using Keystone: {}".format(e),
http_code=HTTPStatus.UNAUTHORIZED)
- def create_role(self, role):
+ def create_role(self, role_info):
"""
Create a role.
- :param role: role name.
+ :param role_info: full role info.
:raises AuthconnOperationException: if role creation failed.
"""
try:
- result = self.keystone.roles.create(role)
+ result = self.keystone.roles.create(role_info["name"], permissions=role_info.get("permissions"),
+ _admin=role_info.get("_admin"))
return result.id
except Conflict as ex:
raise AuthconnConflictException(str(ex))
# self.logger.exception("Error during role deletion using keystone: {}".format(e))
raise AuthconnOperationException("Error during role deletion using Keystone: {}".format(e))
- def update_role(self, role, new_name):
+ def update_role(self, role_info):
"""
Change the name of a role
- :param role: role name or id to be changed
- :param new_name: new name
+ :param role_info: full role info
:return: None
"""
try:
- if is_valid_uuid(role):
- role_id = role
- else:
- role_obj_list = self.keystone.roles.list(name=role)
+ rid = role_info["_id"]
+ if not is_valid_uuid(rid): # Is this required?
+ role_obj_list = self.keystone.roles.list(name=rid)
if not role_obj_list:
- raise AuthconnNotFoundException("Role '{}' not found".format(role))
- role_id = role_obj_list[0].id
- self.keystone.roles.update(role_id, name=new_name)
+ raise AuthconnNotFoundException("Role '{}' not found".format(rid))
+ rid = role_obj_list[0].id
+ self.keystone.roles.update(rid, name=role_info["name"], permissions=role_info.get("permissions"),
+ _admin=role_info.get("_admin"))
except ClientException as e:
# self.logger.exception("Error during role update using keystone: {}".format(e))
raise AuthconnOperationException("Error during role updating using Keystone: {}".format(e))
projects = [{
"name": project.name,
- "_id": project.id
+ "_id": project.id,
+ "_admin": project.to_dict().get("_admin", {}) # TODO: REVISE
} for project in projects]
if filter_q and filter_q.get("_id"):
raise AuthException("Error during user project listing using Keystone: {}".format(e),
http_code=HTTPStatus.UNAUTHORIZED)
- def create_project(self, project):
+ def create_project(self, project_info):
"""
Create a project.
- :param project: project name.
+ :param project_info: full project info.
:return: the internal id of the created project
:raises AuthconnOperationException: if project creation failed.
"""
try:
- result = self.keystone.projects.create(project, self.project_domain_name)
+ result = self.keystone.projects.create(project_info["name"], self.project_domain_name,
+ _admin=project_info["_admin"])
return result.id
except ClientException as e:
# self.logger.exception("Error during project creation using keystone: {}".format(e))
# self.logger.exception("Error during project deletion using keystone: {}".format(e))
raise AuthconnOperationException("Error during project deletion using Keystone: {}".format(e))
- def update_project(self, project_id, new_name):
+ def update_project(self, project_id, project_info):
"""
Change the name of a project
:param project_id: project to be changed
- :param new_name: new name
+ :param project_info: full project info
:return: None
"""
try:
- self.keystone.projects.update(project_id, name=new_name)
+ self.keystone.projects.update(project_id, name=project_info["name"], _admin=project_info["_admin"])
except ClientException as e:
# self.logger.exception("Error during project update using keystone: {}".format(e))
- raise AuthconnOperationException("Error during project deletion using Keystone: {}".format(e))
+ raise AuthconnOperationException("Error during project update using Keystone: {}".format(e))
def assign_role_to_user(self, user, project, role):
"""
alt_id_field = {
"projects": "name",
"users": "username",
- "roles": "name",
- "roles_operations": "name"
+ "roles": "name"
}
def __init__(self, db, fs, msg):
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 VimAccountTopic, WimAccountTopic, SdnTopic
from admin_topics import UserTopicAuth, ProjectTopicAuth, RoleTopicAuth
from descriptor_topics import VnfdTopic, NsdTopic, PduTopic, NstTopic
from instance_topics import NsrTopic, VnfrTopic, NsLcmOpTopic, NsiTopic, NsiLcmOpTopic
"vim_accounts": VimAccountTopic,
"wim_accounts": WimAccountTopic,
"sdns": SdnTopic,
- "users": UserTopic,
- "projects": ProjectTopic,
+ "users": UserTopicAuth, # Valid for both internal and keystone authentication backends
+ "projects": ProjectTopicAuth, # Valid for both internal and keystone authentication backends
"roles": RoleTopicAuth, # Valid for both internal and keystone authentication backends
"nsis": NsiTopic,
"nsilcmops": NsiLcmOpTopic
config["message"]["driver"]))
if not self.auth:
if config["authentication"]["backend"] == "keystone":
- self.auth = AuthconnKeystone(config["authentication"])
+ self.auth = AuthconnKeystone(config["authentication"], self.db, None)
else:
- self.auth = AuthconnInternal(config["authentication"], self.db, dict()) # TO BE CONFIRMED
+ self.auth = AuthconnInternal(config["authentication"], self.db, dict())
if not self.operations:
if "resources_to_operations" in config["rbac"]:
resources_to_operations_file = config["rbac"]["resources_to_operations"]
if value not in self.operations:
self.operations += [value]
- if config["authentication"]["backend"] == "keystone":
- self.map_from_topic_to_class["users"] = UserTopicAuth
- self.map_from_topic_to_class["projects"] = ProjectTopicAuth
- self.map_from_topic_to_class["roles"] = RoleTopicAuth
-
self.write_lock = Lock()
# create one class per topic
for topic, topic_class in self.map_from_topic_to_class.items():
:param session: contains the used login username and working project
:param topic: it can be: users, projects, vnfds, nsds, ...
:param _id: server id of the item
- :return: operation id (None if there is not operation), raise exception if error or not found.
+ :return: dictionary with deleted item _id. It raises exception if not found.
"""
if topic not in self.map_topic:
raise EngineException("Unknown topic {}!!!".format(topic), HTTPStatus.INTERNAL_SERVER_ERROR)
:param _id: identifier to be updated
:param indata: data to be inserted
:param kwargs: used to override the indata descriptor
- :return: operation id (None if there is not operation), raise exception if error or not found.
+ :return: dictionary with edited item _id, raise exception if not found.
"""
if topic not in self.map_topic:
raise EngineException("Unknown topic {}!!!".format(topic), HTTPStatus.INTERNAL_SERVER_ERROR)
with self.write_lock:
return self.map_topic[topic].edit(session, _id, indata, kwargs)
- def create_admin_project(self):
- """
- Creates a new project 'admin' into database if database is empty. Useful for initialization.
- :return: _id identity of the inserted data, or None
- """
-
- projects = self.db.get_one("projects", fail_on_empty=False, fail_on_more=False)
- if projects:
- return None
- project_desc = {"name": "admin"}
- fake_session = {"project_id": "admin", "username": "admin", "admin": True, "force": True, "public": None}
- rollback_list = []
- _id = self.map_topic["projects"].new(rollback_list, fake_session, project_desc)
- return _id
-
- def create_admin_user(self):
- """
- Creates a new user admin/admin into database if database is empty. Useful for initialization
- :return: _id identity of the inserted data, or None
- """
- users = self.db.get_one("users", fail_on_empty=False, fail_on_more=False)
- if users:
- return None
- user_desc = {"username": "admin", "password": "admin", "projects": ["admin"]}
- fake_session = {"project_id": "admin", "username": "admin", "admin": True, "force": True, "public": None}
- rollback_list = []
- _id = self.map_topic["users"].new(rollback_list, fake_session, user_desc)
- return _id
-
- def create_admin(self):
- """
- Creates new 'admin' user and project into database if database is empty. Useful for initialization.
- :return: _id identity of the inserted data, or None
- """
- project_id = self.create_admin_project()
- user_id = self.create_admin_user()
- 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():
raise EngineException("Cannot upgrade to version '{}' with this version of code".format(target_version),
current_version = "1.0"
if current_version in ("1.0", "1.1") and target_version_int >= self.map_target_version_to_int["1.2"]:
- table = "roles_operations" if self.config['authentication']['backend'] == "keystone" else "roles"
- self.db.del_list(table)
-
+ if self.config['authentication']['backend'] == "internal":
+ self.db.del_list("roles")
+
version_data = {
"_id": "version",
"version_int": 1002,
if db_version != target_version:
self.upgrade_db(db_version, target_version)
- # create admin project&user if they don't exist
- if self.config['authentication']['backend'] == 'internal' or not self.auth:
- self.create_admin()
-
return
import getopt
import sys
-from authconn import AuthException
+from authconn import AuthException, AuthconnException
from auth import Authenticator
from engine import Engine, EngineException
from subscriptions import SubscriptionThread
try:
if cherrypy.request.method == "GET":
token_info = self.authenticator.authorize()
- outdata = "Index page"
+ outdata = token_info # Home page
else:
raise cherrypy.HTTPError(HTTPStatus.METHOD_NOT_ALLOWED.value,
"Method {} not allowed for tokens".format(cherrypy.request.method))
return self._format_out(outdata, token_info, _format)
except Exception as e:
if isinstance(e, (NbiException, EngineException, DbException, FsException, MsgException, AuthException,
- ValidationError)):
+ ValidationError, AuthconnException)):
http_code_value = cherrypy.response.status = e.http_code.value
http_code_name = e.http_code.name
cherrypy.log("Exception {}".format(e))
try:
with open("{}/version".format(engine_config["/static"]['tools.staticdir.dir'])) as version_file:
version_data = version_file.read()
- cherrypy.log.error("Starting OSM NBI Version: {}".format(version_data.replace("\n", " ")))
+ version = version_data.replace("\n", " ")
+ backend = engine_config["authentication"]["backend"]
+ cherrypy.log.error("Starting OSM NBI Version {} with {} authentication backend"
+ .format(version, backend))
except Exception:
pass