Added seed code for access-management.
[ta/access-management.git] / src / access_management / rest-plugin / users.py
diff --git a/src/access_management/rest-plugin/users.py b/src/access_management/rest-plugin/users.py
new file mode 100644 (file)
index 0000000..ec708c8
--- /dev/null
@@ -0,0 +1,411 @@
+# 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": <uuid>
+            }
+        }
+
+    :> 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": <uuid> or <username>
+        }
+
+    :> 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))