439 lines
18 KiB
Python
439 lines
18 KiB
Python
# -*- coding: utf-8 -*-
|
|
import os
|
|
import tempfile
|
|
import pytest
|
|
import shutil
|
|
import json
|
|
import mock
|
|
|
|
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(tenant_base, mocker, skeleton_dir):
|
|
from django.core.management import call_command
|
|
from django.conf import settings
|
|
|
|
# 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': u'Service petite enfance',
|
|
'slug': u'service-petite-enfance',
|
|
},
|
|
},
|
|
{
|
|
'model': 'a2_rbac.role',
|
|
'fields': {
|
|
'name': u'Service état-civil',
|
|
'slug': u'service-etat-civil',
|
|
},
|
|
},
|
|
], roles_json)
|
|
|
|
# As a user is created, notify_agents is called, as celery is not running
|
|
# we just block it
|
|
mocker.patch('hobo.agent.authentic2.provisionning.notify_agents')
|
|
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')
|
|
metadatas = [meta1, meta2, meta3]
|
|
side_effect = []
|
|
for meta in metadatas:
|
|
m = mock.Mock()
|
|
m.text = meta
|
|
side_effect.append(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': u'Civilité',
|
|
'disabled': False,
|
|
'user_editable': True,
|
|
'asked_on_registration': False,
|
|
'name': 'title'
|
|
},
|
|
{
|
|
'kind': 'string',
|
|
'description': '',
|
|
'required': True,
|
|
'user_visible': True,
|
|
'label': u'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': u'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': 'string',
|
|
'description': '',
|
|
'required': False,
|
|
'user_visible': True,
|
|
'label': u'Téléphone',
|
|
'disabled': False,
|
|
'user_editable': True,
|
|
'asked_on_registration': False,
|
|
'name': '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'
|
|
}
|
|
]
|
|
},
|
|
'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': u'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': u'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',
|
|
'base_url': 'http://clapiers.example.net',
|
|
'saml-sp-metadata-url':
|
|
'http://clapiers.example.net/saml/metadata',
|
|
},
|
|
]
|
|
}
|
|
hobo_json_content = json.dumps(env)
|
|
hobo_json = tempfile.NamedTemporaryFile()
|
|
hobo_json.write(hobo_json_content)
|
|
hobo_json.flush()
|
|
call_command('hobo_deploy', 'http://sso.example.net', hobo_json.name)
|
|
|
|
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() == 10
|
|
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']
|
|
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 (SPOptionsIdPPolicy,
|
|
LibertyProvider,
|
|
LibertyServiceProvider)
|
|
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() == 14
|
|
assert policy.attributes.filter(enabled=True).count() == 12
|
|
assert policy.attributes.filter(enabled=False).count() == 2
|
|
assert policy.attributes.filter(name_format='basic').count() == 14
|
|
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() == 3
|
|
services = tenant.get_hobo_json()['services']
|
|
from authentic2.a2_rbac.utils import get_default_ou
|
|
from authentic2.a2_rbac.models import Role
|
|
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 4 roles associated
|
|
# to their "ou" excluding their superuser role, 2 admin roles for users
|
|
# and roles, a 2 loaded roles, petite enfance and état-civil
|
|
assert Role.objects.filter(ou__isnull=False,
|
|
service__isnull=True).count() == 8
|
|
for service_id in set(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'])
|
|
assert provider.name == service['title']
|
|
assert provider.federation_source == 'hobo'
|
|
assert provider.entity_id == service['saml-sp-metadata-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['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() == 4
|
|
assert Role.objects.filter(
|
|
ou=provider.ou, service__isnull=True,
|
|
name=u'Service petite enfance').count() == 1
|
|
assert Role.objects.filter(
|
|
ou=provider.ou, service__isnull=True,
|
|
name=u'Service état-civil').count() == 1
|