2015-07-20 15:39:59 +00:00
|
|
|
import logging
|
2016-09-22 22:25:02 +00:00
|
|
|
import os
|
2015-07-20 15:39:59 +00:00
|
|
|
|
|
|
|
from keystoneclient.v2_0 import client as kclient
|
2016-10-27 19:35:52 +00:00
|
|
|
from keystoneclient.v3 import client as kv3client
|
2015-07-20 15:39:59 +00:00
|
|
|
from keystoneclient.exceptions import AuthorizationFailure as KeystoneAuthorizationFailure
|
|
|
|
from keystoneclient.exceptions import Unauthorized as KeystoneUnauthorized
|
2016-10-27 19:35:52 +00:00
|
|
|
from data.users.federated import FederatedUsers, UserInformation
|
Optimize repository search by changing our lookup strategy
Previous to this change, repositories were looked up unfiltered in six different queries, and then filtered using the permissions model, which issued a query per repository found, making search incredibly slow. Instead, we now lookup a chunk of repositories unfiltered and then filter them via a single query to the database. By layering the filtering on top of the lookup, each as queries, we can minimize the number of queries necessary, without (at the same time) using a super expensive join.
Other changes:
- Remove the 5 page pre-lookup on V1 search and simply return that there is one more page available, until there isn't. While technically not correct, it is much more efficient, and no one should be using pagination with V1 search anyway.
- Remove the lookup for repos without entries in the RAC table. Instead, we now add a new RAC entry when the repository is created for *the day before*, with count 0, so that it is immediately searchable
- Remove lookup of results with a matching namespace; these aren't very relevant anyway, and it overly complicates sorting
2017-02-27 22:56:44 +00:00
|
|
|
from util.itertoolrecipes import take
|
2015-07-20 15:39:59 +00:00
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
2016-09-22 22:25:02 +00:00
|
|
|
DEFAULT_TIMEOUT = 10 # seconds
|
|
|
|
|
2016-10-27 19:35:52 +00:00
|
|
|
def get_keystone_users(auth_version, auth_url, admin_username, admin_password, admin_tenant,
|
2016-09-08 16:24:47 +00:00
|
|
|
timeout=None, requires_email=True):
|
2016-10-27 19:35:52 +00:00
|
|
|
if auth_version == 3:
|
2016-09-08 16:24:47 +00:00
|
|
|
return KeystoneV3Users(auth_url, admin_username, admin_password, admin_tenant, timeout,
|
|
|
|
requires_email)
|
2016-10-27 19:35:52 +00:00
|
|
|
else:
|
2016-09-08 16:24:47 +00:00
|
|
|
return KeystoneV2Users(auth_url, admin_username, admin_password, admin_tenant, timeout,
|
|
|
|
requires_email)
|
2016-10-27 19:35:52 +00:00
|
|
|
|
|
|
|
|
|
|
|
class KeystoneV2Users(FederatedUsers):
|
|
|
|
""" Delegates authentication to OpenStack Keystone V2. """
|
2016-09-08 16:24:47 +00:00
|
|
|
def __init__(self, auth_url, admin_username, admin_password, admin_tenant, timeout=None,
|
|
|
|
requires_email=True):
|
|
|
|
super(KeystoneV2Users, self).__init__('keystone', requires_email)
|
2015-07-20 15:39:59 +00:00
|
|
|
self.auth_url = auth_url
|
|
|
|
self.admin_username = admin_username
|
|
|
|
self.admin_password = admin_password
|
|
|
|
self.admin_tenant = admin_tenant
|
2016-09-22 22:25:02 +00:00
|
|
|
self.timeout = timeout or DEFAULT_TIMEOUT
|
|
|
|
self.debug = os.environ.get('USERS_DEBUG') == '1'
|
2016-09-08 16:24:47 +00:00
|
|
|
self.requires_email = requires_email
|
2015-07-20 15:39:59 +00:00
|
|
|
|
|
|
|
def verify_credentials(self, username_or_email, password):
|
|
|
|
try:
|
|
|
|
keystone_client = kclient.Client(username=username_or_email, password=password,
|
2016-09-22 22:25:02 +00:00
|
|
|
auth_url=self.auth_url, timeout=self.timeout,
|
|
|
|
debug=self.debug)
|
2015-07-20 15:39:59 +00:00
|
|
|
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,
|
2016-09-22 22:25:02 +00:00
|
|
|
tenant_name=self.admin_tenant, auth_url=self.auth_url,
|
|
|
|
timeout=self.timeout, debug=self.debug)
|
2015-07-20 15:39:59 +00:00
|
|
|
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)
|
|
|
|
|
2016-09-08 16:24:47 +00:00
|
|
|
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)
|
2016-10-27 19:35:52 +00:00
|
|
|
|
|
|
|
def query_users(self, query, limit=20):
|
2016-12-05 22:19:38 +00:00
|
|
|
return (None, self.federated_service, 'Unsupported in Keystone V2')
|
2016-10-27 19:35:52 +00:00
|
|
|
|
|
|
|
def get_user(self, username_or_email):
|
|
|
|
return (None, 'Unsupported in Keystone V2')
|
|
|
|
|
|
|
|
|
|
|
|
class KeystoneV3Users(FederatedUsers):
|
|
|
|
""" Delegates authentication to OpenStack Keystone V3. """
|
2016-09-08 16:24:47 +00:00
|
|
|
def __init__(self, auth_url, admin_username, admin_password, admin_tenant, timeout=None,
|
|
|
|
requires_email=True):
|
|
|
|
super(KeystoneV3Users, self).__init__('keystone', requires_email)
|
2016-10-27 19:35:52 +00:00
|
|
|
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'
|
2016-09-08 16:24:47 +00:00
|
|
|
self.requires_email = requires_email
|
2016-10-27 19:35:52 +00:00
|
|
|
|
|
|
|
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)
|
2016-09-08 16:24:47 +00:00
|
|
|
|
|
|
|
if self.requires_email and not hasattr(user, 'email'):
|
|
|
|
return (None, 'Missing email field for user %s' % user_id)
|
|
|
|
|
2016-10-27 19:35:52 +00:00
|
|
|
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):
|
2016-12-05 22:19:38 +00:00
|
|
|
users_found, _, err_msg = self.query_users(username_or_email)
|
2016-10-27 19:35:52 +00:00
|
|
|
if err_msg is not None:
|
|
|
|
return (None, err_msg)
|
|
|
|
|
|
|
|
if len(users_found) != 1:
|
|
|
|
return (None, 'Single user not found')
|
|
|
|
|
2016-09-08 16:24:47 +00:00
|
|
|
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)
|
2016-10-27 19:35:52 +00:00
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _user_info(user):
|
2016-09-08 16:24:47 +00:00
|
|
|
email = user.email if hasattr(user, 'email') else None
|
2016-10-27 19:35:52 +00:00
|
|
|
return UserInformation(user.name, email, user.id)
|
|
|
|
|
|
|
|
def query_users(self, query, limit=20):
|
|
|
|
if len(query) < 3:
|
2016-12-05 22:19:38 +00:00
|
|
|
return ([], self.federated_service, None)
|
2016-10-27 19:35:52 +00:00
|
|
|
|
|
|
|
try:
|
|
|
|
keystone_client = 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)
|
Optimize repository search by changing our lookup strategy
Previous to this change, repositories were looked up unfiltered in six different queries, and then filtered using the permissions model, which issued a query per repository found, making search incredibly slow. Instead, we now lookup a chunk of repositories unfiltered and then filter them via a single query to the database. By layering the filtering on top of the lookup, each as queries, we can minimize the number of queries necessary, without (at the same time) using a super expensive join.
Other changes:
- Remove the 5 page pre-lookup on V1 search and simply return that there is one more page available, until there isn't. While technically not correct, it is much more efficient, and no one should be using pagination with V1 search anyway.
- Remove the lookup for repos without entries in the RAC table. Instead, we now add a new RAC entry when the repository is created for *the day before*, with count 0, so that it is immediately searchable
- Remove lookup of results with a matching namespace; these aren't very relevant anyway, and it overly complicates sorting
2017-02-27 22:56:44 +00:00
|
|
|
found_users = list(take(limit, keystone_client.users.list(name=query)))
|
2016-10-27 19:35:52 +00:00
|
|
|
logger.debug('For Keystone query %s found users: %s', query, found_users)
|
|
|
|
if not found_users:
|
2016-12-05 22:19:38 +00:00
|
|
|
return ([], self.federated_service, None)
|
2016-10-27 19:35:52 +00:00
|
|
|
|
2016-12-05 22:19:38 +00:00
|
|
|
return ([self._user_info(user) for user in found_users], self.federated_service, None)
|
2016-10-27 19:35:52 +00:00
|
|
|
except KeystoneAuthorizationFailure as kaf:
|
|
|
|
logger.exception('Keystone auth failure for admin user for query %s', query)
|
2016-12-05 22:19:38 +00:00
|
|
|
return (None, self.federated_service, kaf.message or 'Invalid admin username or password')
|
2016-10-27 19:35:52 +00:00
|
|
|
except KeystoneUnauthorized as kut:
|
|
|
|
logger.exception('Keystone unauthorized for admin user for query %s', query)
|
2016-12-05 22:19:38 +00:00
|
|
|
return (None, self.federated_service, kut.message or 'Invalid admin username or password')
|
2016-10-27 19:35:52 +00:00
|
|
|
|