|
1 |
import os
|
|
2 |
import logging
|
|
3 |
import requests
|
|
4 |
import urllib
|
|
5 |
import urlparse
|
|
6 |
import hashlib
|
|
7 |
import json
|
|
8 |
|
|
9 |
from optparse import make_option
|
|
10 |
|
|
11 |
from django.utils.text import slugify
|
|
12 |
from django.core.management.base import BaseCommand
|
|
13 |
from django.contrib.auth import get_user_model
|
|
14 |
|
|
15 |
from authentic2.saml.models import LibertyProvider
|
|
16 |
from authentic2.a2_rbac.models import Role, RoleAttribute
|
|
17 |
|
|
18 |
|
|
19 |
from hobo import signature
|
|
20 |
from hobo.multitenant.middleware import TenantMiddleware
|
|
21 |
|
|
22 |
from tenant_schemas.utils import tenant_context
|
|
23 |
|
|
24 |
|
|
25 |
class WcsRoleImporter(object):
|
|
26 |
def __init__(self, liberty_provider, key, orig, email,
|
|
27 |
attribute_name='role-slug', delete=False):
|
|
28 |
self.service = liberty_provider
|
|
29 |
self.slug = liberty_provider.slug
|
|
30 |
self.key = key
|
|
31 |
self.orig = orig
|
|
32 |
self.email = email
|
|
33 |
self.attribute_name = attribute_name
|
|
34 |
self.delete = delete
|
|
35 |
assert 'saml/metadata' in self.service.entity_id
|
|
36 |
self.wcs_url = self.service.entity_id.split('saml/metadata')[0]
|
|
37 |
self.logger = logging.getLogger('%s.%s' % (__name__,
|
|
38 |
self.__class__.__name__))
|
|
39 |
self.seen_ids = set()
|
|
40 |
|
|
41 |
def import_roles(self):
|
|
42 |
for role_tpl in self.get_roles():
|
|
43 |
self.seen_ids.add(role_tpl.external_id)
|
|
44 |
self.create_role(role_tpl)
|
|
45 |
if self.delete:
|
|
46 |
self.delete_dead_roles()
|
|
47 |
|
|
48 |
def create_role(self, role_tpl):
|
|
49 |
defaults = {
|
|
50 |
'name': role_tpl.name,
|
|
51 |
# w.c.s. will always provide a slug but for other services we do
|
|
52 |
# not know
|
|
53 |
'slug': role_tpl.slug or slugify(role_tpl.name),
|
|
54 |
}
|
|
55 |
# search role by external id, create if not found
|
|
56 |
role, created = Role.objects.get_or_create(
|
|
57 |
service=self.service,
|
|
58 |
external_id=role_tpl.external_id,
|
|
59 |
defaults=defaults)
|
|
60 |
RoleAttribute.objects.filter(role=role).delete()
|
|
61 |
role_attribute, ra_created = RoleAttribute.objects.get_or_create(
|
|
62 |
role=role,
|
|
63 |
name=self.attribute_name,
|
|
64 |
kind='string',
|
|
65 |
defaults={
|
|
66 |
'value': role_tpl.external_id
|
|
67 |
})
|
|
68 |
if created:
|
|
69 |
self.logger.info('imported new role %r(%r) from service %s',
|
|
70 |
role.external_id, role.name, self.slug)
|
|
71 |
# update role attribute value if it has changed
|
|
72 |
if not ra_created:
|
|
73 |
if role_attribute.value != role_tpl.external_id:
|
|
74 |
role_attribute.value = role_tpl.external_id
|
|
75 |
role_attribute.save()
|
|
76 |
# update role name if has changed
|
|
77 |
if not created:
|
|
78 |
# Update name and slug if they have changed
|
|
79 |
if role.name != role_tpl.name:
|
|
80 |
role.name = role_tpl.name
|
|
81 |
role.save()
|
|
82 |
|
|
83 |
def delete_dead_roles(self):
|
|
84 |
'''Deletes service roles whose id is not in self.seen_ids'''
|
|
85 |
qs = Role.objects.filter(service=self.service) \
|
|
86 |
.exclude(external_id__in=list(self.seen_ids))
|
|
87 |
for role in qs:
|
|
88 |
self.logger.info('deleted dead role %r(%r) from service %s',
|
|
89 |
role.external_id, role.slug, self.slug)
|
|
90 |
qs.delete()
|
|
91 |
|
|
92 |
def get_roles(self):
|
|
93 |
'''Get w.c.s. from its roles web-service by sending a signed GET
|
|
94 |
request.
|
|
95 |
'''
|
|
96 |
url = self.wcs_url + 'roles?%s' % urllib.urlencode(
|
|
97 |
{'format': 'json', 'orig': self.orig, 'email': self.email})
|
|
98 |
signed_url = signature.sign_url(url, self.key)
|
|
99 |
response = requests.get(signed_url)
|
|
100 |
print response.content
|
|
101 |
for role in response.json()['data']:
|
|
102 |
yield Role(name=role['text'], external_id=str(role['slug']),
|
|
103 |
slug=str(role['slug']))
|
|
104 |
|
|
105 |
|
|
106 |
class Command(BaseCommand):
|
|
107 |
option_list = BaseCommand.option_list + (
|
|
108 |
make_option('--delete', action='store_true', dest='delete'),
|
|
109 |
)
|
|
110 |
help = "Import W.C.S. roles"
|
|
111 |
|
|
112 |
requires_system_checks = False
|
|
113 |
|
|
114 |
def handle(self, *args, **options):
|
|
115 |
# traverse list of tenants
|
|
116 |
for tenant in TenantMiddleware.get_tenants():
|
|
117 |
with tenant_context(tenant):
|
|
118 |
self.handle_tenant(tenant, **options)
|
|
119 |
|
|
120 |
def handle_tenant(self, tenant, **options):
|
|
121 |
# extract informations on deployed w.c.s. instances from hobo.json
|
|
122 |
hobo_json_path = os.path.join(tenant.get_directory(), 'hobo.json')
|
|
123 |
if not os.path.exists(hobo_json_path):
|
|
124 |
print 'skipping %s, no hobo.json found' % tenant
|
|
125 |
return
|
|
126 |
hobo_environment = json.load(open(hobo_json_path))
|
|
127 |
# compute our credentials from our hobo configuration
|
|
128 |
me = [x for x in hobo_environment['services'] if x.get('this')]
|
|
129 |
if not me:
|
|
130 |
print 'skipping %s, self services is not marked' % tenant
|
|
131 |
return
|
|
132 |
orig = urlparse.urlsplit(me['base_url']).netloc
|
|
133 |
key = hashlib.sha1(orig+me['secret_key']).hexdigest()
|
|
134 |
# FIXME: get mail of the oldest superuser, could we do better ?
|
|
135 |
User = get_user_model()
|
|
136 |
email = User.objects.order_by('id').filter(email__contains='@',
|
|
137 |
is_superuser=True)[0].email
|
|
138 |
for service in hobo_environment['services']:
|
|
139 |
if not service.get('saml-sp-metadata-url'):
|
|
140 |
continue
|
|
141 |
liberty_provider = LibertyProvider.objects.get(
|
|
142 |
entity_id=service['saml-sp-metadata-url'])
|
|
143 |
importer = WcsRoleImporter(
|
|
144 |
liberty_provider=liberty_provider,
|
|
145 |
key=key,
|
|
146 |
orig=orig,
|
|
147 |
email=email,
|
|
148 |
delete=options.get('delete', False),
|
|
149 |
)
|
|
150 |
importer.import_roles()
|