3 # Copyright (C) 2014 Simo Sorce <simo@redhat.com>
5 # see file 'COPYING' for use and warranty information
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 from ipsilon.tools.certs import Certificate
22 from lxml import etree
27 'email': lasso.SAML2_NAME_IDENTIFIER_FORMAT_EMAIL,
28 'encrypted': lasso.SAML2_NAME_IDENTIFIER_FORMAT_ENCRYPTED,
29 'entity': lasso.SAML2_NAME_IDENTIFIER_FORMAT_ENTITY,
30 'kerberos': lasso.SAML2_NAME_IDENTIFIER_FORMAT_KERBEROS,
31 'persistent': lasso.SAML2_NAME_IDENTIFIER_FORMAT_PERSISTENT,
32 'transient': lasso.SAML2_NAME_IDENTIFIER_FORMAT_TRANSIENT,
33 'unspecified': lasso.SAML2_NAME_IDENTIFIER_FORMAT_UNSPECIFIED,
34 'windows': lasso.SAML2_NAME_IDENTIFIER_FORMAT_WINDOWS,
35 'x509': lasso.SAML2_NAME_IDENTIFIER_FORMAT_X509,
39 'sso-post': ('SingleSignOnService',
40 lasso.SAML2_METADATA_BINDING_POST),
41 'sso-redirect': ('SingleSignOnService',
42 lasso.SAML2_METADATA_BINDING_REDIRECT),
43 'logout-redirect': ('SingleLogoutService',
44 lasso.SAML2_METADATA_BINDING_REDIRECT),
45 'response-post': ('AssertionConsumerService',
46 lasso.SAML2_METADATA_BINDING_POST)
49 EDESC = '{%s}EntityDescriptor' % lasso.SAML2_METADATA_HREF
51 'md': lasso.SAML2_METADATA_HREF,
55 IDPDESC = 'IDPSSODescriptor'
56 SPDESC = 'SPSSODescriptor'
62 # Expire metadata weekly by default
66 def mdElement(_parent, _tag, **kwargs):
67 tag = '{%s}%s' % (lasso.SAML2_METADATA_HREF, _tag)
68 return etree.SubElement(_parent, tag, **kwargs)
71 def dsElement(_parent, _tag, **kwargs):
72 tag = '{%s}%s' % (lasso.DS_HREF, _tag)
73 return etree.SubElement(_parent, tag, **kwargs)
76 class Metadata(object):
78 def __init__(self, role=None, expiration=None):
79 self.root = etree.Element(EDESC, nsmap=NSMAP)
83 self.set_expiration(expiration)
85 def set_entity_id(self, url):
87 self.root.set('entityID', url)
89 def set_role(self, role):
92 elif role == IDP_ROLE:
97 raise ValueError('invalid role: %s' % role)
98 self.role = mdElement(self.root, description)
99 self.role.set('protocolSupportEnumeration', lasso.SAML2_PROTOCOL_HREF)
102 def set_expiration(self, exp):
104 self.root.set('cacheDuration', "P%dD" % (MIN_EXP_DEFAULT))
106 elif isinstance(exp, datetime.date):
107 d = datetime.datetime.combine(exp, datetime.date.min.time())
108 elif isinstance(exp, datetime.datetime):
110 elif isinstance(exp, datetime.timedelta):
111 d = datetime.datetime.now() + exp
113 raise TypeError('Invalid expiration date type')
115 self.root.set('validUntil', d.isoformat())
117 def add_cert(self, certdata, use):
118 desc = mdElement(self.role, 'KeyDescriptor')
120 info = dsElement(desc, 'KeyInfo')
121 data = dsElement(info, 'X509Data')
122 cert = dsElement(data, 'X509Certificate')
125 def add_certs(self, signcert=None, enccert=None):
127 self.add_cert(signcert.get_cert(), 'signing')
129 self.add_cert(enccert.get_cert(), 'encryption')
131 def add_service(self, service, location, **kwargs):
132 svc = mdElement(self.role, service[0])
133 svc.set('Binding', service[1])
134 svc.set('Location', location)
135 for key, value in kwargs.iteritems():
138 def add_allowed_name_format(self, name_format):
139 nameidfmt = mdElement(self.role, 'NameIDFormat')
140 nameidfmt.text = name_format
142 def output(self, path=None):
143 data = etree.tostring(self.root, xml_declaration=True,
144 encoding='UTF-8', pretty_print=True)
148 with open(path, 'w') as f:
152 if __name__ == '__main__':
157 tmpdir = tempfile.mkdtemp()
160 # Test IDP generation
161 sign_cert = Certificate(tmpdir)
162 sign_cert.generate('idp-signing-cert', 'idp.ipsilon.example.com')
163 enc_cert = Certificate(tmpdir)
164 enc_cert.generate('idp-encryption-cert', 'idp.ipsilon.example.com')
166 idp.set_entity_id('https://ipsilon.example.com/idp/metadata')
167 idp.set_role(IDP_ROLE)
168 idp.add_certs(sign_cert, enc_cert)
169 idp.add_service(SAML2_SERVICE_MAP['sso-post'],
170 'https://ipsilon.example.com/idp/saml2/POST')
171 idp.add_service(SAML2_SERVICE_MAP['sso-redirect'],
172 'https://ipsilon.example.com/idp/saml2/Redirect')
173 for k in SAML2_NAMEID_MAP:
174 idp.add_allowed_name_format(SAML2_NAMEID_MAP[k])
175 md_file = os.path.join(tmpdir, 'metadata.xml')
177 with open(md_file) as fd:
179 print '==================== IDP ===================='
181 print '============================================='
184 sign_cert = Certificate(tmpdir)
185 sign_cert.generate('sp-signing-cert', 'sp.ipsilon.example.com')
187 sp.set_entity_id('https://ipsilon.example.com/samlsp/metadata')
189 sp.add_certs(sign_cert)
190 sp.add_service(SAML2_SERVICE_MAP['logout-redirect'],
191 'https://ipsilon.example.com/samlsp/logout')
192 sp.add_service(SAML2_SERVICE_MAP['response-post'],
193 'https://ipsilon.example.com/samlsp/postResponse')
194 md_file = os.path.join(tmpdir, 'metadata.xml')
196 with open(md_file) as fd:
198 print '===================== SP ===================='
200 print '============================================='
203 shutil.rmtree(tmpdir)