Fix bug 724
[osm/NBI.git] / osm_nbi / authconn_keystone.py
index 068c213..7f59270 100644 (file)
@@ -23,6 +23,7 @@ AuthconnKeystone implements implements the connector for
 Openstack Keystone and leverages the RBAC model, to bring
 it for OSM.
 """
 Openstack Keystone and leverages the RBAC model, to bring
 it for OSM.
 """
+import time
 
 __author__ = "Eduardo Sousa <esousa@whitestack.com>"
 __date__ = "$27-jul-2018 23:59:59$"
 
 __author__ = "Eduardo Sousa <esousa@whitestack.com>"
 __date__ = "$27-jul-2018 23:59:59$"
@@ -30,9 +31,11 @@ __date__ = "$27-jul-2018 23:59:59$"
 from authconn import Authconn, AuthException, AuthconnOperationException
 
 import logging
 from authconn import Authconn, AuthException, AuthconnOperationException
 
 import logging
+import requests
 from keystoneauth1 import session
 from keystoneauth1.identity import v3
 from keystoneauth1.exceptions.base import ClientException
 from keystoneauth1 import session
 from keystoneauth1.identity import v3
 from keystoneauth1.exceptions.base import ClientException
+from keystoneauth1.exceptions.http import Conflict
 from keystoneclient.v3 import client
 from http import HTTPStatus
 
 from keystoneclient.v3 import client
 from http import HTTPStatus
 
@@ -50,6 +53,19 @@ class AuthconnKeystone(Authconn):
         self.admin_password = config.get("service_password", "nbi")
         self.project_domain_name = config.get("project_domain_name", "default")
 
         self.admin_password = config.get("service_password", "nbi")
         self.project_domain_name = config.get("project_domain_name", "default")
 
+        # Waiting for Keystone to be up
+        available = None
+        counter = 300
+        while available is None:
+            time.sleep(1)
+            try:
+                result = requests.get(self.auth_url)
+                available = True if result.status_code == 200 else None
+            except Exception:
+                counter -= 1
+                if counter == 0:
+                    raise AuthException("Keystone not available after 300s timeout")
+
         self.auth = v3.Password(user_domain_name=self.user_domain_name,
                                 username=self.admin_username,
                                 password=self.admin_password,
         self.auth = v3.Password(user_domain_name=self.user_domain_name,
                                 username=self.admin_username,
                                 password=self.admin_password,
@@ -98,14 +114,14 @@ class AuthconnKeystone(Authconn):
             projects = self.keystone.projects.list(user=token_info["user"]["id"])
             project_names = [project.name for project in projects]
 
             projects = self.keystone.projects.list(user=token_info["user"]["id"])
             project_names = [project.name for project in projects]
 
-            token = self.keystone.get_raw_token_from_identity_service(
+            new_token = self.keystone.get_raw_token_from_identity_service(
                 auth_url=self.auth_url,
                 token=token,
                 project_name=project,
                 user_domain_name=self.user_domain_name,
                 project_domain_name=self.project_domain_name)
 
                 auth_url=self.auth_url,
                 token=token,
                 project_name=project,
                 user_domain_name=self.user_domain_name,
                 project_domain_name=self.project_domain_name)
 
-            return token["auth_token"], project_names
+            return new_token["auth_token"], project_names
         except ClientException:
             self.logger.exception("Error during user authentication using keystone. Method: bearer")
             raise AuthException("Error during user authentication using Keystone", http_code=HTTPStatus.UNAUTHORIZED)
         except ClientException:
             self.logger.exception("Error during user authentication using keystone. Method: bearer")
             raise AuthException("Error during user authentication using Keystone", http_code=HTTPStatus.UNAUTHORIZED)
@@ -136,6 +152,7 @@ class AuthconnKeystone(Authconn):
         :param token: token to be revoked
         """
         try:
         :param token: token to be revoked
         """
         try:
+            self.logger.info("Revoking token: " + token)
             self.keystone.tokens.revoke_token(token=token)
 
             return True
             self.keystone.tokens.revoke_token(token=token)
 
             return True
@@ -143,7 +160,7 @@ class AuthconnKeystone(Authconn):
             self.logger.exception("Error during token revocation using keystone")
             raise AuthException("Error during token revocation using Keystone", http_code=HTTPStatus.UNAUTHORIZED)
 
             self.logger.exception("Error during token revocation using keystone")
             raise AuthException("Error during token revocation using Keystone", http_code=HTTPStatus.UNAUTHORIZED)
 
-    def get_project_list(self, token):
+    def get_user_project_list(self, token):
         """
         Get all the projects associated with a user.
 
         """
         Get all the projects associated with a user.
 
@@ -160,7 +177,7 @@ class AuthconnKeystone(Authconn):
             self.logger.exception("Error during user project listing using keystone")
             raise AuthException("Error during user project listing using Keystone", http_code=HTTPStatus.UNAUTHORIZED)
 
             self.logger.exception("Error during user project listing using keystone")
             raise AuthException("Error during user project listing using Keystone", http_code=HTTPStatus.UNAUTHORIZED)
 
-    def get_role_list(self, token):
+    def get_user_role_list(self, token):
         """
         Get role list for a scoped project.
 
         """
         Get role list for a scoped project.
 
@@ -170,7 +187,9 @@ class AuthconnKeystone(Authconn):
         """
         try:
             token_info = self.keystone.tokens.validate(token=token)
         """
         try:
             token_info = self.keystone.tokens.validate(token=token)
-            roles = self.keystone.roles.list(user=token_info["user"]["id"], project=token_info["project"]["id"])
+            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:
 
             return roles
         except ClientException:
@@ -184,12 +203,11 @@ class AuthconnKeystone(Authconn):
         :param user: username.
         :param password: password.
         :raises AuthconnOperationException: if user creation failed.
         :param user: username.
         :param password: password.
         :raises AuthconnOperationException: if user creation failed.
+        :return: returns the id of the user in keystone.
         """
         try:
         """
         try:
-            result = self.keystone.users.create(user, password=password, domain=self.user_domain_name)
-
-            if not result:
-                raise ClientException()
+            new_user = self.keystone.users.create(user, password=password, domain=self.user_domain_name)
+            return {"username": new_user.name, "_id": new_user.id}
         except ClientException:
             self.logger.exception("Error during user creation using keystone")
             raise AuthconnOperationException("Error during user creation using Keystone")
         except ClientException:
             self.logger.exception("Error during user creation using keystone")
             raise AuthconnOperationException("Error during user creation using Keystone")
@@ -203,30 +221,98 @@ class AuthconnKeystone(Authconn):
         :raises AuthconnOperationException: if user password change failed.
         """
         try:
         :raises AuthconnOperationException: if user password change failed.
         """
         try:
-            result = self.keystone.users.update(user, password=new_password)
-
-            if not result:
-                raise ClientException()
+            user_obj = list(filter(lambda x: x.name == user, self.keystone.users.list()))[0]
+            self.keystone.users.update(user_obj, password=new_password)
         except ClientException:
             self.logger.exception("Error during user password update using keystone")
             raise AuthconnOperationException("Error during user password update using Keystone")
 
         except ClientException:
             self.logger.exception("Error during user password update using keystone")
             raise AuthconnOperationException("Error during user password update using Keystone")
 
-    def delete_user(self, user):
+    def delete_user(self, user_id):
         """
         Delete user.
 
         """
         Delete user.
 
-        :param user: username.
+        :param user_id: user identifier.
         :raises AuthconnOperationException: if user deletion failed.
         """
         try:
         :raises AuthconnOperationException: if user deletion failed.
         """
         try:
-            result = self.keystone.users.delete(user)
+            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)
+
+            if result.status_code != 204:
+                raise ClientException("User was not deleted")
 
 
-            if not result:
-                raise ClientException()
+            return True
         except ClientException:
             self.logger.exception("Error during user deletion using keystone")
             raise AuthconnOperationException("Error during user deletion using Keystone")
 
         except ClientException:
             self.logger.exception("Error during user deletion using keystone")
             raise AuthconnOperationException("Error during user deletion using Keystone")
 
+    def get_user_list(self, filter_q={}):
+        """
+        Get user list.
+
+        :param filter_q: dictionary to filter user list.
+        :return: returns a list of users.
+        """
+        try:
+            users = self.keystone.users.list()
+            users = [{
+                "username": user.name,
+                "_id": user.id,
+                "id": user.id
+            } for user in users if user.name != self.admin_username]
+
+            allowed_fields = ["_id", "id", "username"]
+            for key in filter_q.keys():
+                if key not in allowed_fields:
+                    continue
+
+                users = [user for user in users 
+                         if filter_q[key] == user[key]]
+
+            for user in users:
+                projects = self.keystone.projects.list(user=user["_id"])
+                projects = [{
+                    "name": project.name,
+                    "_id": project.id,
+                    "id": project.id
+                } for project in projects]
+
+                for project in projects:
+                    roles = self.keystone.roles.list(user=user["_id"], project=project["_id"])
+                    roles = [{
+                        "name": role.name,
+                        "_id": role.id,
+                        "id": role.id
+                    } for role in roles]
+                    project["roles"] = roles
+
+                user["projects"] = projects
+
+            return users
+        except ClientException:
+            self.logger.exception("Error during user listing using keystone")
+            raise AuthconnOperationException("Error during user listing using Keystone")
+
+    def get_role_list(self):
+        """
+        Get role list.
+
+        :return: returns the list of roles.
+        """
+        try:
+            roles_list = self.keystone.roles.list()
+
+            roles = [{
+                "name": role.name,
+                "_id": role.id
+            } for role in roles_list if role.name != "service"]
+
+            return roles
+        except ClientException:
+            self.logger.exception("Error during user role listing using keystone")
+            raise AuthException("Error during user role listing using Keystone", http_code=HTTPStatus.UNAUTHORIZED)
+
     def create_role(self, role):
         """
         Create a role.
     def create_role(self, role):
         """
         Create a role.
@@ -235,30 +321,61 @@ class AuthconnKeystone(Authconn):
         :raises AuthconnOperationException: if role creation failed.
         """
         try:
         :raises AuthconnOperationException: if role creation failed.
         """
         try:
-            result = self.keystone.roles.create(role, domain=self.user_domain_name)
-
-            if not result:
-                raise ClientException()
+            result = self.keystone.roles.create(role)
+            return {"name": result.name, "_id": result.id}
+        except Conflict as ex:
+            self.logger.info("Duplicate entry: %s", str(ex))
         except ClientException:
             self.logger.exception("Error during role creation using keystone")
             raise AuthconnOperationException("Error during role creation using Keystone")
 
         except ClientException:
             self.logger.exception("Error during role creation using keystone")
             raise AuthconnOperationException("Error during role creation using Keystone")
 
-    def delete_role(self, role):
+    def delete_role(self, role_id):
         """
         Delete a role.
 
         """
         Delete a role.
 
-        :param role: role name.
+        :param role_id: role identifier.
         :raises AuthconnOperationException: if role deletion failed.
         """
         try:
         :raises AuthconnOperationException: if role deletion failed.
         """
         try:
-            result = self.keystone.roles.delete(role)
+            roles = self.keystone.roles.list()
+            role_obj = [role for role in roles if role.id == role_id][0]
+            result, _ = self.keystone.roles.delete(role_obj)
+
+            if result.status_code != 204:
+                raise ClientException("Role was not deleted")
 
 
-            if not result:
-                raise ClientException()
+            return True
         except ClientException:
             self.logger.exception("Error during role deletion using keystone")
             raise AuthconnOperationException("Error during role deletion using Keystone")
 
         except ClientException:
             self.logger.exception("Error during role deletion using keystone")
             raise AuthconnOperationException("Error during role deletion using Keystone")
 
+    def get_project_list(self, filter_q={}):
+        """
+        Get all the projects.
+
+        :param filter_q: dictionary to filter project list.
+        :return: list of projects
+        """
+        try:
+            projects = self.keystone.projects.list()
+            projects = [{
+                "name": project.name,
+                "_id": project.id
+            } for project in projects if project.name != self.admin_project]
+
+            allowed_fields = ["_id", "name"]
+            for key in filter_q.keys():
+                if key not in allowed_fields:
+                    continue
+
+                projects = [project for project in projects
+                            if filter_q[key] == project[key]]
+
+            return projects
+        except ClientException:
+            self.logger.exception("Error during user project listing using keystone")
+            raise AuthException("Error during user project listing using Keystone", http_code=HTTPStatus.UNAUTHORIZED)
+
     def create_project(self, project):
         """
         Create a project.
     def create_project(self, project):
         """
         Create a project.
@@ -267,26 +384,28 @@ class AuthconnKeystone(Authconn):
         :raises AuthconnOperationException: if project creation failed.
         """
         try:
         :raises AuthconnOperationException: if project creation failed.
         """
         try:
-            result = self.keystone.project.create(project, self.project_domain_name)
-
-            if not result:
-                raise ClientException()
+            result = self.keystone.projects.create(project, self.project_domain_name)
+            return {"name": result.name, "_id": result.id}
         except ClientException:
             self.logger.exception("Error during project creation using keystone")
             raise AuthconnOperationException("Error during project creation using Keystone")
 
         except ClientException:
             self.logger.exception("Error during project creation using keystone")
             raise AuthconnOperationException("Error during project creation using Keystone")
 
-    def delete_project(self, project):
+    def delete_project(self, project_id):
         """
         Delete a project.
 
         """
         Delete a project.
 
-        :param project: project name.
+        :param project_id: project identifier.
         :raises AuthconnOperationException: if project deletion failed.
         """
         try:
         :raises AuthconnOperationException: if project deletion failed.
         """
         try:
-            result = self.keystone.project.delete(project)
+            projects = self.keystone.projects.list()
+            project_obj = [project for project in projects if project.id == project_id][0]
+            result, _ = self.keystone.projects.delete(project_obj)
 
 
-            if not result:
-                raise ClientException()
+            if result.status_code != 204:
+                raise ClientException("Project was not deleted")
+
+            return True
         except ClientException:
             self.logger.exception("Error during project deletion using keystone")
             raise AuthconnOperationException("Error during project deletion using Keystone")
         except ClientException:
             self.logger.exception("Error during project deletion using keystone")
             raise AuthconnOperationException("Error during project deletion using Keystone")
@@ -301,10 +420,11 @@ class AuthconnKeystone(Authconn):
         :raises AuthconnOperationException: if role assignment failed.
         """
         try:
         :raises AuthconnOperationException: if role assignment failed.
         """
         try:
-            result = self.keystone.roles.grant(role, user=user, project=project)
+            user_obj = list(filter(lambda x: x.name == user, self.keystone.users.list()))[0]
+            project_obj = list(filter(lambda x: x.name == project, self.keystone.projects.list()))[0]
+            role_obj = list(filter(lambda x: x.name == role, self.keystone.roles.list()))[0]
 
 
-            if not result:
-                raise ClientException()
+            self.keystone.roles.grant(role_obj, user=user_obj, project=project_obj)
         except ClientException:
             self.logger.exception("Error during user role assignment using keystone")
             raise AuthconnOperationException("Error during user role assignment using Keystone")
         except ClientException:
             self.logger.exception("Error during user role assignment using keystone")
             raise AuthconnOperationException("Error during user role assignment using Keystone")
@@ -319,10 +439,11 @@ class AuthconnKeystone(Authconn):
         :raises AuthconnOperationException: if role assignment revocation failed.
         """
         try:
         :raises AuthconnOperationException: if role assignment revocation failed.
         """
         try:
-            result = self.keystone.roles.revoke(role, user=user, project=project)
+            user_obj = list(filter(lambda x: x.name == user, self.keystone.users.list()))[0]
+            project_obj = list(filter(lambda x: x.name == project, self.keystone.projects.list()))[0]
+            role_obj = list(filter(lambda x: x.name == role, self.keystone.roles.list()))[0]
 
 
-            if not result:
-                raise ClientException()
+            self.keystone.roles.revoke(role_obj, user=user_obj, project=project_obj)
         except ClientException:
             self.logger.exception("Error during user role revocation using keystone")
             raise AuthconnOperationException("Error during user role revocation using Keystone")
         except ClientException:
             self.logger.exception("Error during user role revocation using keystone")
             raise AuthconnOperationException("Error during user role revocation using Keystone")