# Copyright 2019 Nokia # 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. import time import access_management.db.amdb as amdb from am_api_base import * from keystoneauth1 import exceptions from cmframework.apis import cmclient class Users(AMApiBase): """ User create operations .. :quickref: Users;User create operations .. http:post:: /am/v1/users **Start User create** **Example request**: .. sourcecode:: http POST am/v1/users HTTP/1.1 Host: haproxyvip:61200 Accept: application/json { "username": "user_1", "password": "Passwd_1", "email": "test@mail.com", "project": "10f8fa2c6efe409d8207517128f03265", "description": "desc" } :> json string username: The created user name. :> json string password: The user's password. :> json string email: The user's e-mail. :> json string project: ID of the project to be set as primary project for the user. :> json string description: The user's description. **Example response**: .. sourcecode:: http HTTP/1.1 200 OK { "code": 0, "description": "", "data": { "id": } } :> json int code: the status code :> json string description: the error description, present if code is non zero :> json object data: a dictionary with the created user's id :> json string id: The created user's id. Users list operations .. :quickref: Users;Users list operations .. http:get:: /am/v1/users **Start Users list** **Example request**: .. sourcecode:: http GET am/v1/users HTTP/1.1 Host: haproxyvip:61200 Accept: application/json **Example response**: .. sourcecode:: http HTTP/1.1 200 OK { "code": 0, "description": "", "data": { "0edf341a27544c349b7c37bb76ab25d1": { "enabled": true, "id": "0edf341a27544c349b7c37bb76ab25d1", "name": "cinder", "password_expires_at": null }, "32e8859519f94b1ea80f61d53d17e74e": { "enabled": true, "id": "32e8859519f94b1ea80f61d53d17e74e", "name": "nova", "password_expires_at": null } } } :> json int code: the status code :> json string description: the error description, present if code is non zero :> json object data: The existing users. :> json string enabled: The user's state. :> json string id: The user's id. :> json string name: The user's name. :> json string password_expires_at: The user's password expiration date. User delete operations .. :quickref: Users;User delete operations .. http:delete:: /am/v1/users **Start User delete** **Example request**: .. sourcecode:: http DELETE am/v1/users HTTP/1.1 Host: haproxyvip:61200 Accept: application/json { "user": or } :> json string user: The removed user's id or user name. **Example response**: .. sourcecode:: http HTTP/1.1 200 OK { "code": 0, "description": "User deleted!" } :> json int code: the status code :> json string description: the error description, present if code is non zero """ endpoints = ['users'] parser_arguments = ['username', 'password', 'email', 'user', 'project', 'description'] def post(self): self.logger.info("Received a user create request!") args = self.parse_args() if args["email"] is not None: if re.match("^[\.a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+\.[a-z]+$", args["email"]) is None: self.logger.error("E-mail validation failed!") return AMApiBase.embed_data({}, 1, "E-mail validation failed!") if self.id_validator(args["username"]): self.logger.error("{0} username is invalid, because cannot assign a valid uuid to it.".format(args["username"])) return AMApiBase.embed_data({}, 1, "{0} username is invalid, because cannot assign a valid uuid to it.".format(args["username"])) if args["project"]: projectidstate = self.id_validator(args["project"]) if projectidstate == False: self.logger.error("Project id validation failed") return AMApiBase.embed_data({}, 1, "Project id validation failed") if re.match("^[a-zA-Z0-9_-]+$", args["username"]) is None: self.logger.error("Username validation failed!") return AMApiBase.embed_data({}, 1, "Username validation failed!") passstate = self.passwd_validator(args["password"]) if passstate is not None: self.logger.error(passstate) return AMApiBase.embed_data({}, 1, passstate) state, result = self._create_user(args) if state: self.logger.info("User created!") return AMApiBase.embed_data({"id": result}, 0, "") else: return AMApiBase.embed_data({}, 1, result) def get(self): self.logger.info("Received a user list request!") user_list = {} try: self.keystone = self.auth_keystone() u_list = self.keystone.users.list() except Exception as ex: self.logger.error("{0}".format(ex)) return False, "{0}".format(ex) for element in u_list: user_list.update({element.id : element._info}) self.logger.info("The user list response done!") return AMApiBase.embed_data(user_list, 0, "User list.") def delete(self): self.logger.info("Received a user delete request!") args = self.parse_args() state, user_info = self.get_uuid_and_name(args["user"]) if state: token_owner = self.get_uuid_from_token() if user_info["id"] == token_owner: self.logger.error("The {0} user tried to delete own account!".format(user_info["id"])) return AMApiBase.embed_data({}, 1, "You cannot delete your own account!") state, message = self._delete_user(user_info) if state: self.logger.info("User deleted!") return AMApiBase.embed_data({}, 0, "User deleted!") else: self.logger.error(message) return AMApiBase.embed_data({}, 1, message) else: self.logger.error(user_info) return AMApiBase.embed_data({}, 1, user_info) def _delete_user(self, user_info): state, name = self._delete_user_from_db(user_info) if state: self.logger.info("User removed from the db!") try: self.keystone.users.delete(user_info["id"]) except exceptions.http.NotFound as ex: self.logger.info("{0} user does not exist in the keystone!".format(user_info["name"])) return True, "Done, but this user didn't exist in the keystone!" except Exception as ex: self.logger.error("{0}".format(ex)) return False, "{0}".format(ex) return True, "Done" else: return False, name def _delete_user_from_db(self, user_info): state_open, message_open = self._open_db() if state_open: try: roles = self.db.get_user_roles(user_info["id"]) for role in roles: if self.db.is_chroot_role(role): self.logger.debug("This user has a chroot role.") for x in range(3): self.remove_chroot_linux_role_handling(user_info["id"], "Chroot", "cloud.chroot") time.sleep(2) if self.check_chroot_linux_state(user_info["name"], "cloud.chroot", "absent"): self.db.delete_user(user_info["id"]) return True, user_info["name"] if role == "linux_user": self.logger.debug("This user has a linux_user role!") for x in range(3): self.remove_chroot_linux_role_handling(user_info["id"], "Linux", "cloud.linuxuser") time.sleep(2) if self.check_chroot_linux_state(user_info["name"], "cloud.linuxuser", "absent"): self.db.delete_user(user_info["id"]) return True, user_info["name"] self.db.delete_user(user_info["id"]) except amdb.NotAllowedOperation: self.logger.error("Deleting service user is not allowed: {0}".format(user_info["name"])) return False, "Deleting service user is not allowed: {0}".format(user_info["name"]) except amdb.NotExist: self.logger.info("The {0} user does not exist!".format(user_info["name"])) return True, "" except Exception as ex: self.logger.error("Internal error: {0}".format(ex)) return False, "Internal error: {0}".format(ex) finally: state_close, message_close = self._close_db() if not state_close: self._close_db() return True, user_info["name"] else: return False, message_open def _create_user(self, args): roles = [] ks_member_roleid = self.get_role_id(defaults.KS_MEMBER_NAME) if ks_member_roleid is None: self.logger.error("Member user role not found!") return False, "Member user role not found!" else: roles.append(ks_member_roleid) basic_member_roleid = self.get_role_id(defaults.AM_MEMBER_NAME) if basic_member_roleid is None: self.logger.error("basic_member user role not found!") return False, "basic_member user role not found!" else: roles.append(basic_member_roleid) um_proj_id = self.get_project_id(defaults.PROJECT_NAME) if um_proj_id is None: self.logger.error("The user management project is not found!") return False, "The user management project is not found!" if args["email"] is None: args["email"] = 'None' if args["project"] is None: args["project"] = um_proj_id try: c_user_out = self.keystone.users.create(name=args["username"], password=args["password"], email=args["email"], default_project=args["project"], description=args["description"]) except exceptions.http.Conflict as ex: self.logger.error("{0}".format(ex)) return False, "This user exists in the keystone!" except Exception as ex: self.logger.error("{0}".format(ex)) return False, "{0}".format(ex) ID = c_user_out.id state, message = self._add_basic_roles(um_proj_id, ID, roles) if not state: return False, message if args["project"] != um_proj_id: state, message = self._add_basic_roles(args["project"], ID, [ks_member_roleid]) if not state: return False, message return self._create_user_in_db(ID, args) def _add_basic_roles(self, project, ID, roles): for role in roles: try: self.keystone.roles.grant(role, user=ID, project=project) except Exception: try: self.keystone.roles.grant(role, user=ID, project=project) except Exception as ex: self.logger.error("{0}".format(ex)) self.keystone.users.delete(ID) return False, "{0}".format(ex) return True, "OK" def _create_user_in_db(self, ID, args): state_open, message_open = self._open_db() if state_open: try: self.db.create_user(ID, args["username"]) self.db.add_user_role(ID, defaults.AM_MEMBER_NAME) except amdb.AlreadyExist as ex1: self.logger.error("User already exists in table!") try: self.keystone.users.delete(ID) self.db.delete_user(ID) except amdb.NotAllowedOperation as ex2: self.logger.error("Internal error: Except1: {0}, Except2: {1}".format(ex1, ex2)) return False, "Except1: {0}, Except2: {1}".format(ex1, ex2) except Exception as ex3: self.logger.error("Internal error: Except1: {0}, Except2: {1}".format(ex1, ex3)) return False, "Except1: {0}, Except2: {1}".format(ex1, ex3) return False, "User already exists!" except Exception as ex: self.logger.error("Internal error: {0}".format(ex)) try: self.keystone.users.delete(ID) except exceptions.http.NotFound as ex: self.logger.error("{0}".format(ex)) return False, "This user does not exist in the keystone!" except Exception as ex: self.logger.error("{0}".format(ex)) return False, "{0}".format(ex) return False, "Internal error: {0}".format(ex) finally: state_close, message_close = self._close_db() if not state_close: self._close_db() return True, ID else: return False, message_open def remove_chroot_linux_role_handling(self, user_id, user_type, list_name): cmc = cmclient.CMClient() user_list = cmc.get_property(list_name) user_list = json.loads(user_list) self.logger.debug("{0} user list before the change: {1}".format(user_type, json.dumps(user_list))) if user_list is not None: self.logger.debug("The {0} user list exists!".format(user_type)) username, def_project = self.get_user_from_uuid(user_id) self.logger.debug("User name: {0}".format(username)) for val in user_list: if val["name"] == username: val["public_key"] = "" val["state"] = "absent" val["remove"] = "yes" val["password"] = "" break self.logger.debug("{0} user list after the change: {1}".format(user_type, json.dumps(user_list))) cmc.set_property(list_name, json.dumps(user_list))