Add support for linking to external users in entity search

This commit is contained in:
Joseph Schorr 2016-10-27 15:31:32 -04:00
parent 845d1795a3
commit d145222812
8 changed files with 156 additions and 15 deletions

View file

@ -499,8 +499,7 @@ def get_matching_user_namespaces(namespace_prefix, username, limit=10):
return _basequery.filter_to_repos_for_user(base_query, username).limit(limit)
def get_matching_users(username_prefix, robot_namespace=None,
organization=None):
def get_matching_users(username_prefix, robot_namespace=None, organization=None, limit=20):
user_search = _basequery.prefix_search(User.username, username_prefix)
direct_user_query = (user_search & (User.organization == False) & (User.robot == False))
@ -516,23 +515,24 @@ def get_matching_users(username_prefix, robot_namespace=None,
if organization:
query = (query
.select(User.username, User.email, User.robot, fn.Sum(Team.id))
.select(User.id, User.username, User.email, User.robot, fn.Sum(Team.id))
.join(TeamMember, JOIN_LEFT_OUTER)
.join(Team, JOIN_LEFT_OUTER, on=((Team.id == TeamMember.team) &
(Team.organization == organization))))
class MatchingUserResult(object):
def __init__(self, *args):
self.username = args[0]
self.email = args[1]
self.robot = args[2]
self.id = args[0]
self.username = args[1]
self.email = args[2]
self.robot = args[3]
if organization:
self.is_org_member = (args[3] != None)
else:
self.is_org_member = None
return (MatchingUserResult(*args) for args in query.tuples().limit(10))
return (MatchingUserResult(*args) for args in query.tuples().limit(limit))
def verify_user(username_or_email, password):
@ -749,3 +749,16 @@ def get_region_locations(user):
""" Returns the locations defined as preferred storage for the given user. """
query = UserRegion.select().join(ImageStorageLocation).where(UserRegion.user == user)
return set([region.location.name for region in query])
def get_federated_logins(user_ids, service_name):
""" Returns all federated logins for the given user ids under the given external service. """
if not user_ids:
return []
return (FederatedLogin
.select()
.join(User)
.switch(FederatedLogin)
.join(LoginService)
.where(FederatedLogin.user << user_ids,
LoginService.name == service_name))

View file

@ -139,6 +139,31 @@ class UserAuthentication(object):
return data.get('password', encrypted)
@property
def federated_service(self):
""" Returns the name of the federated service for the auth system. If none, should return None.
"""
return self.state.federated_service
def query_users(self, query, limit=20):
""" Performs a lookup against the user system for the specified query. The returned tuple
will be of the form (results, federated_login_id, err_msg). If the method is unsupported,
the results portion of the tuple will be None instead of empty list.
Note that this method can and will return results for users not yet found within the
database; it is the responsibility of the caller to call link_user if they need the
database row for the user system record.
Results will be in the form of objects's with username and email fields.
"""
return self.state.query_users(query, limit)
def link_user(self, username_or_email):
""" Returns a tuple containing the database user record linked to the given username/email
and any error that occurred when trying to link the user.
"""
return self.state.link_user(username_or_email)
def confirm_existing_user(self, username, password):
""" Verifies that the given password matches to the given DB username. Unlike
verify_credentials, this call first translates the DB user via the FederatedLogin table

View file

@ -1,6 +1,10 @@
from data import model
class DatabaseUsers(object):
@property
def federated_service(self):
return None
def verify_credentials(self, username_or_email, password):
""" Simply delegate to the model implementation. """
result = model.user.verify_user(username_or_email, password)
@ -16,3 +20,11 @@ class DatabaseUsers(object):
def confirm_existing_user(self, username, password):
return self.verify_credentials(username, password)
def link_user(self, username_or_email):
""" Never used since all users being added are already, by definition, in the database. """
return (None, 'Unsupported for this authentication system')
def query_users(self, query, limit):
""" No need to implement, as we already query for users directly in the database. """
return (None, '', '')

View file

@ -7,7 +7,7 @@ from util.validation import generate_valid_usernames
logger = logging.getLogger(__name__)
VerifiedCredentials = namedtuple('VerifiedCredentials', ['username', 'email'])
UserInformation = namedtuple('UserInformation', ['username', 'email', 'id'])
class FederatedUsers(object):
""" Base class for all federated users systems. """
@ -15,11 +15,26 @@ class FederatedUsers(object):
def __init__(self, federated_service):
self._federated_service = federated_service
@property
def federated_service(self):
return self._federated_service
def get_user(self, username_or_email):
""" Retrieves the user with the given username or email, returning a tuple containing
a UserInformation (if success) and the error message (on failure).
"""
raise NotImplementedError
def verify_credentials(self, username_or_email, password):
""" Verifies the given credentials against the backing federated service, returning
a tuple containing a VerifiedCredentials (if success) and the error message (if failed). """
a tuple containing a UserInformation (on success) and the error message (on failure).
"""
raise NotImplementedError
def query_users(self, query, limit=20):
""" If implemented, get_user must be implemented as well. """
return (None, 'Not supported')
def _get_federated_user(self, username, email):
db_user = model.user.verify_federated_login(self._federated_service, username)
if not db_user:
@ -34,7 +49,8 @@ class FederatedUsers(object):
return (None, 'Unable to pick a username. Please report this to your administrator.')
db_user = model.user.create_federated_user(valid_username, email, self._federated_service,
username, set_password_notification=False)
username,
set_password_notification=False)
else:
# Update the db attributes from the federated service.
db_user.email = email
@ -42,6 +58,13 @@ class FederatedUsers(object):
return (db_user, None)
def link_user(self, username_or_email):
(credentials, err_msg) = self.get_user(username_or_email)
if credentials is None:
return (None, err_msg)
return self._get_federated_user(credentials.username, credentials.email)
def verify_and_link_user(self, username_or_email, password):
""" Verifies the given credentials and, if valid, creates/links a database user to the
associated federated service.