provisionning: only send user to wcs, combo, chrono, lingo and fargo (#82004)
gitea/hobo/pipeline/head This commit looks good Details

This commit is contained in:
Benjamin Dauvergne 2023-10-04 17:57:47 +02:00
parent 8107b3083e
commit 1a9dbed99e
5 changed files with 158 additions and 66 deletions

View File

@ -213,7 +213,7 @@ class Provisionning(threading.local):
if roles_with_attributes:
for ou, users in ous.items():
for service, audience in self.get_audience(ou):
for service, audience in self.get_audience_for_users(ou):
for batched_users in batch(users, 500):
batched_users = list(batched_users)
for user in batched_users:
@ -236,7 +236,7 @@ class Provisionning(threading.local):
)
else:
for ou, users in ous.items():
audience = [a for service, a in self.get_audience(ou)]
audience = [a for service, a in self.get_audience_for_users(ou)]
if not audience:
continue
logger.info(
@ -258,7 +258,7 @@ class Provisionning(threading.local):
sync=sync,
)
elif users:
audience = [audience for ou in ous for s, audience in self.get_audience(ou)]
audience = [audience for ou in ous for s, audience in self.get_audience_for_users(ou)]
logger.info(
'deprovisionning users %s from %s', ', '.join(map(force_str, users)), ', '.join(audience)
)
@ -383,11 +383,31 @@ class Provisionning(threading.local):
self.stop(provision=exc_type is None)
def get_audience(self, ou):
known_services = getattr(settings, 'KNOWN_SERVICES', {})
valid_entity_ids = {
service['saml-sp-metadata-url']
for services in known_services.values()
for service in services.values()
}
if ou:
qs = LibertyProvider.objects.filter(ou=ou)
else:
qs = LibertyProvider.objects.filter(ou__isnull=True)
return [(service, service.entity_id) for service in qs]
return [(service, service.entity_id) for service in qs if service.entity_id in valid_entity_ids]
def get_audience_for_users(self, ou):
audience = self.get_audience(ou)
valid_entity_ids = set()
known_services = getattr(settings, 'KNOWN_SERVICES', {})
for service_type, services in known_services.items():
# ignore service which only need provisionning on SSO
if service_type not in ('wcs', 'combo', 'chrono', 'lingo', 'fargo'):
continue
for service in services.values():
valid_entity_ids.add(service['saml-sp-metadata-url'])
return [(service, entity_id) for (service, entity_id) in audience if entity_id in valid_entity_ids]
def get_entity_id(self):
tenant = getattr(connection, 'tenant', None)

View File

@ -100,8 +100,11 @@ class ApiProvisionningEngine(provisionning.Provisionning):
return services_by_url
def notify_agents(self, data, sync=False):
audience = data['audience'][:]
self.leftover_audience = self.notify_agents_http(data, sync=sync)
# only include filtered services in leftovers
services_by_url = self.get_http_services_by_url()
self.leftover_audience = [x for x in self.leftover_audience if x in services_by_url]
self.reached_audience = [x for x in services_by_url if x not in self.leftover_audience]
self.reached_audience = [
x for x in services_by_url if x not in self.leftover_audience and x in audience
]

View File

@ -100,6 +100,9 @@ class Command(BaseCommand):
# add an attribute to current tenant for easier retrieval
self.me['this'] = True
if not self.me.get('secondary'):
replace_file(tenant_hobo_json, json.dumps(hobo_environment, indent=2))
self.deploy_specifics(hobo_environment, tenant)
if not self.me.get('secondary'):

View File

@ -57,6 +57,16 @@ def tenant_factory(transactional_db, tenant_base, settings):
'provisionning-url': 'http://other.example.net/__provision__/',
'saml-sp-metadata-url': 'http://other.example.net/metadata/',
},
{
'slug': 'combo',
'title': 'Combo',
'service-id': 'combo',
'secret_key': 'abcdef',
'url': 'http://combo.example.net',
'base_url': 'http://combo.example.net',
'provisionning-url': 'http://combo.example.net/__provision__/',
'saml-sp-metadata-url': 'http://combo.example.net/metadata/',
},
{
'slug': 'more',
'title': 'More',
@ -120,4 +130,4 @@ def app_factory(request):
@pytest.fixture
def notify_agents(mocker):
yield mocker.patch('hobo.agent.authentic2.provisionning.notify_agents')
yield mocker.patch('hobo.agent.authentic2.provisionning.Provisionning.notify_agents')

View File

@ -25,7 +25,15 @@ def test_provision_role(transactional_db, tenant, caplog):
with tenant_context(tenant):
LibertyProvider.objects.create(
ou=get_default_ou(),
name='provider',
name='wcs',
slug='wcs',
entity_id='http://more.example.net/metadata/',
protocol_conformance=lasso.PROTOCOL_SAML_2_0,
)
LibertyProvider.objects.create(
ou=get_default_ou(),
name='whatever',
slug='whatever',
entity_id='http://provider.com',
protocol_conformance=lasso.PROTOCOL_SAML_2_0,
)
@ -38,7 +46,7 @@ def test_provision_role(transactional_db, tenant, caplog):
arg = arg[0][0]
assert isinstance(arg, dict)
assert set(arg.keys()) == {'audience', '@type', 'objects', 'full'}
assert arg['audience'] == ['http://provider.com']
assert arg['audience'] == ['http://more.example.net/metadata/']
assert arg['@type'] == 'provision'
assert arg['full'] is False
objects = arg['objects']
@ -75,7 +83,7 @@ def test_provision_role(transactional_db, tenant, caplog):
arg = arg[0][0]
assert isinstance(arg, dict)
assert set(arg.keys()) == {'audience', '@type', 'objects', 'full'}
assert arg['audience'] == ['http://provider.com']
assert arg['audience'] == ['http://more.example.net/metadata/']
assert arg['@type'] == 'provision'
assert arg['full'] is False
objects = arg['objects']
@ -109,7 +117,7 @@ def test_provision_role(transactional_db, tenant, caplog):
arg = arg[0][0]
assert isinstance(arg, dict)
assert set(arg.keys()) == {'audience', '@type', 'objects', 'full'}
assert arg['audience'] == ['http://provider.com']
assert arg['audience'] == ['http://more.example.net/metadata/']
assert arg['@type'] == 'deprovision'
assert arg['full'] is False
objects = arg['objects']
@ -124,7 +132,7 @@ def test_provision_role(transactional_db, tenant, caplog):
def test_technical_role_unprovisionned(transactional_db, tenant, caplog):
with patch('hobo.agent.authentic2.provisionning.notify_agents') as notify_agents:
with patch('hobo.agent.authentic2.provisionning.Provisionning.notify_agents') as notify_agents:
with tenant_context(tenant):
LibertyProvider.objects.create(
ou=get_default_ou(),
@ -153,10 +161,25 @@ def test_technical_role_unprovisionned(transactional_db, tenant, caplog):
def test_provision_user(transactional_db, tenant, caplog):
with patch('hobo.agent.authentic2.provisionning.notify_agents') as notify_agents:
with tenant_context(tenant):
service = LibertyProvider.objects.create(
LibertyProvider.objects.create(
slug='other',
ou=get_default_ou(),
name='provider',
entity_id='http://provider.com',
name='Other',
entity_id='http://other.example.net/metadata/',
protocol_conformance=lasso.PROTOCOL_SAML_2_0,
)
wcs = LibertyProvider.objects.create(
slug='more',
ou=get_default_ou(),
name='More',
entity_id='http://more.example.net/metadata/',
protocol_conformance=lasso.PROTOCOL_SAML_2_0,
)
service = LibertyProvider.objects.create(
slug='combo',
ou=get_default_ou(),
name='Combo',
entity_id='http://combo.example.net/metadata/',
protocol_conformance=lasso.PROTOCOL_SAML_2_0,
)
role = Role.objects.create(name='coin', service=service, ou=get_default_ou(), is_superuser=True)
@ -186,13 +209,21 @@ def test_provision_user(transactional_db, tenant, caplog):
users = {user.uuid: user for user in [user1, user2]}
assert notify_agents.call_count == 2
assert notify_agents.call_args_list[0][0][0]['objects']['@type'] == 'role'
assert set(notify_agents.call_args_list[0][0][0]['audience']) == {
'http://combo.example.net/metadata/',
'http://more.example.net/metadata/',
'http://other.example.net/metadata/',
}
arg = notify_agents.call_args
assert arg == call(ANY)
arg = arg[0][0]
assert isinstance(arg, dict)
assert set(arg.keys()) == {'issuer', 'audience', '@type', 'objects', 'full'}
assert arg['issuer'] == 'https://%s/idp/saml2/metadata' % tenant.domain_url
assert arg['audience'] == ['http://provider.com']
assert set(arg['audience']) == {
'http://combo.example.net/metadata/',
'http://more.example.net/metadata/',
}
assert arg['@type'] == 'provision'
assert arg['full'] is False
objects = arg['objects']
@ -239,7 +270,10 @@ def test_provision_user(transactional_db, tenant, caplog):
assert isinstance(arg, dict)
assert set(arg.keys()) == {'issuer', 'audience', '@type', 'objects', 'full'}
assert arg['issuer'] == 'https://%s/idp/saml2/metadata' % tenant.domain_url
assert arg['audience'] == ['http://provider.com']
assert set(arg['audience']) == {
'http://combo.example.net/metadata/',
'http://more.example.net/metadata/',
}
assert arg['@type'] == 'provision'
assert arg['full'] is False
objects = arg['objects']
@ -249,7 +283,7 @@ def test_provision_user(transactional_db, tenant, caplog):
data = objects['data']
assert isinstance(data, list)
assert len(data) == 2
for o, user in zip(data, [user1, user2]):
for o in data:
assert set(o.keys()) >= {
'code_postal',
'uuid',
@ -260,6 +294,8 @@ def test_provision_user(transactional_db, tenant, caplog):
'email',
'roles',
}
assert o['uuid'] in users
user = users[o['uuid']]
assert o['uuid'] == user.uuid
assert o['username'] == user.username
assert o['first_name'] == user.first_name
@ -274,13 +310,8 @@ def test_provision_user(transactional_db, tenant, caplog):
# test a service in a second OU also get the provisionning message
notify_agents.reset_mock()
ou2 = OrganizationalUnit.objects.create(name='ou2', slug='ou2')
LibertyProvider.objects.create(
ou=ou2,
name='provider2',
slug='provider2',
entity_id='http://provider2.com',
protocol_conformance=lasso.PROTOCOL_SAML_2_0,
)
wcs.ou = ou2
wcs.save()
attribute.set_value(user1, '13500')
with provisionning:
user1.save()
@ -289,7 +320,7 @@ def test_provision_user(transactional_db, tenant, caplog):
assert notify_agents.call_count == 2
assert set(
notify_agents.mock_calls[0][1][0]['audience'] + notify_agents.mock_calls[1][1][0]['audience']
) == {'http://provider.com', 'http://provider2.com'}
) == {'http://more.example.net/metadata/', 'http://combo.example.net/metadata/'}
ou2.delete()
notify_agents.reset_mock()
@ -303,7 +334,7 @@ def test_provision_user(transactional_db, tenant, caplog):
assert isinstance(arg, dict)
assert set(arg.keys()) == {'issuer', 'audience', '@type', 'objects', 'full'}
assert arg['issuer'] == 'https://%s/idp/saml2/metadata' % tenant.domain_url
assert arg['audience'] == ['http://provider.com']
assert arg['audience'] == ['http://combo.example.net/metadata/']
assert arg['@type'] == 'provision'
assert arg['full'] is False
objects = arg['objects']
@ -347,7 +378,7 @@ def test_provision_user(transactional_db, tenant, caplog):
assert isinstance(arg, dict)
assert set(arg.keys()) == {'issuer', 'audience', '@type', 'objects', 'full'}
assert arg['issuer'] == 'https://%s/idp/saml2/metadata' % tenant.domain_url
assert arg['audience'] == ['http://provider.com']
assert arg['audience'] == ['http://combo.example.net/metadata/']
assert arg['@type'] == 'provision'
assert arg['full'] is False
objects = arg['objects']
@ -391,7 +422,7 @@ def test_provision_user(transactional_db, tenant, caplog):
assert isinstance(arg, dict)
assert set(arg.keys()) == {'issuer', 'audience', '@type', 'objects', 'full'}
assert arg['issuer'] == 'https://%s/idp/saml2/metadata' % tenant.domain_url
assert arg['audience'] == ['http://provider.com']
assert arg['audience'] == ['http://combo.example.net/metadata/']
assert arg['@type'] == 'provision'
assert arg['full'] is False
objects = arg['objects']
@ -431,7 +462,7 @@ def test_provision_user(transactional_db, tenant, caplog):
assert isinstance(arg, dict)
assert set(arg.keys()) == {'issuer', 'audience', '@type', 'objects', 'full'}
assert arg['issuer'] == 'https://%s/idp/saml2/metadata' % tenant.domain_url
assert arg['audience'] == ['http://provider.com']
assert arg['audience'] == ['http://combo.example.net/metadata/']
assert arg['@type'] == 'provision'
assert arg['full'] is False
objects = arg['objects']
@ -475,7 +506,7 @@ def test_provision_user(transactional_db, tenant, caplog):
assert isinstance(arg, dict)
assert set(arg.keys()) == {'issuer', 'audience', '@type', 'objects', 'full'}
assert arg['issuer'] == 'https://%s/idp/saml2/metadata' % tenant.domain_url
assert arg['audience'] == ['http://provider.com']
assert arg['audience'] == ['http://combo.example.net/metadata/']
assert arg['@type'] == 'provision'
assert arg['full'] is False
objects = arg['objects']
@ -524,7 +555,7 @@ def test_provision_user(transactional_db, tenant, caplog):
assert isinstance(arg, dict)
assert set(arg.keys()) == {'issuer', 'audience', '@type', 'objects', 'full'}
assert arg['issuer'] == 'https://%s/idp/saml2/metadata' % tenant.domain_url
assert set(arg['audience']) == {'http://provider.com', 'http://provider2.com'}
assert set(arg['audience']) == {'http://combo.example.net/metadata/'}
assert arg['@type'] == 'deprovision'
assert arg['full'] is False
objects = arg['objects']
@ -544,7 +575,16 @@ def test_provision_createsuperuser(transactional_db, tenant, caplog):
with tenant_context(tenant):
# create a provider so notification messages have an audience.
LibertyProvider.objects.create(
name='provider',
ou=get_default_ou(),
name='wcs',
slug='wcs',
entity_id='http://more.example.net/metadata/',
protocol_conformance=lasso.PROTOCOL_SAML_2_0,
)
LibertyProvider.objects.create(
ou=get_default_ou(),
name='whatever',
slug='whatever',
entity_id='http://provider.com',
protocol_conformance=lasso.PROTOCOL_SAML_2_0,
)
@ -564,8 +604,9 @@ def test_command_hobo_provision(notify_agents, transactional_db, tenant, caplog)
ou = get_default_ou()
LibertyProvider.objects.create(
ou=ou,
name='provider',
entity_id='http://provider.com',
name='wcs',
slug='wcs',
entity_id='http://more.example.net/metadata/',
protocol_conformance=lasso.PROTOCOL_SAML_2_0,
)
for i in range(10):
@ -606,8 +647,16 @@ def test_middleware(notify_agents, app_factory, tenant, settings):
user.save()
LibertyProvider.objects.create(
ou=get_default_ou(),
name='provider',
entity_id='http://provider.com',
name='More',
slug='more',
entity_id='http://more.example.net/metadata/',
protocol_conformance=lasso.PROTOCOL_SAML_2_0,
)
LibertyProvider.objects.create(
ou=get_default_ou(),
name='Combo',
slug='combo',
entity_id='http://combo.example.net/metadata/',
protocol_conformance=lasso.PROTOCOL_SAML_2_0,
)
assert notify_agents.call_count == 0
@ -635,13 +684,13 @@ def test_provision_using_http(transactional_db, tenant, settings, caplog):
LibertyProvider.objects.create(
name='provider',
slug='provider',
entity_id='http://example.org',
entity_id='http://more.example.net/metadata/',
protocol_conformance=lasso.PROTOCOL_SAML_2_0,
)
LibertyProvider.objects.create(
name='provider2',
slug='provider2',
entity_id='http://example.com',
entity_id='http://combo.example.net/metadata/',
protocol_conformance=lasso.PROTOCOL_SAML_2_0,
)
with patch('hobo.agent.authentic2.provisionning.notify_agents') as notify_agents:
@ -653,9 +702,14 @@ def test_provision_using_http(transactional_db, tenant, settings, caplog):
interactive=False,
)
assert notify_agents.call_count == 1
assert set(notify_agents.call_args[0][0]['audience']) == {'http://example.org', 'http://example.com'}
assert set(notify_agents.call_args[0][0]['audience']) == {
'http://more.example.net/metadata/',
'http://combo.example.net/metadata/',
}
# then with http provisionning, now used by default
del settings.KNOWN_SERVICES['wcs']['more']['provisionning-url']
del settings.KNOWN_SERVICES['combo']['combo']['provisionning-url']
settings.HOBO_HTTP_PROVISIONNING = True
with patch('hobo.agent.authentic2.provisionning.notify_agents') as notify_agents:
call_command(
@ -666,18 +720,14 @@ def test_provision_using_http(transactional_db, tenant, settings, caplog):
interactive=False,
)
assert notify_agents.call_count == 1
assert set(notify_agents.call_args[0][0]['audience']) == {'http://example.org', 'http://example.com'}
settings.KNOWN_SERVICES = {
'foo': {
'bar': {
'saml-sp-metadata-url': 'http://example.org',
'provisionning-url': 'http://example.org/__provision__/',
'orig': 'example.org',
'secret': 'xxx',
}
assert set(notify_agents.call_args[0][0]['audience']) == {
'http://more.example.net/metadata/',
'http://combo.example.net/metadata/',
}
}
settings.KNOWN_SERVICES['wcs']['more'][
'provisionning-url'
] = 'http:///more.example.net/__provisionning__/'
with patch('hobo.agent.authentic2.provisionning.notify_agents') as notify_agents:
with patch('hobo.agent.authentic2.provisionning.requests.put') as requests_put:
call_command(
@ -688,26 +738,23 @@ def test_provision_using_http(transactional_db, tenant, settings, caplog):
interactive=False,
)
assert notify_agents.call_count == 1
assert notify_agents.call_args[0][0]['audience'] == ['http://example.com']
assert notify_agents.call_args[0][0]['audience'] == ['http://combo.example.net/metadata/']
assert requests_put.call_count == 1
assert '&sync=1' not in requests_put.call_args[0][0]
# cannot check audience passed to requests.put as it's the same
# dictionary that is altered afterwards and would thus also contain
# http://example.com.
settings.KNOWN_SERVICES['foo']['bar2'] = {
'saml-sp-metadata-url': 'http://example.com',
'provisionning-url': 'http://example.com/__provision__/',
'orig': 'example.com',
'secret': 'xxx',
}
settings.KNOWN_SERVICES['combo']['combo'][
'provisionning-url'
] = 'http:///combo.example.net/__provisionning__/'
with patch('hobo.agent.authentic2.provisionning.notify_agents') as notify_agents:
with patch('hobo.agent.authentic2.provisionning.requests.put') as requests_put:
call_command(
'createsuperuser',
domain=tenant.domain_url,
username='coin2',
email='coin2@coin.org',
username='coin3',
email='coin3@coin.org',
interactive=False,
)
assert notify_agents.call_count == 0
@ -732,6 +779,13 @@ def test_provisionning_api(transactional_db, app_factory, tenant, settings, capl
entity_id='http://more.example.net/metadata/',
protocol_conformance=lasso.PROTOCOL_SAML_2_0,
)
LibertyProvider.objects.create(
ou=get_default_ou(),
name='provider3',
slug='provider3',
entity_id='http://combo.example.net/metadata/',
protocol_conformance=lasso.PROTOCOL_SAML_2_0,
)
role = Role.objects.create(name='coin', ou=get_default_ou())
user = User.objects.create(
@ -775,19 +829,19 @@ def test_provisionning_api(transactional_db, app_factory, tenant, settings, capl
assert '&sync=1' in requests_put.call_args[0][0]
assert not resp.json['leftover_audience']
assert set(resp.json['reached_audience']) == {
'http://other.example.net/metadata/',
'http://combo.example.net/metadata/',
'http://more.example.net/metadata/',
}
with patch('hobo.agent.authentic2.provisionning.requests.put') as requests_put:
resp = app.post_json(
signature.sign_url('/api/provision/?orig=%s' % orig, key),
{'user_uuid': user.uuid, 'service_type': 'welco'},
{'user_uuid': user.uuid, 'service_type': 'wcs'},
)
assert requests_put.call_count == 1
assert '&sync=1' in requests_put.call_args[0][0]
assert not resp.json['leftover_audience']
assert set(resp.json['reached_audience']) == {'http://other.example.net/metadata/'}
assert set(resp.json['reached_audience']) == {'http://more.example.net/metadata/'}
with patch('hobo.agent.authentic2.provisionning.requests.put') as requests_put:
resp = app.post_json(
@ -801,7 +855,7 @@ def test_provisionning_api(transactional_db, app_factory, tenant, settings, capl
resp = app.post_json(
signature.sign_url('/api/provision/?orig=%s' % orig, key), {'role_uuid': role.uuid}
)
assert requests_put.call_count == 2
assert requests_put.call_count == 3
assert resp.json['err'] == 0
assert not resp.json['leftover_audience']
@ -819,10 +873,11 @@ def test_provisionning_api(transactional_db, app_factory, tenant, settings, capl
resp = app.post_json(
signature.sign_url('/api/provision/?orig=%s' % orig, key), {'role_uuid': role.uuid}
)
assert requests_put.call_count == 2
assert requests_put.call_count == 3
assert resp.json['err'] == 0
assert not resp.json['leftover_audience']
assert set(resp.json['reached_audience']) == {
'http://combo.example.net/metadata/',
'http://other.example.net/metadata/',
'http://more.example.net/metadata/',
}
@ -850,8 +905,9 @@ def test_provision_debug(transactional_db, tenant, caplog, settings, tmpdir):
with tenant_context(tenant):
LibertyProvider.objects.create(
ou=get_default_ou(),
name='provider',
entity_id='http://provider.com',
name='wcs',
slug='wcs',
entity_id='http://more.example.net/metadata/',
protocol_conformance=lasso.PROTOCOL_SAML_2_0,
)
with provisionning: