Identify groups by name/domain in mapping rules.
Add a mechanism that allows for building federation mapping groups where a local group is identified by a name and a domain. If the group doesn't exist corresponding exceptions will be raised. Partially-Implements: bp mapping-enhancements Change-Id: I67e3c0f1d91fa1e85a1f850f73d04db367ea27e1
This commit is contained in:
parent
9f48b37e0f
commit
3b9d1cc1e3
|
@ -30,7 +30,8 @@ from keystone.openstack.common import log
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@dependency.requires('federation_api', 'identity_api', 'token_provider_api')
|
@dependency.requires('assignment_api', 'federation_api', 'identity_api',
|
||||||
|
'token_provider_api')
|
||||||
class Mapped(auth.AuthMethodHandler):
|
class Mapped(auth.AuthMethodHandler):
|
||||||
|
|
||||||
def _get_token_ref(self, auth_payload):
|
def _get_token_ref(self, auth_payload):
|
||||||
|
@ -62,7 +63,7 @@ class Mapped(auth.AuthMethodHandler):
|
||||||
self.token_provider_api)
|
self.token_provider_api)
|
||||||
else:
|
else:
|
||||||
handle_unscoped_token(context, auth_payload, auth_context,
|
handle_unscoped_token(context, auth_payload, auth_context,
|
||||||
self.federation_api,
|
self.assignment_api, self.federation_api,
|
||||||
self.identity_api)
|
self.identity_api)
|
||||||
|
|
||||||
|
|
||||||
|
@ -99,8 +100,8 @@ def handle_scoped_token(context, auth_payload, auth_context, token_ref,
|
||||||
auth_context[federation.PROTOCOL] = protocol
|
auth_context[federation.PROTOCOL] = protocol
|
||||||
|
|
||||||
|
|
||||||
def handle_unscoped_token(context, auth_payload, auth_context, federation_api,
|
def handle_unscoped_token(context, auth_payload, auth_context,
|
||||||
identity_api):
|
assignment_api, federation_api, identity_api):
|
||||||
assertion = extract_assertion_data(context)
|
assertion = extract_assertion_data(context)
|
||||||
identity_provider = auth_payload['identity_provider']
|
identity_provider = auth_payload['identity_provider']
|
||||||
protocol = auth_payload['protocol']
|
protocol = auth_payload['protocol']
|
||||||
|
@ -115,8 +116,8 @@ def handle_unscoped_token(context, auth_payload, auth_context, federation_api,
|
||||||
|
|
||||||
try:
|
try:
|
||||||
mapped_properties = apply_mapping_filter(identity_provider, protocol,
|
mapped_properties = apply_mapping_filter(identity_provider, protocol,
|
||||||
assertion, federation_api,
|
assertion, assignment_api,
|
||||||
identity_api)
|
federation_api, identity_api)
|
||||||
user_id = setup_username(context, mapped_properties)
|
user_id = setup_username(context, mapped_properties)
|
||||||
group_ids = mapped_properties['group_ids']
|
group_ids = mapped_properties['group_ids']
|
||||||
|
|
||||||
|
@ -151,15 +152,30 @@ def extract_assertion_data(context):
|
||||||
|
|
||||||
|
|
||||||
def apply_mapping_filter(identity_provider, protocol, assertion,
|
def apply_mapping_filter(identity_provider, protocol, assertion,
|
||||||
federation_api, identity_api):
|
assignment_api, federation_api, identity_api):
|
||||||
mapping = federation_api.get_mapping_from_idp_and_protocol(
|
mapping = federation_api.get_mapping_from_idp_and_protocol(
|
||||||
identity_provider, protocol)
|
identity_provider, protocol)
|
||||||
rules = jsonutils.loads(mapping['rules'])
|
rules = jsonutils.loads(mapping['rules'])
|
||||||
LOG.debug('using the following rules: %s', rules)
|
LOG.debug('using the following rules: %s', rules)
|
||||||
rule_processor = utils.RuleProcessor(rules)
|
rule_processor = utils.RuleProcessor(rules)
|
||||||
mapped_properties = rule_processor.process(assertion)
|
mapped_properties = rule_processor.process(assertion)
|
||||||
utils.validate_groups(mapped_properties['group_ids'],
|
|
||||||
mapping['id'], identity_api)
|
# NOTE(marek-denis): We update group_ids only here to avoid fetching
|
||||||
|
# groups identified by name/domain twice.
|
||||||
|
# NOTE(marek-denis): Groups are translated from name/domain to their
|
||||||
|
# corresponding ids in the auth plugin, as we need information what
|
||||||
|
# ``mapping_id`` was used as well as idenity_api and assignment_api
|
||||||
|
# objects.
|
||||||
|
group_ids = mapped_properties['group_ids']
|
||||||
|
utils.validate_groups_in_backend(group_ids,
|
||||||
|
mapping['id'],
|
||||||
|
identity_api)
|
||||||
|
group_ids.extend(
|
||||||
|
utils.transform_to_group_ids(
|
||||||
|
mapped_properties['group_names'], mapping['id'],
|
||||||
|
identity_api, assignment_api))
|
||||||
|
utils.validate_groups_cardinality(group_ids, mapping['id'])
|
||||||
|
mapped_properties['group_ids'] = list(set(group_ids))
|
||||||
return mapped_properties
|
return mapped_properties
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -122,10 +122,36 @@ def validate_expiration(token_ref):
|
||||||
raise exception.Unauthorized(_('Federation token is expired'))
|
raise exception.Unauthorized(_('Federation token is expired'))
|
||||||
|
|
||||||
|
|
||||||
def validate_groups(group_ids, mapping_id, identity_api):
|
def validate_groups_cardinality(group_ids, mapping_id):
|
||||||
|
"""Check if groups list is non-empty.
|
||||||
|
|
||||||
|
:param group_ids: list of group ids
|
||||||
|
:type group_ids: list of str
|
||||||
|
|
||||||
|
:raises exception.MissingGroups: if ``group_ids`` cardinality is 0
|
||||||
|
|
||||||
|
"""
|
||||||
if not group_ids:
|
if not group_ids:
|
||||||
raise exception.MissingGroups(mapping_id=mapping_id)
|
raise exception.MissingGroups(mapping_id=mapping_id)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_groups_in_backend(group_ids, mapping_id, identity_api):
|
||||||
|
"""Iterate over group ids and make sure they are present in the backend/
|
||||||
|
|
||||||
|
This call is not transactional.
|
||||||
|
:param group_ids: IDs of the groups to be checked
|
||||||
|
:type group_ids: list of str
|
||||||
|
|
||||||
|
:param mapping_id: id of the mapping used for this operation
|
||||||
|
:type mapping_id: str
|
||||||
|
|
||||||
|
:param identity_api: Identity Manager object used for communication with
|
||||||
|
backend
|
||||||
|
:type identity_api: identity.Manager
|
||||||
|
|
||||||
|
:raises: exception.MappedGroupNotFound
|
||||||
|
|
||||||
|
"""
|
||||||
for group_id in group_ids:
|
for group_id in group_ids:
|
||||||
try:
|
try:
|
||||||
identity_api.get_group(group_id)
|
identity_api.get_group(group_id)
|
||||||
|
@ -134,6 +160,97 @@ def validate_groups(group_ids, mapping_id, identity_api):
|
||||||
group_id=group_id, mapping_id=mapping_id)
|
group_id=group_id, mapping_id=mapping_id)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_groups(group_ids, mapping_id, identity_api):
|
||||||
|
"""Check group ids cardinality and check their existence in the backend.
|
||||||
|
|
||||||
|
This call is not transactional.
|
||||||
|
:param group_ids: IDs of the groups to be checked
|
||||||
|
:type group_ids: list of str
|
||||||
|
|
||||||
|
:param mapping_id: id of the mapping used for this operation
|
||||||
|
:type mapping_id: str
|
||||||
|
|
||||||
|
:param identity_api: Identity Manager object used for communication with
|
||||||
|
backend
|
||||||
|
:type identity_api: identity.Manager
|
||||||
|
|
||||||
|
:raises: exception.MappedGroupNotFound
|
||||||
|
:raises: exception.MissingGroups
|
||||||
|
|
||||||
|
"""
|
||||||
|
validate_groups_cardinality(group_ids, mapping_id)
|
||||||
|
validate_groups_in_backend(group_ids, mapping_id, identity_api)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO(marek-denis): Optimize this function, so the number of calls to the
|
||||||
|
# backend are minimized.
|
||||||
|
def transform_to_group_ids(group_names, mapping_id,
|
||||||
|
identity_api, assignment_api):
|
||||||
|
"""Transform groups identitified by name/domain to their ids
|
||||||
|
|
||||||
|
Function accepts list of groups identified by a name and domain giving
|
||||||
|
a list of group ids in return.
|
||||||
|
|
||||||
|
Example of group_names parameter::
|
||||||
|
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "group_name",
|
||||||
|
"domain": {
|
||||||
|
"id": "domain_id"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "group_name_2",
|
||||||
|
"domain": {
|
||||||
|
"name": "domain_name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
:param group_names: list of group identified by name and its domain.
|
||||||
|
:type group_names: list
|
||||||
|
|
||||||
|
:param mapping_id: id of the mapping used for mapping assertion into
|
||||||
|
local credentials
|
||||||
|
:type mapping_id: str
|
||||||
|
|
||||||
|
:param identity_api: identity_api object
|
||||||
|
:param assignment_api: assignment_api object
|
||||||
|
|
||||||
|
:returns: generator object with group ids
|
||||||
|
|
||||||
|
:raises: excepton.MappedGroupNotFound: in case asked group doesn't
|
||||||
|
exist in the backend.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def resolve_domain(domain):
|
||||||
|
"""Return domain id.
|
||||||
|
|
||||||
|
Input is a dictionary with a domain identified either by a ``id`` or a
|
||||||
|
``name``. In the latter case system will attempt to fetch domain object
|
||||||
|
from the backend.
|
||||||
|
|
||||||
|
:returns: domain's id
|
||||||
|
:rtype: str
|
||||||
|
|
||||||
|
"""
|
||||||
|
domain_id = (domain.get('id') or
|
||||||
|
assignment_api.get_domain_by_name(
|
||||||
|
domain.get('name')).get('id'))
|
||||||
|
return domain_id
|
||||||
|
|
||||||
|
for group in group_names:
|
||||||
|
try:
|
||||||
|
group_dict = identity_api.get_group_by_name(
|
||||||
|
group['name'], resolve_domain(group['domain']))
|
||||||
|
yield group_dict['id']
|
||||||
|
except exception.GroupNotFound:
|
||||||
|
raise exception.MappedGroupNotFound(
|
||||||
|
group_id=group['name'], mapping_id=mapping_id)
|
||||||
|
|
||||||
|
|
||||||
def get_assertion_params_from_env(context):
|
def get_assertion_params_from_env(context):
|
||||||
LOG.debug('Environment variables: %s', context['environment'])
|
LOG.debug('Environment variables: %s', context['environment'])
|
||||||
prefix = CONF.federation.assertion_prefix
|
prefix = CONF.federation.assertion_prefix
|
||||||
|
@ -189,7 +306,27 @@ class RuleProcessor(object):
|
||||||
|
|
||||||
{
|
{
|
||||||
'name': 'foobar',
|
'name': 'foobar',
|
||||||
'group_ids': ['abc123', 'def456']
|
'group_ids': ['abc123', 'def456'],
|
||||||
|
'group_names': [
|
||||||
|
{
|
||||||
|
'name': 'group_name_1',
|
||||||
|
'domain': {
|
||||||
|
'name': 'domain1'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'group_name_1_1',
|
||||||
|
'domain': {
|
||||||
|
'name': 'domain1'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'group_name_2',
|
||||||
|
'domain': {
|
||||||
|
'id': 'xyz132'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -243,13 +380,20 @@ class RuleProcessor(object):
|
||||||
|
|
||||||
[{'group': {'id': '0cd5e9'}, 'user': {'email': 'bob@example.com'}}]
|
[{'group': {'id': '0cd5e9'}, 'user': {'email': 'bob@example.com'}}]
|
||||||
|
|
||||||
:returns: dictionary with user name and group_ids.
|
:returns: dictionary with user name, group_ids and group_names.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def extract_groups(groups_by_domain):
|
||||||
|
for groups in groups_by_domain.values():
|
||||||
|
for group in {g['name']: g for g in groups}.values():
|
||||||
|
yield group
|
||||||
|
|
||||||
# initialize the group_ids as a set to eliminate duplicates
|
# initialize the group_ids as a set to eliminate duplicates
|
||||||
user_name = None
|
user_name = None
|
||||||
group_ids = set()
|
group_ids = set()
|
||||||
|
group_names = list()
|
||||||
|
groups_by_domain = dict()
|
||||||
|
|
||||||
for identity_value in identity_values:
|
for identity_value in identity_values:
|
||||||
if 'user' in identity_value:
|
if 'user' in identity_value:
|
||||||
|
@ -260,9 +404,18 @@ class RuleProcessor(object):
|
||||||
else:
|
else:
|
||||||
user_name = identity_value['user']['name']
|
user_name = identity_value['user']['name']
|
||||||
if 'group' in identity_value:
|
if 'group' in identity_value:
|
||||||
group_ids.add(identity_value['group']['id'])
|
group = identity_value['group']
|
||||||
|
if 'id' in group:
|
||||||
|
group_ids.add(group['id'])
|
||||||
|
elif 'name' in group:
|
||||||
|
domain = (group['domain'].get('name') or
|
||||||
|
group['domain'].get('id'))
|
||||||
|
groups_by_domain.setdefault(domain, list()).append(group)
|
||||||
|
group_names.extend(extract_groups(groups_by_domain))
|
||||||
|
|
||||||
return {'name': user_name, 'group_ids': list(group_ids)}
|
return {'name': user_name,
|
||||||
|
'group_ids': list(group_ids),
|
||||||
|
'group_names': group_names}
|
||||||
|
|
||||||
def _update_local_mapping(self, local, direct_maps):
|
def _update_local_mapping(self, local, direct_maps):
|
||||||
"""Replace any {0}, {1} ... values with data from the assertion.
|
"""Replace any {0}, {1} ... values with data from the assertion.
|
||||||
|
|
|
@ -15,7 +15,12 @@
|
||||||
EMPLOYEE_GROUP_ID = "0cd5e9"
|
EMPLOYEE_GROUP_ID = "0cd5e9"
|
||||||
CONTRACTOR_GROUP_ID = "85a868"
|
CONTRACTOR_GROUP_ID = "85a868"
|
||||||
TESTER_GROUP_ID = "123"
|
TESTER_GROUP_ID = "123"
|
||||||
|
TESTER_GROUP_NAME = "tester"
|
||||||
DEVELOPER_GROUP_ID = "xyz"
|
DEVELOPER_GROUP_ID = "xyz"
|
||||||
|
DEVELOPER_GROUP_NAME = "developer"
|
||||||
|
DEVELOPER_GROUP_DOMAIN_NAME = "outsourcing"
|
||||||
|
DEVELOPER_GROUP_DOMAIN_ID = "5abc43"
|
||||||
|
|
||||||
|
|
||||||
# Mapping summary:
|
# Mapping summary:
|
||||||
# LastName Smith & Not Contractor or SubContractor -> group 0cd5e9
|
# LastName Smith & Not Contractor or SubContractor -> group 0cd5e9
|
||||||
|
@ -453,6 +458,67 @@ MAPPING_TESTER_REGEX = {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
MAPPING_GROUP_NAMES = {
|
||||||
|
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"local": [
|
||||||
|
{
|
||||||
|
"user": {
|
||||||
|
"name": "{0}",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"remote": [
|
||||||
|
{
|
||||||
|
"type": "UserName"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"local": [
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"name": DEVELOPER_GROUP_NAME,
|
||||||
|
"domain": {
|
||||||
|
"name": DEVELOPER_GROUP_DOMAIN_NAME
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"remote": [
|
||||||
|
{
|
||||||
|
"type": "orgPersonType",
|
||||||
|
"any_one_of": [
|
||||||
|
"Employee"
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"local": [
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"name": TESTER_GROUP_NAME,
|
||||||
|
"domain": {
|
||||||
|
"id": DEVELOPER_GROUP_DOMAIN_ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"remote": [
|
||||||
|
{
|
||||||
|
"type": "orgPersonType",
|
||||||
|
"any_one_of": [
|
||||||
|
"BuildingX"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
EMPLOYEE_ASSERTION = {
|
EMPLOYEE_ASSERTION = {
|
||||||
'Email': 'tim@example.com',
|
'Email': 'tim@example.com',
|
||||||
'UserName': 'tbo',
|
'UserName': 'tbo',
|
||||||
|
@ -493,6 +559,14 @@ CUSTOMER_ASSERTION = {
|
||||||
'orgPersonType': 'Customer;'
|
'orgPersonType': 'Customer;'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ANOTHER_CUSTOMER_ASSERTION = {
|
||||||
|
'Email': 'mark@example.com',
|
||||||
|
'UserName': 'markcol',
|
||||||
|
'FirstName': 'Mark',
|
||||||
|
'LastName': 'Collins',
|
||||||
|
'orgPersonType': 'Managers;CEO;CTO'
|
||||||
|
}
|
||||||
|
|
||||||
TESTER_ASSERTION = {
|
TESTER_ASSERTION = {
|
||||||
'Email': 'testacct@example.com',
|
'Email': 'testacct@example.com',
|
||||||
'UserName': 'testacct',
|
'UserName': 'testacct',
|
||||||
|
@ -501,6 +575,10 @@ TESTER_ASSERTION = {
|
||||||
'orgPersonType': 'MadeupGroup;Tester;GroupX'
|
'orgPersonType': 'MadeupGroup;Tester;GroupX'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ANOTHER_TESTER_ASSERTION = {
|
||||||
|
'UserName': 'IamTester'
|
||||||
|
}
|
||||||
|
|
||||||
BAD_TESTER_ASSERTION = {
|
BAD_TESTER_ASSERTION = {
|
||||||
'Email': 'eviltester@example.org',
|
'Email': 'eviltester@example.org',
|
||||||
'UserName': 'Evil',
|
'UserName': 'Evil',
|
||||||
|
|
|
@ -764,6 +764,38 @@ class MappingRuleEngineTests(FederationTests):
|
||||||
self.assertIsNone(mapped_properties['name'])
|
self.assertIsNone(mapped_properties['name'])
|
||||||
self.assertListEqual(list(), mapped_properties['group_ids'])
|
self.assertListEqual(list(), mapped_properties['group_ids'])
|
||||||
|
|
||||||
|
def test_rule_engine_returns_group_names(self):
|
||||||
|
"""Check whether RuleProcessor returns group names with their domains.
|
||||||
|
|
||||||
|
RuleProcessor shold return 'group_names' entry with a list of
|
||||||
|
dictionaries with two entries 'name' and 'domain' identyfing group by
|
||||||
|
its name and domain.
|
||||||
|
|
||||||
|
"""
|
||||||
|
mapping = mapping_fixtures.MAPPING_GROUP_NAMES
|
||||||
|
rp = mapping_utils.RuleProcessor(mapping['rules'])
|
||||||
|
assertion = mapping_fixtures.EMPLOYEE_ASSERTION
|
||||||
|
mapped_properties = rp.process(assertion)
|
||||||
|
self.assertIsNotNone(mapped_properties)
|
||||||
|
reference = {
|
||||||
|
mapping_fixtures.DEVELOPER_GROUP_NAME:
|
||||||
|
{
|
||||||
|
"name": mapping_fixtures.DEVELOPER_GROUP_NAME,
|
||||||
|
"domain": {
|
||||||
|
"name": mapping_fixtures.DEVELOPER_GROUP_DOMAIN_NAME
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mapping_fixtures.TESTER_GROUP_NAME:
|
||||||
|
{
|
||||||
|
"name": mapping_fixtures.TESTER_GROUP_NAME,
|
||||||
|
"domain": {
|
||||||
|
"id": mapping_fixtures.DEVELOPER_GROUP_DOMAIN_ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for rule in mapped_properties['group_names']:
|
||||||
|
self.assertDictEqual(reference.get(rule.get('name')), rule)
|
||||||
|
|
||||||
|
|
||||||
class FederatedTokenTests(FederationTests):
|
class FederatedTokenTests(FederationTests):
|
||||||
|
|
||||||
|
@ -923,6 +955,19 @@ class FederatedTokenTests(FederationTests):
|
||||||
r = self._issue_unscoped_token()
|
r = self._issue_unscoped_token()
|
||||||
self.assertIsNotNone(r.headers.get('X-Subject-Token'))
|
self.assertIsNotNone(r.headers.get('X-Subject-Token'))
|
||||||
|
|
||||||
|
def test_issue_unscoped_token_group_names_in_mapping(self):
|
||||||
|
r = self._issue_unscoped_token(assertion='ANOTHER_CUSTOMER_ASSERTION')
|
||||||
|
ref_groups = set([self.group_customers['id'], self.group_admins['id']])
|
||||||
|
token_resp = r.json_body
|
||||||
|
token_groups = token_resp['token']['user']['OS-FEDERATION']['groups']
|
||||||
|
token_groups = set([group['id'] for group in token_groups])
|
||||||
|
self.assertEqual(ref_groups, token_groups)
|
||||||
|
|
||||||
|
def test_issue_unscoped_tokens_nonexisting_group(self):
|
||||||
|
self.assertRaises(exception.MappedGroupNotFound,
|
||||||
|
self._issue_unscoped_token,
|
||||||
|
assertion='ANOTHER_TESTER_ASSERTION')
|
||||||
|
|
||||||
def test_issue_unscoped_token_with_remote_user_as_empty_string(self):
|
def test_issue_unscoped_token_with_remote_user_as_empty_string(self):
|
||||||
# make sure that REMOTE_USER set as the empty string won't interfere
|
# make sure that REMOTE_USER set as the empty string won't interfere
|
||||||
r = self._issue_unscoped_token(environment={'REMOTE_USER': ''})
|
r = self._issue_unscoped_token(environment={'REMOTE_USER': ''})
|
||||||
|
@ -1569,8 +1614,87 @@ class FederatedTokenTests(FederationTests):
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
# rules with local group names
|
||||||
|
{
|
||||||
|
"local": [
|
||||||
|
{
|
||||||
|
'user': {
|
||||||
|
'name': '{0}'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"name": self.group_customers['name'],
|
||||||
|
"domain": {
|
||||||
|
"name": self.domainA['name']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"remote": [
|
||||||
|
{
|
||||||
|
'type': 'UserName',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "orgPersonType",
|
||||||
|
"any_one_of": [
|
||||||
|
"CEO",
|
||||||
|
"CTO"
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"local": [
|
||||||
|
{
|
||||||
|
'user': {
|
||||||
|
'name': '{0}'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"name": self.group_admins['name'],
|
||||||
|
"domain": {
|
||||||
|
"id": self.domainA['id']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"remote": [
|
||||||
|
{
|
||||||
|
"type": "UserName",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "orgPersonType",
|
||||||
|
"any_one_of": [
|
||||||
|
"Managers"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"local": [
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"name": "NON_EXISTING",
|
||||||
|
"domain": {
|
||||||
|
"id": self.domainA['id']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"remote": [
|
||||||
|
{
|
||||||
|
"type": "UserName",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "UserName",
|
||||||
|
"any_one_of": [
|
||||||
|
"IamTester"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue