1
|
import os
|
2
|
import json
|
3
|
import subprocess
|
4
|
import xml.etree.ElementTree as etree
|
5
|
import requests
|
6
|
import urlparse
|
7
|
import re
|
8
|
import unidecode
|
9
|
import lasso
|
10
|
|
11
|
from django.conf import settings
|
12
|
from django.core.management.base import BaseCommand, CommandError
|
13
|
|
14
|
def md_element_name(tag_name):
|
15
|
return '{%s}%s' % (lasso.SAML2_METADATA_HREF, tag_name)
|
16
|
|
17
|
def mdui_element_name(tag_name):
|
18
|
return '{%s}%s' % (SAML2_METADATA_UI_HREF, tag_name)
|
19
|
|
20
|
def slugify(text):
|
21
|
text = unidecode.unidecode(text).lower()
|
22
|
return re.sub(r'\W+', '-', text)
|
23
|
|
24
|
SAML2_METADATA_UI_HREF = 'urn:oasis:names:tc:SAML:metadata:ui'
|
25
|
ENTITY_DESCRIPTOR_TN = md_element_name('EntityDescriptor')
|
26
|
ENTITIES_DESCRIPTOR_TN = md_element_name('EntitiesDescriptor')
|
27
|
IDP_SSO_DESCRIPTOR_TN = md_element_name('IDPSSODescriptor')
|
28
|
SP_SSO_DESCRIPTOR_TN = md_element_name('SPSSODescriptor')
|
29
|
ORGANIZATION_DISPLAY_NAME = md_element_name('OrganizationDisplayName')
|
30
|
ORGANIZATION_NAME = md_element_name('OrganizationName')
|
31
|
ORGANIZATION = md_element_name('Organization')
|
32
|
EXTENSIONS = md_element_name('Extensions')
|
33
|
UI_INFO = mdui_element_name('UIInfo')
|
34
|
DISPLAY_NAME = mdui_element_name('DisplayName')
|
35
|
ENTITY_ID = 'entityID'
|
36
|
PROTOCOL_SUPPORT_ENUMERATION = 'protocolSupportEnumeration'
|
37
|
|
38
|
def check_support_saml2(tree):
|
39
|
if tree is not None and lasso.SAML2_PROTOCOL_HREF in tree.get(PROTOCOL_SUPPORT_ENUMERATION):
|
40
|
return True
|
41
|
return False
|
42
|
|
43
|
def verify_metadata(codename, signcert):
|
44
|
metadata = metadata_filename(codename, 'downloaded')
|
45
|
if not signcert:
|
46
|
print 'warn: do not verify %s metadata (no certificate provided)' % codename
|
47
|
ret = True
|
48
|
else:
|
49
|
signcert_pem = metadata_filename(codename, 'signcert.pem')
|
50
|
dir = os.path.join(METADATAS_DIR, codename)
|
51
|
f = open(signcert_pem, 'wb')
|
52
|
f.write(signcert)
|
53
|
f.close()
|
54
|
ret = 0 == subprocess.call(['xmlsec1', '--verify',
|
55
|
'--id-attr:ID', 'EntitiesDescriptor',
|
56
|
'--pubkey-cert-pem', signcert_pem,
|
57
|
'--enabled-key-data', 'key-name',
|
58
|
metadata])
|
59
|
if ret:
|
60
|
os.rename(metadata, metadata_filename(codename))
|
61
|
else:
|
62
|
print 'warn: bad signature for %s metadata' % codename
|
63
|
os.rename(metadata, metadata_filename(codename, 'bad_signature'))
|
64
|
return ret
|
65
|
|
66
|
class Command(BaseCommand):
|
67
|
|
68
|
def handle(self, *args, **options):
|
69
|
|
70
|
idps = []
|
71
|
if not os.path.exists(settings.METADATAS_DIR):
|
72
|
os.mkdir(settings.METADATAS_DIR)
|
73
|
|
74
|
for metadata_uri in settings.METADATA_URIS:
|
75
|
metadata = requests.get(metadata_uri)
|
76
|
url = urlparse.urlparse(metadata_uri)
|
77
|
metadata_file_path = os.path.join(settings.METADATAS_DIR,
|
78
|
url.path.split('/')[-1])
|
79
|
with open(metadata_file_path, 'w') as metadata_file:
|
80
|
metadata_file.write(metadata.content)
|
81
|
|
82
|
idp_dir, ext = os.path.splitext(metadata_file_path)
|
83
|
if not os.path.exists(idp_dir):
|
84
|
os.mkdir(os.path.join(settings.METADATAS_DIR, idp_dir))
|
85
|
doc = etree.parse(metadata_file_path)
|
86
|
if doc.getroot().tag == ENTITIES_DESCRIPTOR_TN:
|
87
|
entity_descriptors = doc.getroot().findall(ENTITY_DESCRIPTOR_TN)
|
88
|
for entity_descriptor in entity_descriptors:
|
89
|
idp = check_support_saml2(entity_descriptor.find(IDP_SSO_DESCRIPTOR_TN))
|
90
|
if not idp:
|
91
|
continue
|
92
|
entity_id = entity_descriptor.get(ENTITY_ID)
|
93
|
name = None
|
94
|
display_name = entity_descriptor.find('.//%s/%s/%s' % (EXTENSIONS, UI_INFO, DISPLAY_NAME))
|
95
|
if display_name is not None:
|
96
|
name = display_name.text
|
97
|
if not name:
|
98
|
organization = entity_descriptor.find(ORGANIZATION)
|
99
|
if organization is not None:
|
100
|
organization_display_name = organization.find(ORGANIZATION_DISPLAY_NAME)
|
101
|
organization_name = organization.find(ORGANIZATION_NAME)
|
102
|
if organization_display_name is not None:
|
103
|
name = organization_display_name.text
|
104
|
elif organization_name is not None:
|
105
|
name = organization_name.text
|
106
|
if not name:
|
107
|
name = entity_id
|
108
|
idp = entity_descriptor.find(IDP_SSO_DESCRIPTOR_TN)
|
109
|
entity_id = entity_descriptor.get(ENTITY_ID)
|
110
|
entities = etree.tostring(entity_descriptor)
|
111
|
metadata_dest_filename = os.path.join(idp_dir, '%s.xml' % slugify(name))
|
112
|
with open(metadata_dest_filename, 'w') as metadata:
|
113
|
metadata.write(entities)
|
114
|
|
115
|
idps.append({'METADATA': os.path.abspath(metadata_dest_filename),
|
116
|
'ENTITY_ID': entity_id,
|
117
|
'NAME': name})
|
118
|
|
119
|
with open(os.path.join(settings.BASE_DIR, 'idps.json'), 'w') as idps_file:
|
120
|
json.dump(idps, idps_file)
|