From 96ce1fb02f199e7be2e9b028fa6c79106a514cff Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Tue, 14 Sep 2021 07:00:19 +0200 Subject: [PATCH] provisionning: add ?sync=1 parameter to /__provision__ API (#56920) When used by the /api/provision API on authentic, it garantees the provisionning is made synchronously. --- hobo/agent/authentic2/provisionning.py | 30 +++++++++++++++----------- hobo/agent/authentic2/views.py | 8 +++---- hobo/provisionning/middleware.py | 4 ++-- tests_authentic/test_provisionning.py | 5 +++++ 4 files changed, 28 insertions(+), 19 deletions(-) diff --git a/hobo/agent/authentic2/provisionning.py b/hobo/agent/authentic2/provisionning.py index 8f37e30..6f11cc0 100644 --- a/hobo/agent/authentic2/provisionning.py +++ b/hobo/agent/authentic2/provisionning.py @@ -122,7 +122,7 @@ class Provisionning(threading.local): if instance.ou_id in ous: instance.ou = ous[instance.ou_id] - def notify_users(self, ous, users, mode='provision'): + def notify_users(self, ous, users, mode='provision', sync=False): allowed_technical_roles_prefixes = getattr(settings, 'HOBO_PROVISION_ROLE_PREFIXES', []) or [] if mode == 'provision': @@ -240,7 +240,8 @@ class Provisionning(threading.local): for user in batched_users ], }, - } + }, + sync=sync, ) else: for ou, users in ous.items(): @@ -262,7 +263,8 @@ class Provisionning(threading.local): '@type': 'user', 'data': [user_to_json(ou, None, user, user_roles) for user in users], }, - } + }, + sync=sync, ) elif users: audience = [audience for ou in ous.keys() for s, audience in self.get_audience(ou)] @@ -284,10 +286,11 @@ class Provisionning(threading.local): for user in users ], }, - } + }, + sync=sync, ) - def notify_roles(self, ous, roles, mode='provision', full=False): + def notify_roles(self, ous, roles, mode='provision', full=False, sync=False): allowed_technical_roles_prefixes = getattr(settings, 'HOBO_PROVISION_ROLE_PREFIXES', []) or [] def is_forbidden_technical_role(role): @@ -340,7 +343,8 @@ class Provisionning(threading.local): '@type': 'role', 'data': data, }, - } + }, + sync=sync, ) global_roles = set(ous.get(None, [])) @@ -486,7 +490,7 @@ class Provisionning(threading.local): for other_instance in instance.members.all(): self.add_saved(other_instance) - def notify_agents(self, data): + def notify_agents(self, data, sync=False): log_path = getattr(settings, 'DEBUG_PROVISIONNING_LOG_PATH', '') if log_path and getattr(settings, 'HOBO_PROVISIONNING_DEBUG', False): try: @@ -498,7 +502,7 @@ class Provisionning(threading.local): pass if getattr(settings, 'HOBO_HTTP_PROVISIONNING', False): - leftover_audience = self.notify_agents_http(data) + leftover_audience = self.notify_agents_http(data, sync=sync) if not leftover_audience: return logger.info('leftover AMQP audience: %s', leftover_audience) @@ -515,7 +519,7 @@ class Provisionning(threading.local): services_by_url[service['saml-sp-metadata-url']] = service return services_by_url - def notify_agents_http(self, data): + def notify_agents_http(self, data, sync=False): services_by_url = self.get_http_services_by_url() audience = data.get('audience') rest_audience = [x for x in audience if x in services_by_url] @@ -523,11 +527,11 @@ class Provisionning(threading.local): for audience in rest_audience: service = services_by_url[audience] data['audience'] = [audience] + url = service['provisionning-url'] + '?orig=%s' % service['orig'] + if sync: + url += '&sync=1' try: - response = requests.put( - sign_url(service['provisionning-url'] + '?orig=%s' % service['orig'], service['secret']), - json=data, - ) + response = requests.put(sign_url(url, service['secret']), json=data) response.raise_for_status() except requests.RequestException as e: logger.error(u'error provisionning to %s (%s)', audience, e) diff --git a/hobo/agent/authentic2/views.py b/hobo/agent/authentic2/views.py index 2530b89..c8ec9cc 100644 --- a/hobo/agent/authentic2/views.py +++ b/hobo/agent/authentic2/views.py @@ -57,14 +57,14 @@ class ProvisionView(GenericAPIView): user = provisionning.User.objects.get(uuid=user_uuid) except provisionning.User.DoesNotExist: return Response({'err': 1, 'err_desc': 'unknown user UUID'}) - engine.notify_users(ous=None, users=[user]) + engine.notify_users(ous=None, users=[user], sync=True) elif role_uuid: try: role = provisionning.Role.objects.get(uuid=role_uuid) except provisionning.Role.DoesNotExist: return Response({'err': 1, 'err_desc': 'unknown role UUID'}) ous = {ou.id: ou for ou in provisionning.OU.objects.all()} - engine.notify_roles(ous=ous, roles=[role]) + engine.notify_roles(ous=ous, roles=[role], sync=True) response = { 'err': 0, @@ -97,8 +97,8 @@ class ApiProvisionningEngine(provisionning.Provisionning): services_by_url = {k: v for k, v in services_by_url.items() if self.service_url in v['url']} return services_by_url - def notify_agents(self, data): - self.leftover_audience = self.notify_agents_http(data) + def notify_agents(self, data, sync=False): + 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] diff --git a/hobo/provisionning/middleware.py b/hobo/provisionning/middleware.py index d315efa..70c020a 100644 --- a/hobo/provisionning/middleware.py +++ b/hobo/provisionning/middleware.py @@ -24,7 +24,7 @@ from django.utils.deprecation import MiddlewareMixin from django.utils.encoding import force_bytes, force_text from django.utils.six.moves.urllib.parse import urlparse -from hobo.provisionning.utils import NotificationProcessing, TryAgain +from hobo.provisionning.utils import NotificationProcessing from hobo.rest_authentication import PublikAuthentication, PublikAuthenticationFailed @@ -54,7 +54,7 @@ class ProvisionningMiddleware(MiddlewareMixin, NotificationProcessing): full = notification['full'] if 'full' in notification else False data = notification['objects']['data'] - if 'uwsgi' in sys.modules: + if 'uwsgi' in sys.modules and 'sync' not in request.GET: from hobo.provisionning.spooler import provision tenant = getattr(connection, 'tenant', None) diff --git a/tests_authentic/test_provisionning.py b/tests_authentic/test_provisionning.py index 4c2770e..e0f32d5 100644 --- a/tests_authentic/test_provisionning.py +++ b/tests_authentic/test_provisionning.py @@ -623,6 +623,7 @@ def test_provision_using_http(transactional_db, tenant, settings, caplog): assert notify_agents.call_count == 1 assert notify_agents.call_args[0][0]['audience'] == ['http://example.com'] 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. @@ -644,6 +645,7 @@ def test_provision_using_http(transactional_db, tenant, settings, caplog): ) assert notify_agents.call_count == 0 assert requests_put.call_count == 2 + assert '&sync=1' not in requests_put.call_args[0][0] def test_provisionning_api(transactional_db, app_factory, tenant, settings, caplog): @@ -703,6 +705,7 @@ def test_provisionning_api(transactional_db, app_factory, tenant, settings, capl signature.sign_url('/api/provision/?orig=%s' % orig, key), {'user_uuid': user.uuid} ) assert requests_put.call_count == 2 + 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/', @@ -715,6 +718,7 @@ def test_provisionning_api(transactional_db, app_factory, tenant, settings, capl {'user_uuid': user.uuid, 'service_type': 'welco'}, ) 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/'} @@ -724,6 +728,7 @@ def test_provisionning_api(transactional_db, app_factory, tenant, settings, capl {'user_uuid': user.uuid, 'service_url': 'example.net'}, ) assert requests_put.call_count == 2 + assert '&sync=1' in requests_put.call_args[0][0] with patch('hobo.agent.authentic2.provisionning.requests.put') as requests_put: resp = app.post_json( -- 2.33.0