import logging import os from keystoneclient.v2_0 import client as kclient from keystoneclient.v3 import client as kv3client from keystoneclient.exceptions import AuthorizationFailure as KeystoneAuthorizationFailure from keystoneclient.exceptions import Unauthorized as KeystoneUnauthorized from keystoneclient.exceptions import NotFound as KeystoneNotFound from data.users.federated import FederatedUsers, UserInformation from util.itertoolrecipes import take logger = logging.getLogger(__name__) DEFAULT_TIMEOUT = 10 # seconds def get_keystone_users(auth_version, auth_url, admin_username, admin_password, admin_tenant, timeout=None, requires_email=True): if auth_version == 3: return KeystoneV3Users(auth_url, admin_username, admin_password, admin_tenant, timeout, requires_email) else: return KeystoneV2Users(auth_url, admin_username, admin_password, admin_tenant, timeout, requires_email) class KeystoneV2Users(FederatedUsers): """ Delegates authentication to OpenStack Keystone V2. """ def __init__(self, auth_url, admin_username, admin_password, admin_tenant, timeout=None, requires_email=True): super(KeystoneV2Users, self).__init__('keystone', requires_email) self.auth_url = auth_url self.admin_username = admin_username self.admin_password = admin_password self.admin_tenant = admin_tenant self.timeout = timeout or DEFAULT_TIMEOUT self.debug = os.environ.get('USERS_DEBUG') == '1' self.requires_email = requires_email def ping(self): try: keystone_client = kclient.Client(username=self.admin_username, password=self.admin_password, tenant_name=self.admin_tenant, auth_url=self.auth_url, timeout=self.timeout, debug=self.debug) keystone_client.user_id # Make sure we loaded a valid user. except KeystoneUnauthorized as kut: logger.exception('Keystone unauthorized admin') return (False, 'Keystone admin credentials are invalid: %s' % kut.message) except Exception: logger.exception('Keystone unauthorized admin') return (False, 'Keystone ping check failed: %s' % kut.message) return (True, None) def at_least_one_user_exists(self): logger.debug('Checking if any users exist in Keystone') try: keystone_client = kclient.Client(username=self.admin_username, password=self.admin_password, tenant_name=self.admin_tenant, auth_url=self.auth_url, timeout=self.timeout, debug=self.debug) user_list = keystone_client.users.list(tenant_id=self.admin_tenant, limit=1) if len(user_list) < 1: return (False, None) return (True, None) except Exception as e: # Catch exceptions to give the user our custom error message logger.exception('Unable to list users in Keystone') return (False, e.message) def verify_credentials(self, username_or_email, password): try: keystone_client = kclient.Client(username=username_or_email, password=password, auth_url=self.auth_url, timeout=self.timeout, debug=self.debug) user_id = keystone_client.user_id except KeystoneAuthorizationFailure as kaf: logger.exception('Keystone auth failure for user: %s', username_or_email) return (None, kaf.message or 'Invalid username or password') except KeystoneUnauthorized as kut: logger.exception('Keystone unauthorized for user: %s', username_or_email) return (None, kut.message or 'Invalid username or password') try: admin_client = kclient.Client(username=self.admin_username, password=self.admin_password, tenant_name=self.admin_tenant, auth_url=self.auth_url, timeout=self.timeout, debug=self.debug) user = admin_client.users.get(user_id) except KeystoneUnauthorized as kut: logger.exception('Keystone unauthorized admin') return (None, 'Keystone admin credentials are invalid: %s' % kut.message) if self.requires_email and not hasattr(user, 'email'): return (None, 'Missing email field for user %s' % user_id) email = user.email if hasattr(user, 'email') else None return (UserInformation(username=username_or_email, email=email, id=user_id), None) def query_users(self, query, limit=20): return (None, self.federated_service, 'Unsupported in Keystone V2') def get_user(self, username_or_email): return (None, 'Unsupported in Keystone V2') class KeystoneV3Users(FederatedUsers): """ Delegates authentication to OpenStack Keystone V3. """ def __init__(self, auth_url, admin_username, admin_password, admin_tenant, timeout=None, requires_email=True): super(KeystoneV3Users, self).__init__('keystone', requires_email) self.auth_url = auth_url self.admin_username = admin_username self.admin_password = admin_password self.admin_tenant = admin_tenant self.timeout = timeout or DEFAULT_TIMEOUT self.debug = os.environ.get('USERS_DEBUG') == '1' self.requires_email = requires_email def _get_admin_client(self): return kv3client.Client(username=self.admin_username, password=self.admin_password, tenant_name=self.admin_tenant, auth_url=self.auth_url, timeout=self.timeout, debug=self.debug) def ping(self): try: self._get_admin_client().user_id # Make sure we loaded a valid user except KeystoneUnauthorized as kut: logger.exception('Keystone unauthorized admin') return (False, 'Keystone admin credentials are invalid: %s' % kut.message) except Exception: logger.exception('Keystone unauthorized admin') return (False, 'Keystone ping check failed: %s' % kut.message) return (True, None) def at_least_one_user_exists(self): logger.debug('Checking if any users exist in admin tenant in Keystone') try: user_list = self._get_admin_client().users.list(self.admin_tenant, limit=1) if len(user_list) < 1: return (False, 'No users found in tenant: %s' % self.admin_tenant) return (True, None) except Exception as e: # Catch exceptions to give the user our custom error message logger.exception('Unable to list users in Keystone') return (False, e.message) def verify_credentials(self, username_or_email, password): try: keystone_client = kv3client.Client(username=username_or_email, password=password, auth_url=self.auth_url, timeout=self.timeout, debug=self.debug) user_id = keystone_client.user_id user = keystone_client.users.get(user_id) if self.requires_email and not hasattr(user, 'email'): return (None, 'Missing email field for user %s' % user_id) return (self._user_info(user), None) except KeystoneAuthorizationFailure as kaf: logger.exception('Keystone auth failure for user: %s', username_or_email) return (None, kaf.message or 'Invalid username or password') except KeystoneUnauthorized as kut: logger.exception('Keystone unauthorized for user: %s', username_or_email) return (None, kut.message or 'Invalid username or password') def get_user(self, username_or_email): users_found, _, err_msg = self.query_users(username_or_email) if err_msg is not None: return (None, err_msg) if len(users_found) != 1: return (None, 'Single user not found') user = users_found[0] if self.requires_email and not user.email: return (None, 'Missing email field for user %s' % user.id) return (user, None) def check_group_lookup_args(self, group_lookup_args): if not group_lookup_args.get('group_id'): return (False, 'Missing group_id') group_id = group_lookup_args['group_id'] return self._check_group(group_id) def _check_group(self, group_id): try: return (bool(self._get_admin_client().groups.get(group_id)), None) except KeystoneNotFound: return (False, 'Group not found') except KeystoneAuthorizationFailure as kaf: logger.exception('Keystone auth failure for admin user for group lookup %s', group_id) return (False, kaf.message or 'Invalid admin username or password') except KeystoneUnauthorized as kut: logger.exception('Keystone unauthorized for admin user for group lookup %s', group_id) return (False, kut.message or 'Invalid admin username or password') def iterate_group_members(self, group_lookup_args, page_size=None, disable_pagination=False): group_id = group_lookup_args['group_id'] (status, err) = self._check_group(group_id) if not status: return (None, err) try: user_info_iterator = self._get_admin_client().users.list(group=group_id) def iterator(): for user in user_info_iterator: yield (self._user_info(user), None) return (iterator(), None) except KeystoneAuthorizationFailure as kaf: logger.exception('Keystone auth failure for admin user for group lookup %s', group_id) return (False, kaf.message or 'Invalid admin username or password') except KeystoneUnauthorized as kut: logger.exception('Keystone unauthorized for admin user for group lookup %s', group_id) return (False, kut.message or 'Invalid admin username or password') @staticmethod def _user_info(user): email = user.email if hasattr(user, 'email') else None return UserInformation(user.name, email, user.id) def query_users(self, query, limit=20): if len(query) < 3: return ([], self.federated_service, None) try: found_users = list(take(limit, self._get_admin_client().users.list(name=query))) logger.debug('For Keystone query %s found users: %s', query, found_users) if not found_users: return ([], self.federated_service, None) return ([self._user_info(user) for user in found_users], self.federated_service, None) except KeystoneAuthorizationFailure as kaf: logger.exception('Keystone auth failure for admin user for query %s', query) return (None, self.federated_service, kaf.message or 'Invalid admin username or password') except KeystoneUnauthorized as kut: logger.exception('Keystone unauthorized for admin user for query %s', query) return (None, self.federated_service, kut.message or 'Invalid admin username or password')