245 lines
11 KiB
Python
245 lines
11 KiB
Python
import requests
|
|
import logging
|
|
import os
|
|
|
|
from authentic2 import app_settings
|
|
from authentic2.compat_lasso import lasso
|
|
from authentic2.models import Attribute
|
|
from authentic2.saml.models import LibertyProvider, SPOptionsIdPPolicy, \
|
|
SAMLAttribute, LibertyServiceProvider
|
|
from authentic2.a2_rbac.utils import get_default_ou
|
|
|
|
from django.contrib.auth import get_user_model
|
|
from django.contrib.contenttypes.models import ContentType
|
|
from django.utils.translation import ugettext_lazy as _, activate
|
|
from django.core import serializers
|
|
|
|
from django_rbac.utils import get_role_model, get_ou_model
|
|
from django.conf import settings
|
|
|
|
from tenant_schemas.utils import tenant_context
|
|
|
|
from hobo.agent.common.management.commands import hobo_deploy
|
|
|
|
User = get_user_model()
|
|
|
|
|
|
class Command(hobo_deploy.Command):
|
|
help = 'Deploy multitenant authentic service from hobo'
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
self.logger = logging.getLogger(__name__)
|
|
super(Command, self).__init__(*args, **kwargs)
|
|
|
|
def deploy_specifics(self, hobo_environment, tenant):
|
|
# generate SAML keys
|
|
self.generate_saml_keys(tenant)
|
|
self.configure_theme(hobo_environment, tenant)
|
|
|
|
with tenant_context(tenant):
|
|
# Activate default translation
|
|
activate(settings.LANGUAGE_CODE)
|
|
for user_dict in hobo_environment.get('users'):
|
|
# create hobo users in authentic to bootstrap
|
|
# (don't update them, hobo is not a provisioning system)
|
|
if not user_dict.get('email'):
|
|
# get an email for settings, or default to system
|
|
try:
|
|
user_dict['email'] = settings.ADMINS[0][1]
|
|
except (IndexError, ValueError):
|
|
user_dict['email'] = 'root@localhost'
|
|
|
|
if not (user_dict.get('first_name') or user_dict.get('last_name')):
|
|
# some SP require a name
|
|
user_dict['first_name'] = user_dict['username']
|
|
|
|
user_dict['is_staff'] = True
|
|
user_dict['is_superuser'] = True
|
|
|
|
user, created = User.objects.get_or_create(
|
|
defaults=user_dict,
|
|
username=user_dict['username'])
|
|
|
|
# create/update user attributes
|
|
fields = []
|
|
disabled_fields = []
|
|
for i, attribute in enumerate(hobo_environment.get('profile', {}).get('fields')):
|
|
if attribute['name'] == 'email':
|
|
# this field is hardcoded in the user model, don't add
|
|
# it as a new attribute, but add it to the fields list,
|
|
# so it gets shared as SAML attribute.
|
|
fields.append(attribute['name'])
|
|
continue
|
|
attr, created = Attribute.all_objects.get_or_create(
|
|
name=attribute['name'],
|
|
defaults={'kind': attribute['kind']})
|
|
for key in ('label', 'description', 'asked_on_registration',
|
|
'user_editable', 'user_visible', 'required',
|
|
'searchable', 'disabled'):
|
|
if key in attribute:
|
|
setattr(attr, key, attribute[key])
|
|
attr.order = i
|
|
if attribute['disabled']:
|
|
disabled_fields.append(attr.name)
|
|
else:
|
|
fields.append(attr.name)
|
|
attr.save()
|
|
|
|
# creation of IdpPolicy
|
|
policy, created = SPOptionsIdPPolicy.objects.get_or_create(name='Default')
|
|
policy.enabled = True
|
|
policy.authn_request_signed = False
|
|
policy.accepted_name_id_format = ['uuid']
|
|
policy.default_name_id_format = 'uuid'
|
|
policy.idp_initiated_sso = True
|
|
policy.save()
|
|
|
|
policy_type = ContentType.objects.get_for_model(SPOptionsIdPPolicy)
|
|
provider_type = ContentType.objects.get_for_model(LibertyProvider)
|
|
|
|
# create SAML default policy attributes
|
|
names = ['username', 'is_superuser'] + fields + disabled_fields
|
|
for name in names:
|
|
attribute, created = SAMLAttribute.objects.get_or_create(
|
|
name=name, name_format='basic',
|
|
attribute_name='django_user_%s' % name,
|
|
object_id=policy.id, content_type=policy_type)
|
|
attribute.enabled = not (name in disabled_fields)
|
|
attribute.save()
|
|
|
|
# also pass verified attributes
|
|
SAMLAttribute.objects.get_or_create(
|
|
name='verified_attributes',
|
|
name_format='basic',
|
|
attribute_name='@verified_attributes@',
|
|
object_id=policy.id,
|
|
content_type=policy_type)
|
|
|
|
# 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
|
|
|
|
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:
|
|
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 == 'Superuser':
|
|
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'])
|
|
|
|
def load_skeleton(self, provider, service_id, template_name,
|
|
create_ou=False):
|
|
if not getattr(settings, 'HOBO_SKELETONS_DIR', None):
|
|
self.logger.debug('no skeleton: no HOBO_SKELETONS_DIR setting')
|
|
return
|
|
# ex.: /var/lib/authentic2-multitenant/skeletons/communes/wcs/
|
|
skeleton_dir = os.path.join(settings.HOBO_SKELETONS_DIR, template_name,
|
|
service_id)
|
|
if not os.path.exists(skeleton_dir):
|
|
self.logger.debug('no skeleton: skeleton dir %r does not exist',
|
|
skeleton_dir)
|
|
return
|
|
self.load_skeleton_roles(skeleton_dir, provider)
|
|
|
|
def load_skeleton_roles(self, skeleton_dir, provider):
|
|
'''Load default roles based on a template'''
|
|
roles_filename = os.path.join(skeleton_dir, 'roles.json')
|
|
if not os.path.exists(roles_filename):
|
|
self.logger.debug('no skeleton roles: roles file %r does not '
|
|
'exist', roles_filename)
|
|
return
|
|
Role = get_role_model()
|
|
if Role.objects.filter(ou=provider.ou).exclude(slug__startswith='_').exists():
|
|
return
|
|
roles = []
|
|
for role in serializers.deserialize('json', open(roles_filename)):
|
|
assert isinstance(role.object, Role)
|
|
# reset id and natural key
|
|
role.object.pk = None
|
|
role.object.uuid = Role._meta.get_field('uuid').default()
|
|
# same ou as provider
|
|
role.object.ou = provider.ou
|
|
# XXX: attach to service or not ?
|
|
roles.append(role.object)
|
|
if roles:
|
|
Role.objects.bulk_create(roles)
|
|
Role.objects.get(uuid=roles[-1].uuid).save()
|