hobo/hobo/agent/authentic2/management/commands/import-wcs-roles.py

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()