203 lines
8.2 KiB
Python
203 lines
8.2 KiB
Python
# hobo - portal to configure and deploy applications
|
|
# Copyright (C) 2015 Entr'ouvert
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify it
|
|
# under the terms of the GNU Affero General Public License as published
|
|
# by the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU Affero General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import os
|
|
import logging
|
|
import requests
|
|
import urllib
|
|
import urlparse
|
|
import hashlib
|
|
import json
|
|
|
|
from optparse import make_option
|
|
|
|
from django.utils.text import slugify
|
|
from django.core.management.base import BaseCommand
|
|
from django.utils.translation import ugettext as _
|
|
from django.conf import settings
|
|
|
|
from authentic2.saml.models import LibertyProvider
|
|
from authentic2.a2_rbac.models import Role, RoleAttribute
|
|
from authentic2 import app_settings
|
|
|
|
|
|
from hobo import signature
|
|
from hobo.multitenant.middleware import TenantMiddleware
|
|
|
|
from tenant_schemas.utils import tenant_context
|
|
|
|
|
|
class WcsRoleImporter(object):
|
|
def __init__(self, liberty_provider, key, orig,
|
|
attribute_name='role-slug', delete=False):
|
|
self.service = liberty_provider
|
|
self.slug = liberty_provider.slug
|
|
self.key = key
|
|
self.orig = orig
|
|
self.attribute_name = attribute_name
|
|
self.delete = delete
|
|
assert 'saml/metadata' in self.service.entity_id
|
|
self.wcs_url = self.service.entity_id.split('saml/metadata')[0]
|
|
self.logger = logging.getLogger('%s.%s' % (__name__,
|
|
self.__class__.__name__))
|
|
self.seen_ids = set()
|
|
|
|
def import_roles(self):
|
|
for role_tpl in self.get_roles():
|
|
self.seen_ids.add(role_tpl.external_id)
|
|
self.create_role(role_tpl)
|
|
if self.delete:
|
|
self.delete_dead_roles()
|
|
su_role, created = Role.objects.get_or_create(
|
|
service=self.service, slug='_a2-hobo-superuser',
|
|
defaults={'name': _('Superuser')})
|
|
su_role.attributes.get_or_create(name='is_superuser', kind='string',
|
|
value='true')
|
|
|
|
def create_role(self, role_tpl):
|
|
defaults = {
|
|
'name': role_tpl.name,
|
|
# w.c.s. will always provide a slug but for other services we do
|
|
# not know
|
|
'slug': role_tpl.slug or slugify(role_tpl.name),
|
|
}
|
|
# search role by external id, create if not found
|
|
role, created = Role.objects.get_or_create(
|
|
ou=self.service.ou,
|
|
external_id=role_tpl.external_id,
|
|
defaults=defaults)
|
|
RoleAttribute.objects.filter(role=role).delete()
|
|
role_attribute, ra_created = RoleAttribute.objects.get_or_create(
|
|
role=role,
|
|
name=self.attribute_name,
|
|
kind='string',
|
|
defaults={
|
|
'value': role_tpl.external_id
|
|
})
|
|
if created:
|
|
self.logger.info('imported new role %r(%r) from service %s',
|
|
role.external_id, role.name, self.slug)
|
|
# update role attribute value if it has changed
|
|
if not ra_created:
|
|
if role_attribute.value != role_tpl.external_id:
|
|
role_attribute.value = role_tpl.external_id
|
|
role_attribute.save()
|
|
# update role name if has changed
|
|
if not created:
|
|
# Update name and slug if they have changed
|
|
if role.name != role_tpl.name:
|
|
role.name = role_tpl.name
|
|
role.save()
|
|
# update emails, emails_to_members and details in RA
|
|
if role_tpl.emails:
|
|
ra, created = RoleAttribute.objects.get_or_create(
|
|
role=role, name='emails', kind='json',
|
|
defaults={'value': json.dumps(role_tpl.emails)})
|
|
if ra.value != json.dumps(role_tpl.emails):
|
|
ra.value = json.dumps(role_tpl.emails)
|
|
ra.save()
|
|
ra, created = RoleAttribute.objects.get_or_create(
|
|
role=role, name='emails_to_members', kind='json',
|
|
defaults={'value': json.dumps(role_tpl.emails_to_members)})
|
|
if ra.value != json.dumps(role_tpl.emails_to_members):
|
|
ra.value = json.dumps(role_tpl.emails_to_members)
|
|
ra.save()
|
|
if role_tpl.details:
|
|
value = json.dumps(role_tpl.details)
|
|
ra, created = RoleAttribute.objects.get_or_create(
|
|
role=role, name='details', kind='json',
|
|
defaults={'value': value})
|
|
if ra.value != value:
|
|
ra.value = value
|
|
ra.save()
|
|
|
|
def delete_dead_roles(self):
|
|
'''Deletes service roles whose id is not in self.seen_ids'''
|
|
qs = Role.objects.filter(service=self.service) \
|
|
.exclude(external_id__in=list(self.seen_ids))
|
|
for role in qs:
|
|
self.logger.info('deleted dead role %r(%r) from service %s',
|
|
role.external_id, role.slug, self.slug)
|
|
qs.delete()
|
|
|
|
def get_roles(self):
|
|
'''Get w.c.s. from its roles web-service by sending a signed GET
|
|
request.
|
|
'''
|
|
url = self.wcs_url + 'api/roles?%s' % urllib.urlencode(
|
|
{'format': 'json', 'orig': self.orig})
|
|
signed_url = signature.sign_url(url, self.key)
|
|
response = requests.get(signed_url, verify=app_settings.A2_VERIFY_SSL)
|
|
if response.status_code == 200:
|
|
for role in response.json()['data']:
|
|
new_role = Role(name=role['text'], external_id=str(role['slug']),
|
|
slug=str(role['slug']))
|
|
new_role.description = role.get('details', u'')
|
|
new_role.emails = role.get('emails', [])
|
|
new_role.emails_to_members = role.get('emails_to_members', False)
|
|
new_role.details = role.get('details', '')
|
|
yield new_role
|
|
else:
|
|
self.logger.warn('failed to get roles for %s (response: %s)',
|
|
self.wcs_url, response.status_code)
|
|
|
|
|
|
class Command(BaseCommand):
|
|
help = "Import W.C.S. roles"
|
|
|
|
requires_system_checks = False
|
|
|
|
def add_arguments(self, parser):
|
|
parser.add_argument('--delete', action="store_true", dest='delete')
|
|
|
|
def handle(self, *args, **options):
|
|
# traverse list of tenants
|
|
for tenant in TenantMiddleware.get_tenants():
|
|
with tenant_context(tenant):
|
|
if getattr(settings, 'HOBO_ROLE_EXPORT', True):
|
|
continue
|
|
self.handle_tenant(tenant, **options)
|
|
|
|
def handle_tenant(self, tenant, **options):
|
|
# extract informations on deployed w.c.s. instances from hobo.json
|
|
hobo_json_path = os.path.join(tenant.get_directory(), 'hobo.json')
|
|
if not os.path.exists(hobo_json_path):
|
|
print 'skipping %s, no hobo.json found' % tenant
|
|
return
|
|
hobo_environment = json.load(open(hobo_json_path))
|
|
# compute our credentials from our hobo configuration
|
|
me = [x for x in hobo_environment['services'] if x.get('this')]
|
|
if not me:
|
|
print 'skipping %s, self services is not marked' % tenant
|
|
return
|
|
me = me[0]
|
|
orig = urlparse.urlsplit(me['base_url']).netloc.split(':')[0]
|
|
for service_id in settings.KNOWN_SERVICES:
|
|
if service_id != 'wcs':
|
|
continue
|
|
for service in settings.KNOWN_SERVICES[service_id].values():
|
|
if not service.get('secret'):
|
|
continue
|
|
liberty_provider = LibertyProvider.objects.get(
|
|
entity_id=service['url'] + 'saml/metadata')
|
|
importer = WcsRoleImporter(
|
|
liberty_provider=liberty_provider,
|
|
key=service['secret'],
|
|
orig=orig,
|
|
delete=options.get('delete', False),
|
|
)
|
|
importer.import_roles()
|