759 lines
31 KiB
Python
759 lines
31 KiB
Python
import json
|
|
import os
|
|
import shutil
|
|
import tempfile
|
|
import time
|
|
from unittest import mock
|
|
|
|
import pytest
|
|
from authentic2.data_transfer import export_site
|
|
from django.core.management import call_command
|
|
from django.db import connection
|
|
from requests import RequestException
|
|
|
|
from hobo.multitenant.middleware import TenantMiddleware
|
|
|
|
os.sys.path.append('%s/tests' % os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
from utils import byteify
|
|
|
|
pytestmark = pytest.mark.django_db
|
|
|
|
|
|
@pytest.fixture
|
|
def skeleton_dir(request, settings):
|
|
settings.HOBO_SKELETONS_DIR = tempfile.mkdtemp('skeletons')
|
|
|
|
def fin():
|
|
shutil.rmtree(settings.HOBO_SKELETONS_DIR)
|
|
|
|
request.addfinalizer(fin)
|
|
return settings.HOBO_SKELETONS_DIR
|
|
|
|
|
|
def test_hobo_deploy(monkeypatch, tenant_base, mocker, skeleton_dir, tmp_path):
|
|
from django.conf import settings
|
|
from django.core.management import call_command
|
|
|
|
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'))
|
|
with open(os.path.join(skeleton_dir, 'commune', 'wcs', 'roles.json'), 'w') as roles_json:
|
|
json.dump(
|
|
[
|
|
{
|
|
'model': 'a2_rbac.role',
|
|
'fields': {
|
|
'name': 'Service petite enfance',
|
|
'slug': 'service-petite-enfance',
|
|
},
|
|
},
|
|
{
|
|
'model': 'a2_rbac.role',
|
|
'fields': {
|
|
'name': 'Service état-civil',
|
|
'slug': 'service-etat-civil',
|
|
},
|
|
},
|
|
],
|
|
roles_json,
|
|
)
|
|
|
|
requests_get = mocker.patch('requests.get')
|
|
meta1 = '''<?xml version="1.0"?>
|
|
<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
|
|
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
|
|
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
|
|
entityID="http://eservices.example.net/saml/metadata">
|
|
<SPSSODescriptor
|
|
AuthnRequestsSigned="true" WantAssertionsSigned="true"
|
|
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
|
|
<KeyDescriptor use="signing">
|
|
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
|
|
<KeyValue xmlns="http://www.w3.org/2000/09/xmldsig#">
|
|
<RSAKeyValue>
|
|
<Modulus>nJpkBznHNbvE+RAC6mU+NPQnIWs8gFNCm6I3FPcUKYpaJbXaurJ4cJgvnaEiqIXPQDcbHxuLeCbYbId9yascWZirvQbh8d/r+Vv+24bPG++9gW+i3Nnz1VW8V+z0b+puHWvM/FjJjBNJgWkI38gaupz47U6/02CtWx00stitiwk=</Modulus>
|
|
<Exponent>AQAB</Exponent>
|
|
</RSAKeyValue>
|
|
</KeyValue>
|
|
</ds:KeyInfo>
|
|
</KeyDescriptor>
|
|
<KeyDescriptor use="encryption">
|
|
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
|
|
<KeyValue xmlns="http://www.w3.org/2000/09/xmldsig#">
|
|
<RSAKeyValue>
|
|
<Modulus>3BxSiAzGvY1Yuqa31L7Zr2WHM/8cn5oX+Q6A2SYgzjuvAgnWyizN8YgW/fHR4G7MtkmZ5RFJLXfcSLwbUfpFHV6KO1ikbgViYuFempM+SWtjqEI7ribm9GaI5kUzHJZBrH3/Q9XAd9/GLLALxurGjbKDeLfc0D+7el26g4sYmA8=</Modulus>
|
|
<Exponent>AQAB</Exponent>
|
|
</RSAKeyValue>
|
|
</KeyValue>
|
|
</ds:KeyInfo>
|
|
</KeyDescriptor>
|
|
|
|
<SingleLogoutService
|
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
|
Location="http://eservices.example.net/saml/singleLogout"
|
|
ResponseLocation="http://eservices.example.net/saml/singleLogoutReturn" />
|
|
<SingleLogoutService
|
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
|
|
Location="http://eservices.example.net/saml/singleLogoutSOAP" />
|
|
<ManageNameIDService
|
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
|
Location="http://eservices.example.net/saml/manageNameId"
|
|
ResponseLocation="http://eservices.example.net/saml/manageNameIdReturn" />
|
|
<ManageNameIDService
|
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
|
|
Location="http://eservices.example.net/saml/manageNameIdSOAP" />
|
|
<AssertionConsumerService isDefault="true" index="0"
|
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"
|
|
Location="http://eservices.example.net/saml/assertionConsumerArtifact" />
|
|
<AssertionConsumerService index="1"
|
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
|
Location="http://eservices.example.net/saml/assertionConsumerPost" />
|
|
<AssertionConsumerService index="2"
|
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:PAOS"
|
|
Location="http://eservices.example.net/saml/assertionConsumerSOAP" />
|
|
<AssertionConsumerService index="3"
|
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
|
Location="http://eservices.example.net/saml/assertionConsumerRedirect" />
|
|
</SPSSODescriptor>
|
|
</EntityDescriptor>'''
|
|
meta2 = meta1.replace('eservices', 'passerelle')
|
|
meta3 = meta1.replace('eservices', 'clapiers')
|
|
meta4 = meta1.replace('eservices', 'portal')
|
|
metadatas = [meta1, meta2, meta3, meta4]
|
|
monkeypatch.setattr(HoboDeployCommand, 'backoff_factor', 0.0001)
|
|
side_effect_iter = iter([meta1, meta2, RequestException(), meta4, meta3])
|
|
|
|
def side_effect(*args, **kwargs):
|
|
for v in side_effect_iter:
|
|
if isinstance(v, Exception):
|
|
raise v
|
|
m = mock.Mock()
|
|
m.text = v
|
|
return m
|
|
|
|
requests_get.side_effect = side_effect
|
|
env = {
|
|
'users': [
|
|
{
|
|
'username': 'john.doe',
|
|
'first_name': 'John',
|
|
'last_name': 'Doe',
|
|
'email': 'john.doe@example.net',
|
|
'password': 'password',
|
|
},
|
|
],
|
|
'profile': {
|
|
'fields': [
|
|
{
|
|
'kind': 'title',
|
|
'description': '',
|
|
'required': False,
|
|
'user_visible': True,
|
|
'label': 'Civilité',
|
|
'disabled': True,
|
|
'user_editable': True,
|
|
'asked_on_registration': False,
|
|
'name': 'title',
|
|
},
|
|
{
|
|
'kind': 'string',
|
|
'description': '',
|
|
'required': True,
|
|
'user_visible': True,
|
|
'label': 'Prénom',
|
|
'disabled': False,
|
|
'user_editable': True,
|
|
'asked_on_registration': True,
|
|
'name': 'first_name',
|
|
},
|
|
{
|
|
'kind': 'string',
|
|
'description': '',
|
|
'required': True,
|
|
'user_visible': True,
|
|
'label': 'Nom',
|
|
'disabled': False,
|
|
'user_editable': True,
|
|
'asked_on_registration': True,
|
|
'name': 'last_name',
|
|
},
|
|
{
|
|
'kind': 'email',
|
|
'description': '',
|
|
'required': True,
|
|
'user_visible': True,
|
|
'label': 'Adresse électronique',
|
|
'disabled': False,
|
|
'user_editable': True,
|
|
'asked_on_registration': False,
|
|
'name': 'email',
|
|
},
|
|
{
|
|
'kind': 'string',
|
|
'description': '',
|
|
'required': False,
|
|
'user_visible': True,
|
|
'label': 'Addresse',
|
|
'disabled': False,
|
|
'user_editable': True,
|
|
'asked_on_registration': False,
|
|
'name': 'address',
|
|
},
|
|
{
|
|
'kind': 'string',
|
|
'description': '',
|
|
'required': False,
|
|
'user_visible': True,
|
|
'label': 'Code postal',
|
|
'disabled': False,
|
|
'user_editable': True,
|
|
'asked_on_registration': False,
|
|
'name': 'zipcode',
|
|
},
|
|
{
|
|
'kind': 'string',
|
|
'description': '',
|
|
'required': False,
|
|
'user_visible': True,
|
|
'label': 'Commune',
|
|
'disabled': False,
|
|
'user_editable': True,
|
|
'asked_on_registration': False,
|
|
'name': 'city',
|
|
},
|
|
{
|
|
'kind': 'phone_number',
|
|
'description': '',
|
|
'required': False,
|
|
'user_visible': True,
|
|
'label': 'Téléphone',
|
|
'disabled': False,
|
|
'user_editable': True,
|
|
'asked_on_registration': False,
|
|
'name': 'phone',
|
|
},
|
|
{
|
|
'kind': 'fr_phone_number',
|
|
'description': '',
|
|
'required': False,
|
|
'user_visible': True,
|
|
'label': 'Téléphone Français',
|
|
'disabled': False,
|
|
'user_editable': True,
|
|
'asked_on_registration': False,
|
|
'name': 'fr_phone',
|
|
},
|
|
{
|
|
'kind': 'string',
|
|
'description': '',
|
|
'required': False,
|
|
'user_visible': True,
|
|
'label': 'Mobile',
|
|
'disabled': False,
|
|
'user_editable': True,
|
|
'asked_on_registration': False,
|
|
'name': 'mobile',
|
|
},
|
|
{
|
|
'kind': 'string',
|
|
'description': '',
|
|
'required': False,
|
|
'user_visible': True,
|
|
'label': 'Pays',
|
|
'disabled': True,
|
|
'user_editable': True,
|
|
'asked_on_registration': False,
|
|
'name': 'country',
|
|
},
|
|
{
|
|
'kind': 'birthdate',
|
|
'description': '',
|
|
'required': False,
|
|
'user_visible': True,
|
|
'label': 'Date de naissance',
|
|
'disabled': True,
|
|
'user_editable': True,
|
|
'asked_on_registration': False,
|
|
'name': 'birthdate',
|
|
},
|
|
{
|
|
'kind': 'boolean',
|
|
'description': '',
|
|
'required': False,
|
|
'user_visible': True,
|
|
'label': 'CGU?',
|
|
'disabled': False,
|
|
'user_editable': False,
|
|
'asked_on_registration': True,
|
|
'required_on_login': True,
|
|
'name': 'cgu',
|
|
},
|
|
]
|
|
},
|
|
'variables': {
|
|
'hobo_test_variable': True,
|
|
'other_variable': 'foo',
|
|
},
|
|
'services': [
|
|
{
|
|
'service-id': 'authentic',
|
|
'slug': 'test',
|
|
'title': 'Test',
|
|
'this': True,
|
|
'secret_key': '12345',
|
|
'base_url': 'http://sso.example.net',
|
|
'variables': {
|
|
'other_variable': 'bar',
|
|
},
|
|
},
|
|
{
|
|
'service-id': 'wcs',
|
|
'template_name': 'commune',
|
|
'slug': 'montpellier-metropole',
|
|
'title': 'Montpellier-Métropole',
|
|
'base_url': 'http://eservices.example.net',
|
|
'saml-sp-metadata-url': 'http://eservices.example.net/saml/metadata',
|
|
},
|
|
{
|
|
'service-id': 'passerelle',
|
|
'slug': 'passerelle',
|
|
'title': 'Passerelle',
|
|
'base_url': 'http://passerelle.example.net',
|
|
'saml-sp-metadata-url': 'http://passerelle.example.net/saml/metadata',
|
|
},
|
|
{
|
|
'service-id': 'wcs',
|
|
'template_name': 'commune',
|
|
'slug': 'clapiers',
|
|
'title': 'Clapiers',
|
|
'secondary': True,
|
|
'base_url': 'http://clapiers.example.net',
|
|
'saml-sp-metadata-url': 'http://clapiers.example.net/saml/metadata',
|
|
'variables': {
|
|
'ou-label': 'OU label',
|
|
'ou-slug': 'ou-slug',
|
|
},
|
|
},
|
|
{
|
|
'service-id': 'combo',
|
|
'template_name': 'portal-user',
|
|
'slug': 'portal',
|
|
'title': 'Portail Montpellier-Métropole',
|
|
'base_url': 'http://portal.example.net',
|
|
'saml-sp-metadata-url': 'http://portal.example.net/saml/metadata',
|
|
},
|
|
],
|
|
}
|
|
|
|
def hobo_json():
|
|
with tempfile.NamedTemporaryFile(mode='w', dir=str(tmp_path), delete=False) as hobo_json:
|
|
hobo_json_content = json.dumps(env)
|
|
hobo_json.write(hobo_json_content)
|
|
return hobo_json.name
|
|
|
|
with mock.patch('hobo.agent.authentic2.provisionning.notify_agents') as mock_notify, mock.patch(
|
|
'hobo.agent.authentic2.management.commands.hobo_deploy.sleep', wraps=time.sleep
|
|
) as sleep_mock:
|
|
call_command('hobo_deploy', 'http://sso.example.net', hobo_json())
|
|
assert (
|
|
sleep_mock.call_count == 1
|
|
) # there is one retry, as the third service's metadata is temporarily unavailable
|
|
|
|
# check role mass provisionning to new services
|
|
# two wcs => two ous => two audiences
|
|
assert mock_notify.call_count == 2
|
|
audiences = sorted(arg[0][0]['audience'] for arg in mock_notify.call_args_list)
|
|
assert audiences == [
|
|
['http://clapiers.example.net/saml/metadata'],
|
|
[
|
|
'http://eservices.example.net/saml/metadata',
|
|
'http://passerelle.example.net/saml/metadata',
|
|
'http://portal.example.net/saml/metadata',
|
|
],
|
|
]
|
|
assert [arg[0][0]['@type'] for arg in mock_notify.call_args_list] == ['provision', 'provision']
|
|
assert [arg[0][0]['objects']['@type'] for arg in mock_notify.call_args_list] == ['role', 'role']
|
|
assert [arg[0][0]['full'] for arg in mock_notify.call_args_list] == [True, True]
|
|
|
|
from hobo.multitenant.middleware import TenantMiddleware
|
|
|
|
tenants = list(TenantMiddleware.get_tenants())
|
|
assert len(tenants) == 1
|
|
tenant = tenants[0]
|
|
assert tenant.domain_url == 'sso.example.net'
|
|
assert tenant.schema_name == 'sso_example_net'
|
|
tenant_directory = tenant.get_directory()
|
|
assert tenant_directory == os.path.join(tenant_base, tenant.domain_url)
|
|
assert os.path.exists(os.path.join(tenant_directory, 'saml.crt'))
|
|
assert os.path.exists(os.path.join(tenant_directory, 'saml.key'))
|
|
|
|
from tenant_schemas.utils import tenant_context
|
|
|
|
with tenant_context(tenant):
|
|
from authentic2.custom_user.models import User
|
|
|
|
assert User.objects.count() == 1
|
|
user = User.objects.get()
|
|
assert user.username == 'john.doe'
|
|
assert user.first_name == 'John'
|
|
assert user.last_name == 'Doe'
|
|
assert user.email == 'john.doe@example.net'
|
|
assert user.is_superuser is True
|
|
assert user.is_staff is True
|
|
from authentic2.models import Attribute
|
|
|
|
assert Attribute.all_objects.count() == 12
|
|
for field in env['profile']['fields']:
|
|
if field['name'] != 'email':
|
|
at = Attribute.all_objects.get(name=field['name'])
|
|
assert at.label == field['label']
|
|
assert at.description == field['description']
|
|
assert at.kind == field['kind']
|
|
if field['disabled']:
|
|
assert at.disabled is True
|
|
else:
|
|
assert at.disabled is False
|
|
assert at.asked_on_registration == field['asked_on_registration']
|
|
assert at.user_visible == field['user_visible']
|
|
assert at.user_editable == field['user_editable']
|
|
assert at.required_on_login == field.get('required_on_login', False)
|
|
for at in Attribute.all_objects.all():
|
|
assert [field for field in env['profile']['fields'] if field['name'] == at.name]
|
|
|
|
# OIDC checks
|
|
from authentic2_idp_oidc.utils import get_jwkset
|
|
|
|
assert get_jwkset()
|
|
|
|
# SAML checks
|
|
from authentic2.saml.models import LibertyProvider, LibertyServiceProvider, SPOptionsIdPPolicy
|
|
|
|
assert SPOptionsIdPPolicy.objects.count() == 1
|
|
policy = SPOptionsIdPPolicy.objects.get()
|
|
assert policy.name == 'Default'
|
|
assert policy.enabled is True
|
|
assert policy.authn_request_signed is False
|
|
assert policy.accepted_name_id_format == ['uuid']
|
|
assert policy.default_name_id_format == 'uuid'
|
|
assert policy.attributes.count() == 16
|
|
assert policy.attributes.filter(enabled=True).count() == 13
|
|
assert policy.attributes.filter(enabled=False).count() == 3
|
|
assert policy.attributes.filter(name_format='basic').count() == 16
|
|
assert (
|
|
policy.attributes.filter(name='is_superuser', attribute_name='django_user_is_superuser').count()
|
|
== 1
|
|
)
|
|
assert policy.attributes.filter(name='username', attribute_name='django_user_username').count() == 1
|
|
for field in env['profile']['fields']:
|
|
assert (
|
|
policy.attributes.filter(
|
|
name=field['name'], attribute_name='django_user_%s' % field['name']
|
|
).count()
|
|
== 1
|
|
)
|
|
assert LibertyProvider.objects.count() == 4
|
|
services = tenant.get_hobo_json()['services']
|
|
from authentic2.a2_rbac.models import Role
|
|
from authentic2.a2_rbac.utils import get_default_ou
|
|
|
|
other_services = [service for service in services if not service.get('this')]
|
|
for meta, service in zip(metadatas, other_services):
|
|
provider = LibertyProvider.objects.get(slug=service['slug'])
|
|
assert provider.metadata == meta
|
|
# Two services loaded roles.json, so there must be 5 roles associated
|
|
# to their "ou" excluding their superuser role, 3 admin roles for users,
|
|
# roles and services, and 2 loaded roles, petite enfance and état-civil
|
|
assert Role.objects.filter(ou__isnull=False, service__isnull=True).count() == 10
|
|
for service_id in {s['service-id'] for s in other_services}:
|
|
same_services = [s for s in other_services if s['service-id'] == service_id]
|
|
for i, service in enumerate(same_services):
|
|
assert LibertyProvider.objects.filter(slug=service['slug']).count() == 1
|
|
provider = LibertyProvider.objects.get(slug=service['slug'])
|
|
if service['slug'] == 'clapiers':
|
|
assert provider.name == 'Clapiers (OU label)'
|
|
else:
|
|
assert provider.name == service['title']
|
|
assert provider.federation_source == 'hobo'
|
|
assert provider.entity_id == service['saml-sp-metadata-url']
|
|
if service.get('template_name') == 'portal-user':
|
|
assert provider.ou.home_url == service['base_url']
|
|
assert LibertyServiceProvider.objects.filter(liberty_provider=provider).count() == 1
|
|
|
|
service_provider = LibertyServiceProvider.objects.get(liberty_provider=provider)
|
|
assert service_provider.enabled is True
|
|
assert service_provider.sp_options_policy == policy
|
|
assert service_provider.users_can_manage_federations is False
|
|
assert Role.objects.filter(slug='_a2-hobo-superuser', service=provider).count() == 1
|
|
su_role = Role.objects.get(slug='_a2-hobo-superuser', service=provider)
|
|
assert su_role.attributes.count() == 1
|
|
assert (
|
|
su_role.attributes.filter(name='is_superuser', kind='string', value='true').count() == 1
|
|
)
|
|
if i == 0 or service_id != 'wcs':
|
|
assert provider.ou == get_default_ou()
|
|
else:
|
|
assert (
|
|
provider.ou.name == service['variables']['ou-label']
|
|
if 'variables' in service
|
|
else service['title']
|
|
)
|
|
assert su_role.ou == provider.ou
|
|
assert provider.attributes.count() == 2
|
|
assert (
|
|
provider.attributes.filter(
|
|
name='is_superuser', name_format='basic', attribute_name='is_superuser'
|
|
).count()
|
|
== 1
|
|
)
|
|
assert (
|
|
provider.attributes.filter(
|
|
name='role-slug', name_format='basic', attribute_name='a2_service_ou_role_uuids'
|
|
).count()
|
|
== 1
|
|
)
|
|
if service.get('template_name'):
|
|
assert Role.objects.filter(ou=provider.ou, service__isnull=True).count() == 5
|
|
assert (
|
|
Role.objects.filter(
|
|
ou=provider.ou, service__isnull=True, name='Service petite enfance'
|
|
).count()
|
|
== 1
|
|
)
|
|
assert (
|
|
Role.objects.filter(
|
|
ou=provider.ou, service__isnull=True, name='Service état-civil'
|
|
).count()
|
|
== 1
|
|
)
|
|
|
|
with mock.patch('hobo.agent.authentic2.provisionning.notify_agents') as mock_notify, mock.patch(
|
|
'hobo.agent.authentic2.management.commands.hobo_deploy.sleep', wraps=time.sleep
|
|
) as sleep_mock:
|
|
call_command('hobo_deploy', redeploy=True)
|
|
assert sleep_mock.call_count == 0
|
|
|
|
# test attribute kind update
|
|
with tenant_context(tenant):
|
|
assert Attribute.objects.filter(name='mobile', kind='string').count() == 1
|
|
field = env['profile']['fields'][9]
|
|
assert field['name'] == 'mobile'
|
|
field['kind'] = 'phone_number'
|
|
side_effect_iter = iter([meta1, meta2, RequestException(), meta4, meta3])
|
|
with mock.patch('hobo.agent.authentic2.provisionning.notify_agents') as mock_notify, mock.patch(
|
|
'hobo.agent.authentic2.management.commands.hobo_deploy.sleep', wraps=time.sleep
|
|
) as sleep_mock:
|
|
call_command('hobo_deploy', 'http://sso.example.net', hobo_json(), ignore_timestamp=True)
|
|
with tenant_context(tenant):
|
|
assert Attribute.objects.filter(name='mobile', kind='string').count() == 0
|
|
assert Attribute.objects.filter(name='mobile', kind='phone_number').count() == 1
|
|
|
|
|
|
def test_import_template(db, tenant_base):
|
|
def listify(value):
|
|
if isinstance(value, dict):
|
|
value = list((k, listify(v) or []) for k, v in value.items())
|
|
value.sort()
|
|
if isinstance(value, list):
|
|
value = list(listify(x) for x in value)
|
|
value.sort()
|
|
return value
|
|
|
|
def sort_and_remove_uuid(value):
|
|
if isinstance(value, dict):
|
|
if 'uuid' in value:
|
|
value.pop('uuid')
|
|
value = {k: sort_and_remove_uuid(v) for k, v in value.items()}
|
|
if isinstance(value, list):
|
|
value = [sort_and_remove_uuid(elt) for elt in value]
|
|
value.sort(key=lambda elt: listify(elt))
|
|
return value
|
|
|
|
call_command('create_tenant', 'authentic.example.net')
|
|
tenant = TenantMiddleware.get_tenant_by_hostname('authentic.example.net')
|
|
connection.set_tenant(tenant)
|
|
call_command('import_template', '--basepath=%s' % os.path.dirname(__file__), 'data_authentic_export_site')
|
|
content = open('%s/data_authentic_export_site.json' % os.path.dirname(__file__)).read()
|
|
|
|
export_ref = sort_and_remove_uuid(export_site())
|
|
file_ref = sort_and_remove_uuid(json.loads(content))
|
|
assert export_ref == file_ref
|
|
|
|
|
|
def test_hobo_deploy_with_legacy_urls(monkeypatch, tenant_base, mocker, skeleton_dir, tmp_path):
|
|
from django.core.management import call_command
|
|
|
|
from hobo.agent.authentic2.management.commands.hobo_deploy import Command as HoboDeployCommand
|
|
|
|
requests_get = mocker.patch('requests.get')
|
|
meta1 = '''<?xml version="1.0"?>
|
|
<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
|
|
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
|
|
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
|
|
entityID="http://passerelle.example.net/saml/metadata">
|
|
<SPSSODescriptor
|
|
AuthnRequestsSigned="true" WantAssertionsSigned="true"
|
|
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
|
|
<KeyDescriptor use="signing">
|
|
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
|
|
<KeyValue xmlns="http://www.w3.org/2000/09/xmldsig#">
|
|
<RSAKeyValue>
|
|
<Modulus>nJpkBznHNbvE+RAC6mU+NPQnIWs8gFNCm6I3FPcUKYpaJbXaurJ4cJgvnaEiqIXPQDcbHxuLeCbYbId9yascWZirvQbh8d/r+Vv+24bPG++9gW+i3Nnz1VW8V+z0b+puHWvM/FjJjBNJgWkI38gaupz47U6/02CtWx00stitiwk=</Modulus>
|
|
<Exponent>AQAB</Exponent>
|
|
</RSAKeyValue>
|
|
</KeyValue>
|
|
</ds:KeyInfo>
|
|
</KeyDescriptor>
|
|
<KeyDescriptor use="encryption">
|
|
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
|
|
<KeyValue xmlns="http://www.w3.org/2000/09/xmldsig#">
|
|
<RSAKeyValue>
|
|
<Modulus>3BxSiAzGvY1Yuqa31L7Zr2WHM/8cn5oX+Q6A2SYgzjuvAgnWyizN8YgW/fHR4G7MtkmZ5RFJLXfcSLwbUfpFHV6KO1ikbgViYuFempM+SWtjqEI7ribm9GaI5kUzHJZBrH3/Q9XAd9/GLLALxurGjbKDeLfc0D+7el26g4sYmA8=</Modulus>
|
|
<Exponent>AQAB</Exponent>
|
|
</RSAKeyValue>
|
|
</KeyValue>
|
|
</ds:KeyInfo>
|
|
</KeyDescriptor>
|
|
|
|
<SingleLogoutService
|
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
|
Location="http://passerelle.example.net/saml/singleLogout"
|
|
ResponseLocation="http://passerelle.example.net/saml/singleLogoutReturn" />
|
|
<SingleLogoutService
|
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
|
|
Location="http://passerelle.example.net/saml/singleLogoutSOAP" />
|
|
<ManageNameIDService
|
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
|
Location="http://passerelle.example.net/saml/manageNameId"
|
|
ResponseLocation="http://passerelle.example.net/saml/manageNameIdReturn" />
|
|
<ManageNameIDService
|
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
|
|
Location="http://passerelle.example.net/saml/manageNameIdSOAP" />
|
|
<AssertionConsumerService isDefault="true" index="0"
|
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"
|
|
Location="http://passerelle.example.net/saml/assertionConsumerArtifact" />
|
|
<AssertionConsumerService index="1"
|
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
|
Location="http://passerelle.example.net/saml/assertionConsumerPost" />
|
|
<AssertionConsumerService index="2"
|
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:PAOS"
|
|
Location="http://passerelle.example.net/saml/assertionConsumerSOAP" />
|
|
<AssertionConsumerService index="3"
|
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
|
Location="http://passerelle.example.net/saml/assertionConsumerRedirect" />
|
|
</SPSSODescriptor>
|
|
</EntityDescriptor>'''
|
|
meta2 = meta1.replace('passerelle.example.net', 'new-passerelle.example.net')
|
|
monkeypatch.setattr(HoboDeployCommand, 'backoff_factor', 0.0001)
|
|
|
|
side_effect_iter = iter([meta1, meta2])
|
|
|
|
def side_effect(*args, **kwargs):
|
|
for v in side_effect_iter:
|
|
m = mock.Mock()
|
|
m.text = v
|
|
return m
|
|
|
|
requests_get.side_effect = side_effect
|
|
|
|
def hobo_json(env_dict):
|
|
with tempfile.NamedTemporaryFile(mode='w', dir=str(tmp_path), delete=False) as hobo_json:
|
|
hobo_json_content = json.dumps(env_dict)
|
|
hobo_json.write(hobo_json_content)
|
|
return hobo_json.name
|
|
|
|
env = {
|
|
'services': [
|
|
{
|
|
'service-id': 'authentic',
|
|
'slug': 'test',
|
|
'title': 'Test',
|
|
'this': True,
|
|
'secret_key': '12345',
|
|
'base_url': 'http://sso.example.net',
|
|
'variables': {
|
|
'other_variable': 'bar',
|
|
},
|
|
},
|
|
{
|
|
'service-id': 'passerelle',
|
|
'slug': 'passerelle',
|
|
'title': 'Passerelle',
|
|
'base_url': 'http://passerelle.example.net',
|
|
'saml-sp-metadata-url': 'http://passerelle.example.net/saml/metadata',
|
|
},
|
|
],
|
|
'users': [],
|
|
'profile': {'fields': []},
|
|
}
|
|
|
|
with mock.patch('hobo.agent.authentic2.provisionning.notify_agents'):
|
|
call_command('hobo_deploy', 'http://sso.example.net', hobo_json(env))
|
|
|
|
from hobo.multitenant.middleware import TenantMiddleware
|
|
|
|
tenants = list(TenantMiddleware.get_tenants())
|
|
assert len(tenants) == 1
|
|
tenant = tenants[0]
|
|
assert tenant.domain_url == 'sso.example.net'
|
|
assert tenant.schema_name == 'sso_example_net'
|
|
tenant_directory = tenant.get_directory()
|
|
assert tenant_directory == os.path.join(tenant_base, tenant.domain_url)
|
|
assert os.path.exists(os.path.join(tenant_directory, 'saml.crt'))
|
|
assert os.path.exists(os.path.join(tenant_directory, 'saml.key'))
|
|
|
|
from tenant_schemas.utils import tenant_context
|
|
|
|
with tenant_context(tenant):
|
|
# SAML checks
|
|
from authentic2.saml.models import LibertyProvider
|
|
|
|
assert LibertyProvider.objects.count() == 1
|
|
provider = LibertyProvider.objects.first()
|
|
provider_id = provider.pk
|
|
assert provider.entity_id == 'http://passerelle.example.net/saml/metadata'
|
|
assert provider.metadata == meta1
|
|
|
|
new_env = {
|
|
'services': [
|
|
{
|
|
'service-id': 'authentic',
|
|
'slug': 'test',
|
|
'title': 'Test',
|
|
'this': True,
|
|
'secret_key': '12345',
|
|
'base_url': 'http://sso.example.net',
|
|
'variables': {
|
|
'other_variable': 'bar',
|
|
},
|
|
},
|
|
{
|
|
'service-id': 'passerelle',
|
|
'slug': 'passerelle',
|
|
'title': 'Passerelle',
|
|
'base_url': 'http://new-passerelle.example.net',
|
|
'saml-sp-metadata-url': 'http://new-passerelle.example.net/saml/metadata',
|
|
'legacy_urls': [
|
|
{
|
|
'base_url': 'http://passerelle.example.net',
|
|
'saml-sp-metadata-url': 'http://passerelle.example.net/saml/metadata',
|
|
}
|
|
],
|
|
},
|
|
],
|
|
'users': [],
|
|
'profile': {'fields': []},
|
|
}
|
|
|
|
with mock.patch('hobo.agent.authentic2.provisionning.notify_agents'):
|
|
call_command('hobo_deploy', '--ignore-timestamp', 'http://sso.example.net', hobo_json(new_env))
|
|
# check that liberty provider is updated
|
|
with tenant_context(tenant):
|
|
from authentic2.saml.models import LibertyProvider
|
|
|
|
assert LibertyProvider.objects.count() == 1
|
|
provider = LibertyProvider.objects.first()
|
|
assert provider.metadata == meta2
|
|
assert provider.entity_id == 'http://new-passerelle.example.net/saml/metadata'
|
|
assert provider.pk == provider_id
|