3 # Copyright (C) 2014 Ipsilon project Contributors, for license see COPYING
6 from ipsilon.tools.certs import Certificate
12 'email': lasso.SAML2_NAME_IDENTIFIER_FORMAT_EMAIL,
13 'encrypted': lasso.SAML2_NAME_IDENTIFIER_FORMAT_ENCRYPTED,
14 'entity': lasso.SAML2_NAME_IDENTIFIER_FORMAT_ENTITY,
15 'kerberos': lasso.SAML2_NAME_IDENTIFIER_FORMAT_KERBEROS,
16 'persistent': lasso.SAML2_NAME_IDENTIFIER_FORMAT_PERSISTENT,
17 'transient': lasso.SAML2_NAME_IDENTIFIER_FORMAT_TRANSIENT,
18 'unspecified': lasso.SAML2_NAME_IDENTIFIER_FORMAT_UNSPECIFIED,
19 'windows': lasso.SAML2_NAME_IDENTIFIER_FORMAT_WINDOWS,
20 'x509': lasso.SAML2_NAME_IDENTIFIER_FORMAT_X509,
24 'sso-post': ('SingleSignOnService',
25 lasso.SAML2_METADATA_BINDING_POST),
26 'sso-redirect': ('SingleSignOnService',
27 lasso.SAML2_METADATA_BINDING_REDIRECT),
28 'sso-soap': ('SingleSignOnService',
29 lasso.SAML2_METADATA_BINDING_SOAP),
30 'logout-redirect': ('SingleLogoutService',
31 lasso.SAML2_METADATA_BINDING_REDIRECT),
32 'slo-soap': ('SingleLogoutService',
33 lasso.SAML2_METADATA_BINDING_SOAP),
34 'response-post': ('AssertionConsumerService',
35 lasso.SAML2_METADATA_BINDING_POST),
36 'response-paos': ('AssertionConsumerService',
37 lasso.SAML2_METADATA_BINDING_PAOS),
40 EDESC = '{%s}EntityDescriptor' % lasso.SAML2_METADATA_HREF
42 'md': lasso.SAML2_METADATA_HREF,
46 IDPDESC = 'IDPSSODescriptor'
47 SPDESC = 'SPSSODescriptor'
53 # Expire metadata weekly by default
57 def mdElement(_parent, _tag, **kwargs):
58 tag = '{%s}%s' % (lasso.SAML2_METADATA_HREF, _tag)
59 return etree.SubElement(_parent, tag, **kwargs)
62 def dsElement(_parent, _tag, **kwargs):
63 tag = '{%s}%s' % (lasso.DS_HREF, _tag)
64 return etree.SubElement(_parent, tag, **kwargs)
67 class Metadata(object):
69 def __init__(self, role=None, expiration=None):
70 self.root = etree.Element(EDESC, nsmap=NSMAP)
74 self.set_expiration(expiration)
76 def set_entity_id(self, url):
78 self.root.set('entityID', url)
80 def set_role(self, role):
83 elif role == IDP_ROLE:
88 raise ValueError('invalid role: %s' % role)
89 self.role = mdElement(self.root, description)
90 self.role.set('protocolSupportEnumeration', lasso.SAML2_PROTOCOL_HREF)
92 self.role.set('WantAuthnRequestsSigned', 'true')
95 def set_expiration(self, exp):
97 self.root.set('cacheDuration', "P%dD" % (MIN_EXP_DEFAULT))
99 elif isinstance(exp, datetime.date):
100 d = datetime.datetime.combine(exp, datetime.date.min.time())
101 elif isinstance(exp, datetime.datetime):
103 elif isinstance(exp, datetime.timedelta):
104 d = datetime.datetime.utcnow() + exp
106 raise TypeError('Invalid expiration date type')
108 self.root.set('validUntil', d.isoformat() + 'Z')
110 def add_cert(self, certdata, use):
111 desc = mdElement(self.role, 'KeyDescriptor')
113 info = dsElement(desc, 'KeyInfo')
114 data = dsElement(info, 'X509Data')
115 cert = dsElement(data, 'X509Certificate')
118 def add_certs(self, signcert=None, enccert=None):
120 self.add_cert(signcert.get_cert(), 'signing')
122 self.add_cert(enccert.get_cert(), 'encryption')
124 def add_service(self, service, location, **kwargs):
125 svc = mdElement(self.role, service[0])
126 svc.set('Binding', service[1])
127 svc.set('Location', location)
128 for key, value in kwargs.iteritems():
131 def add_allowed_name_format(self, name_format):
132 nameidfmt = mdElement(self.role, 'NameIDFormat')
133 nameidfmt.text = name_format
135 def output(self, path=None):
136 data = etree.tostring(self.root, xml_declaration=True,
137 encoding='UTF-8', pretty_print=True)
141 with open(path, 'w') as f:
145 if __name__ == '__main__':
150 tmpdir = tempfile.mkdtemp()
153 # Test IDP generation
154 sign_cert = Certificate(tmpdir)
155 sign_cert.generate('idp-signing-cert', 'idp.ipsilon.example.com')
156 enc_cert = Certificate(tmpdir)
157 enc_cert.generate('idp-encryption-cert', 'idp.ipsilon.example.com')
159 idp.set_entity_id('https://ipsilon.example.com/idp/metadata')
160 idp.set_role(IDP_ROLE)
161 idp.add_certs(sign_cert, enc_cert)
162 idp.add_service(SAML2_SERVICE_MAP['sso-post'],
163 'https://ipsilon.example.com/idp/saml2/POST')
164 idp.add_service(SAML2_SERVICE_MAP['sso-redirect'],
165 'https://ipsilon.example.com/idp/saml2/Redirect')
166 for k in SAML2_NAMEID_MAP:
167 idp.add_allowed_name_format(SAML2_NAMEID_MAP[k])
168 md_file = os.path.join(tmpdir, 'metadata.xml')
170 with open(md_file) as fd:
172 print '==================== IDP ===================='
174 print '============================================='
177 sign_cert = Certificate(tmpdir)
178 sign_cert.generate('sp-signing-cert', 'sp.ipsilon.example.com')
180 sp.set_entity_id('https://ipsilon.example.com/samlsp/metadata')
182 sp.add_certs(sign_cert)
183 sp.add_service(SAML2_SERVICE_MAP['logout-redirect'],
184 'https://ipsilon.example.com/samlsp/logout')
185 sp.add_service(SAML2_SERVICE_MAP['response-post'],
186 'https://ipsilon.example.com/samlsp/postResponse')
187 md_file = os.path.join(tmpdir, 'metadata.xml')
189 with open(md_file) as fd:
191 print '===================== SP ===================='
193 print '============================================='
196 shutil.rmtree(tmpdir)