Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
tvault_configurator / common / workloadmgr_keystoneclient.py
Size: Mime:
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2013 TrilioData, Inc.
# All Rights Reserved.


"""Keystone Client functionality for use by resources."""

import collections

from keystoneclient.auth.identity import v3 as kc_auth_v3
import keystoneclient.exceptions as kc_exception
from keystoneclient import session
from keystoneclient.v3 import client as kc_v3
from oslo_config import cfg
from oslo_log import log as logging
from keystoneauth1.identity.generic import password as passMod
from keystoneclient import client
from novaclient import client as novaclient

from workloadmgr.common import context
from workloadmgr import exception
from workloadmgr.common.i18n import _LE
from workloadmgr.common.i18n import _LW
#from workloadmgr.vault import vault
from workloadmgr.db.workloadmgrdb import WorkloadMgrDB

LOG = logging.getLogger('workloadmgr.common.keystoneclient')

#AccessKey = collections.namedtuple('AccessKey', ['id', 'access', 'secret'])

_default_keystone_backend = "workloadmgr.common.workloadmgr_keystoneclient.KeystoneClientV3"

keystone_opts = [
    cfg.StrOpt('keystone_backend',
               default=_default_keystone_backend,
               help="Fully qualified class name to use as a keystone backend.")
]

cfg.CONF.register_opts(keystone_opts)
CONF = cfg.CONF


class KeystoneClientBase(object):
    """Wrap keystone client so we can encapsulate logic used in resources.
    Note this is intended to be initialized from a resource on a per-session
    basis, so the session context is passed in on initialization
    Also note that an instance of this is created in each request context as
    part of a lazy-loaded cloud backend and it can be easily referenced in
    each resource as ``self.keystone()``, so there should not be any need to
    directly instantiate instances of this class inside resources themselves.
    """

    def __init__(self, context):
        # If a trust_id is specified in the context, we immediately
        # authenticate so we can populate the context with a trust token
        # otherwise, we delay client authentication until needed to avoid
        # unnecessary calls to keystone.
        #
        # Note that when you obtain a token using a trust, it cannot be
        # used to reauthenticate and get another token, so we have to
        # get a new trust-token even if context.auth_token is set.
        #
        # - context.auth_url is expected to contain a versioned keystone
        #   path, we will work with either a v2.0 or v3 path
        self.context = context
        self._client = None
        self._client_instance = None
        self._nova_client = None
        self._admin_auth = None
        self._domain_admin_auth = None
        self._domain_admin_client = None

        self.session = session.Session.construct(self._ssl_options())
        try:
            self.v3_endpoint = self.context.keystone_v3_endpoint
        except BaseException:
            self.v3_endpoint = None

        try:
            if self.context.trust_id:
                # Create a client with the specified trust_id, this
                # populates self.context.auth_token with a trust-scoped token
                self._client = self._v3_client_init()
        except BaseException:
            self._client = self._v3_client_init()

    @property
    def client(self):
        if not self._client:
            # Create connection to v3 API
            self._client = self._v3_client_init()
        return self._client

    @property
    def client_instance(self):
        if not self._client_instance:
            # Create connection to API
            self._client_instance = self._common_client_init()
        return self._client_instance

    @property
    def nova_client(self):
        if not self._nova_client:
            self._nova_client = self._common_client_init(nova_client=True)
        return self._nova_client

    def _common_client_init(self, nova_client=False):
        try:
            username = CONF.get('keystone_authtoken').username
        except BaseException:
            username = CONF.get('keystone_authtoken').admin_user
        try:
            password = CONF.get('keystone_authtoken').password
        except BaseException:
            password = CONF.get('keystone_authtoken').admin_password
        try:
            tenant_name = CONF.get('keystone_authtoken').admin_tenant_name
        except BaseException:
            project_id = context.project_id
            context.project_id = 'Configurator'
            tenant_name = WorkloadMgrDB().db.setting_get(
                context, 'service_tenant_name', get_hidden=True).value
            context.project_id = project_id
        auth_url = CONF.keystone_endpoint_url
        if CONF.keystone_auth_version == '3':
            username = CONF.get('nova_admin_username')
            password = CONF.get('nova_admin_password')
            if username == 'triliovault':
                domain_id = CONF.get('triliovault_user_domain_id')
            else:
                domain_id = CONF.get('domain_name')

            cloud_admin_project = CONF.get('neutron_admin_tenant_name', None)
            if nova_client is True:
                auth = passMod.Password(auth_url=auth_url,
                                        username=username,
                                        password=password,
                                        user_domain_id=domain_id,
                                        project_name=cloud_admin_project,
                                        project_domain_id=domain_id,
                                        )
            else:
                auth = passMod.Password(auth_url=auth_url,
                                        username=username,
                                        password=password,
                                        user_domain_id=domain_id,
                                        domain_id=domain_id,
                                        )
        else:
            auth = passMod.Password(auth_url=auth_url,
                                    username=username,
                                    password=password,
                                    project_name=tenant_name,
                                    )
        sess = session.Session(auth=auth, verify=False)
        if nova_client is True:
            return novaclient.Client("2", session=sess,
                                     endpoint_type=CONF.clients.endpoint_type)
        return client.Client(session=sess, auth_url=auth_url, insecure=True,
                             endpoint_type=CONF.clients.endpoint_type)

    def _v3_client_init(self):
        client = kc_v3.Client(session=self.session,
                              auth=self.context.auth_plugin)

        if hasattr(self.context.auth_plugin, 'get_access'):
            # NOTE(jamielennox): get_access returns the current token without
            # reauthenticating if it's present and valid.
            try:
                auth_ref = self.context.auth_plugin.get_access(self.session)
            except kc_exception.Unauthorized:
                LOG.error(_LE("Keystone client authentication failed"))
                raise exception.AuthorizationFailure()

            if self.context.trust_id:
                # Sanity check
                if not auth_ref.trust_scoped:
                    LOG.error(_LE("trust token re-scoping failed!"))
                    raise exception.AuthorizationFailure()
                # Sanity check that impersonation is effective
                if self.context.trustor_user_id != auth_ref.user_id:
                    LOG.error(_LE("Trust impersonation failed"))
                    raise exception.AuthorizationFailure()

        return client

    def _ssl_options(self):
        opts = {'cacert': cfg.CONF.keystone_authtoken.cafile,
                'insecure': cfg.CONF.keystone_authtoken.insecure,
                'cert': cfg.CONF.keystone_authtoken.certfile,
                'key': cfg.CONF.keystone_authtoken.keyfile}
        return opts

    def create_trust_context(self):
        """Create a trust using the trustor identity in the current context.
        The trust is created with the trustee as the heat service user.
        If the current context already contains a trust_id, we do nothing
        and return the current context.
        Returns a context containing the new trust_id.
        """
        if self.context.trust_id:
            return self.context

        # We need the service admin user ID (not name), as the trustor user
        # can't lookup the ID in keystoneclient unless they're admin
        # workaround this by getting the user_id from admin_client

        try:
            trustee_user_id = self.context.trusts_auth_plugin.get_user_id(
                self.session)
        except kc_exception.Unauthorized:
            LOG.error(_LE("Domain admin client authentication failed"))
            raise exception.AuthorizationFailure()

        trustor_user_id = self.context.trustor_user_id
        trustor_proj_id = self.context.tenant_id

        # inherit the roles of the trustor
        roles = self.context.roles

        try:
            trust = self.client.trusts.create(trustor_user=trustor_user_id,
                                              trustee_user=trustee_user_id,
                                              project=trustor_proj_id,
                                              impersonation=True,
                                              role_names=roles)
        except kc_exception.NotFound as ex:
            LOG.exception(ex)
            LOG.debug("Failed to find roles %s for user %s"
                      % (roles, trustor_user_id))
            raise exception.MissingCredentialError(
                message="Invalid roles %s" % roles)

        trust_context = context.RequestContext.from_dict(
            self.context.to_dict())
        trust_context.trust_id = trust.id
        trust_context.trustor_user_id = trustor_user_id
        return trust_context

    def delete_trust(self, trust_id):
        """Delete the specified trust."""
        try:
            self.client.trusts.delete(trust_id)
        except kc_exception.NotFound:
            pass

    def _get_username(self, username):
        if(len(username) > 64):
            LOG.warning(_LW("Truncating the username %s to the last 64 "
                            "characters."), username)
        # get the last 64 characters of the username
        return username[-64:]

    def url_for(self, **kwargs):
        default_region_name = (self.context.region_name or
                               cfg.CONF.region_name_for_services)
        kwargs.setdefault('region_name', default_region_name)
        return self.context.auth_plugin.get_endpoint(self.session, **kwargs)

    @property
    def auth_token(self):
        return self.context.auth_plugin.get_token(self.session)

    @property
    def auth_ref(self):
        return self.context.auth_plugin.get_access(self.session)


class KeystoneClientV3(KeystoneClientBase):

    def __init__(self, context):
        super(KeystoneClientV3, self).__init__(context)

    def user_exist_in_tenant(self, project_id, user_id):
        try:
            output = None
            user = self.client.users.get(user_id)
            project = self.client.projects.get(project_id)
            output = self.client.role_assignments.list(
                user=user, project=project, effective=True)
            if output:
                return True
            else:
                user_groups = self.client.groups.list()
                for grp in user_groups:
                    user_grp_check = False
                    try:
                        user_grp_check = self.client.users.check_in_group(user, grp)
                    except Exception as ex:
                        LOG.exception("User {} is not a member of a group {}. Checking in next group".format(
                            user_id, grp.id))
                        pass
                    if user_grp_check:
                        LOG.info("User {} is present in a group {}".format(user_id, grp.id))
                        output = self.client.role_assignments.list(project=project, group=grp)
                    if output:
                        return True
            return False
        except Exception as ex:
            LOG.exception(ex)
            raise ex

    def check_user_role(self, project_id, user_id, context=None):
        try:
            roles = []
            if context:
                roles = context.roles
            else:
                # Fetch role of a user in a project
                roles = [role.name for role in self.client.roles.list(
                    user=user_id, project=project_id)]

                # Fetch role of user group in a project
                user_groups = self.client.groups.list()
                grp_roles = []
                user = self.client.users.get(user_id)
                for grp in user_groups:
                    user_grp_check = False
                    try:
                        user_grp_check = self.client.users.check_in_group(user, grp)
                    except Exception as ex:
                        LOG.exception("User {} is not a member of a group {}. Checking in next group".format(
                            user_id, grp.id))
                        pass
                    if user_grp_check:
                        LOG.info("User {} is present in a group {}".format(user_id, grp.id))
                        grp_roles.extend(
                            self.client.roles.list(project=project_id, group=grp)
                        )
                """
                  If the roles is not assigned to the project on user level or group level.
                  Check if the roles assigned to the project by any user/group through inheritance on project/Domain Level.
                """
                if not grp_roles:
                    grp_roles = self.client.role_assignments.list(user=self.client.users.get(user_id), project=self.client.projects.get(project_id), effective=True)
                try:
                    roles.extend([role.name for role in grp_roles])
                except Exception as ex:
                    LOG.info('Roles has been assigned through inheritance on a project via group:- {0}'.format(ex))
                    roles.extend([str(self.client.roles.get(role=str(role.role.get('id'))).name) for role in grp_roles])

            if not roles:
                return False
            trustee_role = CONF.trustee_role
            for role in roles:
                if (trustee_role == role) or (role.lower() == 'admin') or (role.lower() == 'administrator'):
                    return True
            return False
        except Exception as ex:
            LOG.exception(ex)
            raise ex

    def get_project_list_for_import(self, context):
        try:
            res = []
            if context.user_id == CONF.get('cloud_admin_user_id'):
                res = self.client.projects.list()
            else:
                project_list = []
                user = self.client.users.get(context.user_id)
                # check if user is domain admin and then fetch domain projects
                user_domain = self.client.domains.get(user.domain_id)
                user_role = self.client.roles.list(user=user, domain=user_domain)
                role_list = [role.name for role in user_role]
                is_domain_admin = False
                for role in role_list:
                    if (role.lower() == 'admin') or (role.lower() == 'administrator'):
                        is_domain_admin = True
                        break
                if is_domain_admin:
                    all_projects = self.client.projects.list()
                    res = [prj for prj in all_projects if prj.domain_id == user.domain_id]
                else:
                    # Or check if user is belongs to a group and then fetch projects
                    project_list = self.client.role_assignments.list(user=user)
                    user_groups = self.client.groups.list()
                    for grp in user_groups:
                        projects = []
                        user_grp_check = False
                        try:
                            user_grp_check = self.client.users.check_in_group(user, grp)
                        except Exception as ex:
                            LOG.exception("User {} is not a member of a group {}. Checking in next group".format(
                                user.id, grp.id))
                            pass
                        if user_grp_check:
                            LOG.info("User {} is present in a group {}".format(user.id, grp.id))
                            projects = self.client.role_assignments.list(group=grp)
                        if projects:
                            project_list.extend(projects)
                    if project_list:
                        for proj in project_list:
                            if hasattr(proj, 'scope'):
                                project_id = proj.scope.get('project', {}).get('id')
                                if project_id:
                                    project = self.client.projects.get(project_id)
                                    res.append(project)
            return res
        except Exception as ex:
            LOG.exception(ex)
            raise ex


class KeystoneClientV2(KeystoneClientBase):

    def __init__(self, context):
        super(KeystoneClientV2, self).__init__(context)

    def user_exist_in_tenant(self, project_id, user_id):
        try:
            users = self.client_instance.users.list(project_id)
            user_ids = [user.id for user in users]
            if user_id in user_ids:
                return True
            else:
                return False
        except Exception as ex:
            LOG.exception(ex)

    def check_user_role(self, project_id, user_id):
        try:
            roles = self.client_instance.tenants.role_manager.roles_for_user(
                user_id, project_id)
            trustee_role = CONF.trustee_role
            for role in roles:
                if (trustee_role == role.name) or (role.name == 'admin'):
                    return True
            return False
        except Exception as ex:
            LOG.exception(ex)

    def get_project_list_for_import(self, context):
        try:
            projects = self.client_instance.tenants.list()
            return projects
        except Exception as ex:
            LOG.exception(ex)


class KeystoneClient(object):
    """Keystone Auth Client.
    """

    _instance = None

    def __new__(class_, *args, **kwargs):
        if not isinstance(class_._instance, class_):
            class_._instance = object.__new__(class_)
        return class_._instance

    def __init__(self, context):
        keystone_version = CONF.keystone_auth_version
        if keystone_version == '3':
            self.client = KeystoneClientV3(context)
        else:
            self.client = KeystoneClientV2(context)

    def get_user_to_get_email_address(self, context):
        user = self.client.client.users.get(context.user_id)
        if not hasattr(user, 'email'):
            user.email = None
        return user

    def get_user_list(self):
        users = self.client.client.users.list()
        return users

    def create_flavor(self, name, ram, vcpus, disk, ephemeral=0, swap=0):
        nova = self.client.nova_client
        return nova.flavors.create(
            name, ram, vcpus, disk, ephemeral=ephemeral, swap=swap)


def list_opts():
    yield None, keystone_opts