Add group iteration and syncing support to Keystone auth
This commit is contained in:
parent
47278cc559
commit
d7825c6720
6 changed files with 148 additions and 15 deletions
|
@ -422,7 +422,8 @@ def list_team_robots(team):
|
|||
def set_team_syncing(team, login_service_name, config):
|
||||
""" Sets the given team to sync to the given service using the given config. """
|
||||
login_service = LoginService.get(name=login_service_name)
|
||||
TeamSync.create(team=team, transaction_id='', service=login_service, config=json.dumps(config))
|
||||
return TeamSync.create(team=team, transaction_id='', service=login_service,
|
||||
config=json.dumps(config))
|
||||
|
||||
|
||||
def remove_team_syncing(orgname, teamname):
|
||||
|
|
|
@ -5,6 +5,7 @@ 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
|
||||
|
||||
|
@ -83,6 +84,11 @@ class KeystoneV3Users(FederatedUsers):
|
|||
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 verify_credentials(self, username_or_email, password):
|
||||
try:
|
||||
keystone_client = kv3client.Client(username=username_or_email, password=password,
|
||||
|
@ -116,6 +122,46 @@ class KeystoneV3Users(FederatedUsers):
|
|||
|
||||
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:
|
||||
group_member_iterator = self._get_admin_client().users.list(group=group_id)
|
||||
def iterator():
|
||||
for user in group_member_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
|
||||
|
@ -126,10 +172,7 @@ class KeystoneV3Users(FederatedUsers):
|
|||
return ([], self.federated_service, None)
|
||||
|
||||
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)
|
||||
found_users = list(take(limit, keystone_client.users.list(name=query)))
|
||||
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)
|
||||
|
|
|
@ -7,6 +7,7 @@ from data.users.federated import FederatedUsers, UserInformation
|
|||
from data.users.teamsync import sync_team, sync_teams_to_groups
|
||||
from test.fixtures import app, appconfig, database_uri, init_db_path, sqlitedb_file
|
||||
from test.test_ldap import mock_ldap
|
||||
from test.test_keystone_auth import fake_keystone
|
||||
from util.names import parse_robot_username
|
||||
|
||||
_FAKE_AUTH = 'fake'
|
||||
|
@ -213,20 +214,28 @@ def test_sync_teams_to_groups(app):
|
|||
assert third_sync_info.last_updated == updated_sync_info.last_updated
|
||||
assert third_sync_info.transaction_id == updated_sync_info.transaction_id
|
||||
|
||||
# Set the stale threshold to -1 seconds, and ensure the team is resynced.
|
||||
sync_teams_to_groups(fake_auth, timedelta(seconds=-1))
|
||||
# Set the stale threshold to -10 seconds, and ensure the team is resynced.
|
||||
sync_teams_to_groups(fake_auth, timedelta(seconds=-120))
|
||||
|
||||
fourth_sync_info = model.team.get_team_sync_information('buynlarge', 'synced')
|
||||
assert fourth_sync_info.transaction_id != updated_sync_info.transaction_id
|
||||
|
||||
|
||||
@pytest.mark.parametrize('auth_system_builder', [
|
||||
mock_ldap,
|
||||
@pytest.mark.parametrize('auth_system_builder,config', [
|
||||
(mock_ldap, {'group_dn': 'cn=AwesomeFolk'}),
|
||||
(fake_keystone, {'group_id': 'somegroupid'}),
|
||||
])
|
||||
def test_teamsync_end_to_end(auth_system_builder, app):
|
||||
# Assert the team has not yet been updated.
|
||||
sync_team_info = model.team.get_team_sync_information('buynlarge', 'synced')
|
||||
assert sync_team_info.last_updated is None
|
||||
|
||||
def test_teamsync_end_to_end(auth_system_builder, config, app):
|
||||
with auth_system_builder() as auth:
|
||||
# Create an new team to sync.
|
||||
org = model.organization.get_organization('buynlarge')
|
||||
new_synced_team = model.team.create_team('synced2', org, 'member', 'Some synced team.')
|
||||
sync_team_info = model.team.set_team_syncing(new_synced_team, auth.federated_service, config)
|
||||
|
||||
# Sync the team.
|
||||
assert sync_team(auth, sync_team_info)
|
||||
|
||||
# Ensure we now have members.
|
||||
msg = 'Auth system: %s' % auth.federated_service
|
||||
sync_team_info = model.team.get_team_sync_information('buynlarge', 'synced2')
|
||||
assert len(list(model.team.list_team_users(sync_team_info.team))) > 0, msg
|
||||
|
|
|
@ -709,8 +709,9 @@ def populate_database(minimal=False, with_storage=False):
|
|||
|
||||
model.team.add_user_to_team(new_user_4, sell_owners)
|
||||
|
||||
sync_config = {'group_dn': 'cn=Test-Group,ou=Users', 'group_id': 'somegroupid'}
|
||||
synced_team = model.team.create_team('synced', org, 'member', 'Some synced team.')
|
||||
model.team.set_team_syncing(synced_team, 'ldap', {'group_dn': 'cn=Test-Group,ou=Users'})
|
||||
model.team.set_team_syncing(synced_team, 'ldap', sync_config)
|
||||
|
||||
another_synced_team = model.team.create_team('synced', thirdorg, 'member', 'Some synced team.')
|
||||
model.team.set_team_syncing(another_synced_team, 'ldap', {'group_dn': 'cn=Test-Group,ou=Users'})
|
||||
|
|
|
@ -39,6 +39,9 @@
|
|||
<div ng-if="syncInfo.service == 'ldap'">
|
||||
<code>{{ syncInfo.config.group_dn }}</code>
|
||||
</div>
|
||||
<div ng-if="syncInfo.service == 'keystone'">
|
||||
<code>{{ syncInfo.config.group_id }}</code>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -174,6 +177,10 @@
|
|||
Enter the distinguished name of the group, relative to <code>{{ enableSyncingInfo.service_info.base_dn }}</code>:
|
||||
<input type="text" class="form-control" placeholder="Group DN" ng-model="enableSyncingInfo.config.group_dn" required>
|
||||
</div>
|
||||
<div ng-switch-when="keystone">
|
||||
Enter the Keystone group ID:
|
||||
<input type="text" class="form-control" placeholder="Group ID" ng-model="enableSyncingInfo.config.group_id" required>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -47,6 +47,23 @@ def _create_app(requires_email=True):
|
|||
{'username': 'some.neat.user', 'name': 'Neat User', 'password': 'foobar'},
|
||||
]
|
||||
|
||||
groups = [
|
||||
{'id': 'somegroupid', 'name': 'somegroup', 'description': 'Hi there!',
|
||||
'members': ['adminuser', 'cool.user']},
|
||||
]
|
||||
|
||||
def _get_user(username):
|
||||
for user in users:
|
||||
if user['username'] == username:
|
||||
user_data = {}
|
||||
user_data['id'] = username
|
||||
user_data['name'] = username
|
||||
if requires_email:
|
||||
user_data['email'] = username + '@example.com'
|
||||
return user_data
|
||||
|
||||
return None
|
||||
|
||||
ks_app = Flask('testks')
|
||||
ks_app.config['SERVER_HOSTNAME'] = 'localhost:%s' % _PORT_NUMBER
|
||||
if os.environ.get('DEBUG') == 'true':
|
||||
|
@ -66,6 +83,35 @@ def _create_app(requires_email=True):
|
|||
|
||||
abort(404)
|
||||
|
||||
@ks_app.route('/v3/identity/groups/<groupid>/users', methods=['GET'])
|
||||
def getv3groupmembers(groupid):
|
||||
for group in groups:
|
||||
if group['id'] == groupid:
|
||||
group_data = {
|
||||
"links": {},
|
||||
"users": [_get_user(username) for username in group['members']],
|
||||
}
|
||||
|
||||
return json.dumps(group_data)
|
||||
|
||||
abort(404)
|
||||
|
||||
@ks_app.route('/v3/identity/groups/<groupid>', methods=['GET'])
|
||||
def getv3group(groupid):
|
||||
for group in groups:
|
||||
if group['id'] == groupid:
|
||||
group_data = {
|
||||
"description": group['description'],
|
||||
"domain_id": "default",
|
||||
"id": groupid,
|
||||
"links": {},
|
||||
"name": group['name'],
|
||||
}
|
||||
|
||||
return json.dumps({'group': group_data})
|
||||
|
||||
abort(404)
|
||||
|
||||
@ks_app.route('/v3/identity/users/<userid>', methods=['GET'])
|
||||
def getv3user(userid):
|
||||
for user in users:
|
||||
|
@ -321,6 +367,32 @@ class KeystoneV3AuthTests(KeystoneAuthTestsMixin, unittest.TestCase):
|
|||
self.assertIsNotNone(result)
|
||||
self.assertEquals('cool_user', result.username)
|
||||
|
||||
def test_check_group_lookup_args(self):
|
||||
with self.fake_keystone() as keystone:
|
||||
(status, err) = keystone.check_group_lookup_args({})
|
||||
self.assertFalse(status)
|
||||
self.assertEquals('Missing group_id', err)
|
||||
|
||||
(status, err) = keystone.check_group_lookup_args({'group_id': 'unknownid'})
|
||||
self.assertFalse(status)
|
||||
self.assertEquals('Group not found', err)
|
||||
|
||||
(status, err) = keystone.check_group_lookup_args({'group_id': 'somegroupid'})
|
||||
self.assertTrue(status)
|
||||
self.assertIsNone(err)
|
||||
|
||||
def test_iterate_group_members(self):
|
||||
with self.fake_keystone() as keystone:
|
||||
(itt, err) = keystone.iterate_group_members({'group_id': 'somegroupid'})
|
||||
self.assertIsNone(err)
|
||||
|
||||
results = list(itt)
|
||||
results.sort()
|
||||
|
||||
self.assertEquals(2, len(results))
|
||||
self.assertEquals('adminuser', results[0][0].id)
|
||||
self.assertEquals('cool.user', results[1][0].id)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
Reference in a new issue