agent/authentic2: retry service's metadata retrieval (#35351)
This commit is contained in:
parent
2ad03f8140
commit
0f0043ca53
|
@ -1,6 +1,8 @@
|
|||
import requests
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
from authentic2 import app_settings
|
||||
from authentic2.compat_lasso import lasso
|
||||
|
@ -27,6 +29,8 @@ User = get_user_model()
|
|||
class Command(hobo_deploy.Command):
|
||||
help = 'Deploy multitenant authentic service from hobo'
|
||||
|
||||
backoff_factor = 5
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.logger = logging.getLogger(__name__)
|
||||
super(Command, self).__init__(*args, **kwargs)
|
||||
|
@ -117,93 +121,123 @@ class Command(hobo_deploy.Command):
|
|||
|
||||
# create or update Service Providers
|
||||
services = hobo_environment['services']
|
||||
for service in services:
|
||||
if not service.get('saml-sp-metadata-url'):
|
||||
continue
|
||||
sp_url = service['saml-sp-metadata-url']
|
||||
try:
|
||||
metadata_response = requests.get(
|
||||
sp_url, verify=app_settings.A2_VERIFY_SSL, timeout=5)
|
||||
metadata_response.raise_for_status()
|
||||
except requests.exceptions.RequestException as e:
|
||||
self.stderr.write(self.style.WARNING(
|
||||
'Error registering %s: %r\n' % (sp_url, e)))
|
||||
continue
|
||||
metadata_text = metadata_response.text
|
||||
retries = 0
|
||||
loaded = 0
|
||||
max_retries = 1 if self.redeploy else 5
|
||||
while retries < max_retries:
|
||||
for service in services:
|
||||
if service.get('$done'):
|
||||
continue
|
||||
if not service.get('saml-sp-metadata-url'):
|
||||
service['$done'] = True
|
||||
loaded += 1
|
||||
continue
|
||||
sp_url = service['saml-sp-metadata-url']
|
||||
metadata_text = None
|
||||
try:
|
||||
metadata_response = requests.get(
|
||||
sp_url, verify=app_settings.A2_VERIFY_SSL, timeout=5)
|
||||
metadata_response.raise_for_status()
|
||||
# verify metadata is correct
|
||||
if self.check_saml_metadata(metadata_response.text):
|
||||
metadata_text = metadata_response.text
|
||||
else:
|
||||
service['$last-error'] = 'metadata is incorrect'
|
||||
continue
|
||||
except requests.exceptions.RequestException as e:
|
||||
service['$last-error'] = str(e)
|
||||
continue
|
||||
metadata_text = metadata_response.text
|
||||
|
||||
provider, service_created = \
|
||||
LibertyProvider.objects.get_or_create(
|
||||
entity_id=sp_url,
|
||||
protocol_conformance=lasso.PROTOCOL_SAML_2_0)
|
||||
provider.name = service['title']
|
||||
provider.slug = service['slug']
|
||||
provider.federation_source = 'hobo'
|
||||
provider.metadata = metadata_text
|
||||
provider.metadata_url = service['saml-sp-metadata-url']
|
||||
if service.get('variables', {}).get('ou-slug'):
|
||||
ou, created = get_ou_model().objects.get_or_create(
|
||||
slug=service['variables']['ou-slug'])
|
||||
ou.name = service['variables']['ou-label']
|
||||
ou.save()
|
||||
else:
|
||||
# if there are more than one w.c.s. service we will create an
|
||||
# ou of the same name
|
||||
ou = get_default_ou()
|
||||
create_ou = False
|
||||
if service_created and service['service-id'] == 'wcs':
|
||||
for s in services:
|
||||
if s['service-id'] != 'wcs':
|
||||
continue
|
||||
if s['slug'] == service['slug']:
|
||||
continue
|
||||
if LibertyProvider.objects.filter(
|
||||
slug=s['slug']).exists():
|
||||
create_ou = True
|
||||
break
|
||||
if create_ou:
|
||||
provider, service_created = \
|
||||
LibertyProvider.objects.get_or_create(
|
||||
entity_id=sp_url,
|
||||
protocol_conformance=lasso.PROTOCOL_SAML_2_0)
|
||||
provider.name = service['title']
|
||||
provider.slug = service['slug']
|
||||
provider.federation_source = 'hobo'
|
||||
provider.metadata = metadata_text
|
||||
provider.metadata_url = service['saml-sp-metadata-url']
|
||||
if service.get('variables', {}).get('ou-slug'):
|
||||
ou, created = get_ou_model().objects.get_or_create(
|
||||
name=service['title'])
|
||||
if service_created or not provider.ou:
|
||||
provider.ou = ou
|
||||
provider.save()
|
||||
if service_created:
|
||||
service_provider = LibertyServiceProvider(
|
||||
enabled=True, liberty_provider=provider,
|
||||
sp_options_policy=policy,
|
||||
users_can_manage_federations=False)
|
||||
service_provider.save()
|
||||
slug=service['variables']['ou-slug'])
|
||||
ou.name = service['variables']['ou-label']
|
||||
ou.save()
|
||||
else:
|
||||
# if there are more than one w.c.s. service we will create an
|
||||
# ou of the same name
|
||||
ou = get_default_ou()
|
||||
create_ou = False
|
||||
if service_created and service['service-id'] == 'wcs':
|
||||
for s in services:
|
||||
if s['service-id'] != 'wcs':
|
||||
continue
|
||||
if s['slug'] == service['slug']:
|
||||
continue
|
||||
if LibertyProvider.objects.filter(
|
||||
slug=s['slug']).exists():
|
||||
create_ou = True
|
||||
break
|
||||
if create_ou:
|
||||
ou, created = get_ou_model().objects.get_or_create(
|
||||
name=service['title'])
|
||||
if service_created or not provider.ou:
|
||||
provider.ou = ou
|
||||
provider.save()
|
||||
if service_created:
|
||||
service_provider = LibertyServiceProvider(
|
||||
enabled=True, liberty_provider=provider,
|
||||
sp_options_policy=policy,
|
||||
users_can_manage_federations=False)
|
||||
service_provider.save()
|
||||
|
||||
# add a superuser role for the service
|
||||
Role = get_role_model()
|
||||
name = _('Superuser of %s') % service['title']
|
||||
su_role, created = Role.objects.get_or_create(
|
||||
service=provider, slug='_a2-hobo-superuser',
|
||||
defaults={'name': name})
|
||||
if su_role.name != name:
|
||||
su_role.name = name
|
||||
su_role.save()
|
||||
su_role.attributes.get_or_create(name='is_superuser',
|
||||
kind='string',
|
||||
value='true')
|
||||
# pass the new attribute to the service
|
||||
SAMLAttribute.objects.get_or_create(
|
||||
name='is_superuser',
|
||||
name_format='basic',
|
||||
attribute_name='is_superuser',
|
||||
object_id=provider.pk,
|
||||
content_type=provider_type)
|
||||
SAMLAttribute.objects.get_or_create(
|
||||
name='role-slug',
|
||||
name_format='basic',
|
||||
attribute_name='a2_service_ou_role_uuids',
|
||||
object_id=provider.pk,
|
||||
content_type=provider_type)
|
||||
# load skeleton if service is new
|
||||
if service.get('template_name'):
|
||||
# if there are more of the same servie, we will create an
|
||||
# ou
|
||||
self.load_skeleton(provider, service['service-id'],
|
||||
service['template_name'])
|
||||
# add a superuser role for the service
|
||||
Role = get_role_model()
|
||||
name = _('Superuser of %s') % service['title']
|
||||
su_role, created = Role.objects.get_or_create(
|
||||
service=provider, slug='_a2-hobo-superuser',
|
||||
defaults={'name': name})
|
||||
if su_role.name != name:
|
||||
su_role.name = name
|
||||
su_role.save()
|
||||
su_role.attributes.get_or_create(name='is_superuser',
|
||||
kind='string',
|
||||
value='true')
|
||||
# pass the new attribute to the service
|
||||
SAMLAttribute.objects.get_or_create(
|
||||
name='is_superuser',
|
||||
name_format='basic',
|
||||
attribute_name='is_superuser',
|
||||
object_id=provider.pk,
|
||||
content_type=provider_type)
|
||||
SAMLAttribute.objects.get_or_create(
|
||||
name='role-slug',
|
||||
name_format='basic',
|
||||
attribute_name='a2_service_ou_role_uuids',
|
||||
object_id=provider.pk,
|
||||
content_type=provider_type)
|
||||
# load skeleton if service is new
|
||||
if service.get('template_name'):
|
||||
# if there are more of the same servie, we will create an
|
||||
# ou
|
||||
self.load_skeleton(provider, service['service-id'],
|
||||
service['template_name'])
|
||||
service['$done'] = True
|
||||
loaded += 1
|
||||
|
||||
if len(services) == loaded:
|
||||
# it's finished no need to continue
|
||||
break
|
||||
|
||||
# wait 5, 10, 20, 40, .. seconds
|
||||
time.sleep(self.backoff_factor * (2 ** retries))
|
||||
retries += 1
|
||||
|
||||
for service in services:
|
||||
if not service.get('$done'):
|
||||
last_error = service['$last-error']
|
||||
sp_url = service['saml-sp-metadata-url']
|
||||
self.stderr.write(self.style.WARNING('Error registering %s: %s\n' % (sp_url, last_error)))
|
||||
|
||||
def load_skeleton(self, provider, service_id, template_name,
|
||||
create_ou=False):
|
||||
|
@ -242,3 +276,10 @@ class Command(hobo_deploy.Command):
|
|||
if roles:
|
||||
Role.objects.bulk_create(roles)
|
||||
Role.objects.get(uuid=roles[-1].uuid).save()
|
||||
|
||||
def check_saml_metadata(self, saml_metadata):
|
||||
try:
|
||||
root = ET.fromstring(saml_metadata)
|
||||
except ET.ParseError:
|
||||
return False
|
||||
return root.tag == '{%s}EntityDescriptor' % lasso.SAML2_METADATA_HREF
|
||||
|
|
|
@ -44,6 +44,7 @@ class Command(BaseCommand):
|
|||
|
||||
def handle(self, base_url=None, json_filename=None, ignore_timestamp=None,
|
||||
redeploy=None, *args, **kwargs):
|
||||
self.redeploy = redeploy
|
||||
if redeploy:
|
||||
for tenant in TenantMiddleware.get_tenants():
|
||||
try:
|
||||
|
|
|
@ -6,6 +6,8 @@ import shutil
|
|||
import json
|
||||
import mock
|
||||
|
||||
from requests import RequestException
|
||||
|
||||
from django.core.management import call_command
|
||||
from django.db import connection
|
||||
from hobo.multitenant.middleware import TenantMiddleware
|
||||
|
@ -26,9 +28,10 @@ def skeleton_dir(request, settings):
|
|||
return settings.HOBO_SKELETONS_DIR
|
||||
|
||||
|
||||
def test_hobo_deploy(tenant_base, mocker, skeleton_dir):
|
||||
def test_hobo_deploy(monkeypatch, tenant_base, mocker, skeleton_dir):
|
||||
from django.core.management import call_command
|
||||
from django.conf import settings
|
||||
from hobo.agent.authentic2.management.commands.hobo_deploy import Command as HoboDeployCommand
|
||||
|
||||
# Create skeleton roles.json
|
||||
os.makedirs(os.path.join(skeleton_dir, 'commune', 'wcs'))
|
||||
|
@ -117,11 +120,16 @@ def test_hobo_deploy(tenant_base, mocker, skeleton_dir):
|
|||
meta2 = meta1.replace('eservices', 'passerelle')
|
||||
meta3 = meta1.replace('eservices', 'clapiers')
|
||||
metadatas = [meta1, meta2, meta3]
|
||||
side_effect = []
|
||||
for meta in metadatas:
|
||||
monkeypatch.setattr(HoboDeployCommand, 'backoff_factor', 0.0001)
|
||||
side_effect_iter = iter([meta1, meta2, RequestException(), meta3])
|
||||
|
||||
def side_effect(*args, **kwargs):
|
||||
v = next(side_effect_iter)
|
||||
if isinstance(v, Exception):
|
||||
raise v
|
||||
m = mock.Mock()
|
||||
m.text = meta
|
||||
side_effect.append(m)
|
||||
m.text = v
|
||||
return m
|
||||
requests_get.side_effect = side_effect
|
||||
env = {
|
||||
'users': [
|
||||
|
|
Loading…
Reference in New Issue