Merge "Add federated support for creating a user"

This commit is contained in:
Zuul 2020-04-10 11:02:36 +00:00 committed by Gerrit Code Review
commit ea1b2b0a65
6 changed files with 196 additions and 1 deletions

View File

@ -924,6 +924,57 @@ class Manager(manager.Manager):
# backward compatible
pass
def _validate_federated_objects(self, fed_obj_list):
# Validate that the ipd and protocols exist
for fed_obj in fed_obj_list:
try:
self.federation_api.get_idp(fed_obj['idp_id'])
except exception.IdentityProviderNotFound:
msg = (_("Could not find Identity Provider: %s")
% fed_obj['idp_id'])
raise exception.ValidationError(msg)
for protocol in fed_obj['protocols']:
try:
self.federation_api.get_protocol(fed_obj['idp_id'],
protocol['protocol_id'])
except exception.FederatedProtocolNotFound:
msg = (_("Could not find federated protocol "
"%(protocol)s for Identity Provider: %(idp)s.")
% {'protocol': protocol['protocol_id'],
'idp': fed_obj['idp_id']})
raise exception.ValidationError(msg)
def _create_federated_objects(self, user_ref, fed_obj_list):
for fed_obj in fed_obj_list:
for protocols in fed_obj['protocols']:
federated_dict = {
'user_id': user_ref['id'],
'idp_id': fed_obj['idp_id'],
'protocol_id': protocols['protocol_id'],
'unique_id': protocols['unique_id'],
'display_name': user_ref['name']
}
self.shadow_users_api.create_federated_object(
federated_dict)
def _create_user_with_federated_objects(self, user, driver):
# If the user did not pass a federated object along inside the user
# object then we simply create the user as normal.
if not user.get('federated'):
if 'federated' in user:
del user['federated']
user = driver.create_user(user['id'], user)
return user
# Otherwise, validate the federated object and create the user.
else:
user_ref = user.copy()
del user['federated']
self._validate_federated_objects(user_ref['federated'])
user = driver.create_user(user['id'], user)
self._create_federated_objects(user_ref, user_ref['federated'])
user['federated'] = user_ref['federated']
return user
@domains_configured
@exception_translated('user')
def create_user(self, user_ref, initiator=None):
@ -946,7 +997,7 @@ class Manager(manager.Manager):
# the underlying driver so that it could conform to rules set down by
# that particular driver type.
user['id'] = uuid.uuid4().hex
ref = driver.create_user(user['id'], user)
ref = self._create_user_with_federated_objects(user, driver)
notifications.Audit.created(self._USER, user['id'], initiator)
return self._set_domain_id_and_mapping(
ref, domain_id, driver, mapping.EntityType.USER)

View File

@ -33,6 +33,30 @@ _user_properties = {
'description': validation.nullable(parameter_types.description),
'domain_id': parameter_types.id_string,
'enabled': parameter_types.boolean,
'federated': {
'type': 'array',
'items':
{
'type': 'object',
'properties': {
'idp_id': {'type': 'string'},
'protocols': {
'type': 'array',
'items':
{
'type': 'object',
'properties': {
'protocol_id': {'type': 'string'},
'unique_id': {'type': 'string'}
},
'required': ['protocol_id', 'unique_id']
},
'minItems': 1
}
},
'required': ['idp_id', 'protocols']
},
},
'name': _identity_name,
'password': {
'type': ['string', 'null']

View File

@ -50,6 +50,14 @@ def federated_objects_to_list(fed_ref):
class ShadowUsersDriverBase(object, metaclass=abc.ABCMeta):
"""Interface description for an Shadow Users driver."""
@abc.abstractmethod
def create_federated_object(self, fed_dict):
"""Create a new federated object.
:param dict federated_dict: Reference to the federated user
"""
raise exception.NotImplemented()
@abc.abstractmethod
def create_federated_user(self, domain_id, federated_dict, email=None):
"""Create a new user with the federated identity.

View File

@ -54,6 +54,12 @@ class ShadowUsers(base.ShadowUsersDriverBase):
session.add(user_ref)
return identity_base.filter_user(user_ref.to_dict())
@sql.handle_conflicts(conflict_type='federated_user')
def create_federated_object(self, fed_dict):
with sql.session_for_write() as session:
fed_ref = model.FederatedUser.from_dict(fed_dict)
session.add(fed_ref)
def get_federated_objects(self, user_id):
with sql.session_for_read() as session:
query = session.query(model.FederatedUser)

View File

@ -13,6 +13,7 @@
import uuid
from keystone.common import provider_api
from keystone import exception
from keystone.tests import unit
from keystone.tests.unit import default_fixtures
from keystone.tests.unit.identity.shadow_users import test_backend
@ -88,3 +89,63 @@ class TestUserWithFederatedUser(ShadowUsersTests):
self.assertEqual(1, len(user_ref['federated']))
self.assertFederatedDictsEqual(fed_dict, user_ref['federated'][0])
def test_create_user_with_invalid_idp_and_protocol_fails(self):
baduser = unit.new_user_ref(domain_id=self.domain_id)
baduser['federated'] = [
{
'idp_id': 'fakeidp',
'protocols': [
{
'protocol_id': 'nonexistent',
'unique_id': 'unknown'
}
]
}
]
# Check validation works by throwing a federated object with
# invalid idp_id, protocol_id inside the user passed to create_user.
self.assertRaises(exception.ValidationError,
self.identity_api.create_user,
baduser)
baduser['federated'][0]['idp_id'] = self.idp['id']
self.assertRaises(exception.ValidationError,
self.identity_api.create_user,
baduser)
def test_create_user_with_federated_attributes(self):
# Create the schema of a federated attribute being passed in with a
# user.
user = unit.new_user_ref(domain_id=self.domain_id)
unique_id = uuid.uuid4().hex
user['federated'] = [
{
'idp_id': self.idp['id'],
'protocols': [
{
'protocol_id': self.protocol['id'],
'unique_id': unique_id
}
]
}
]
# Test that there are no current federated_users that match our users
# federated object and create the user
self.assertRaises(exception.UserNotFound,
self.shadow_users_api.get_federated_user,
self.idp['id'],
self.protocol['id'],
unique_id)
ref = self.identity_api.create_user(user)
# Test that the user and federated object now exists
self.assertEqual(user['name'], ref['name'])
self.assertEqual(user['federated'], ref['federated'])
fed_user = self.shadow_users_api.get_federated_user(
self.idp['id'],
self.protocol['id'],
unique_id)
self.assertIsNotNone(fed_user)

View File

@ -1177,3 +1177,48 @@ class UserFederatedAttributesTests(test_v3.RestfulTestCase):
self.assertIn('unique_id', user['federated'][0]['protocols'][0])
r = self.get('/users/%(user_id)s' % {'user_id': user['id']})
self.assertValidUserResponse(r, user)
def test_create_user_with_federated_attributes(self):
"""Call ``POST /users``."""
idp, protocol = self._create_federated_attributes()
ref = unit.new_user_ref(domain_id=self.domain_id)
ref['federated'] = [
{
'idp_id': idp['id'],
'protocols': [
{
'protocol_id': protocol['id'],
'unique_id': uuid.uuid4().hex
}
]
}
]
r = self.post(
'/users',
body={'user': ref})
user = r.result['user']
self.assertEqual(user['name'], ref['name'])
self.assertEqual(user['federated'], ref['federated'])
self.assertValidUserResponse(r, ref)
def test_create_user_fails_when_given_invalid_idp_and_protocols(self):
"""Call ``POST /users`` with invalid idp and protocol to fail."""
idp, protocol = self._create_federated_attributes()
ref = unit.new_user_ref(domain_id=self.domain_id)
ref['federated'] = [
{
'idp_id': 'fakeidp',
'protocols': [
{
'protocol_id': 'fakeprotocol_id',
'unique_id': uuid.uuid4().hex
}
]
}
]
self.post('/users', body={'user': ref}, token=self.get_admin_token(),
expected_status=http.client.BAD_REQUEST)
ref['federated'][0]['idp_id'] = idp['id']
self.post('/users', body={'user': ref}, token=self.get_admin_token(),
expected_status=http.client.BAD_REQUEST)