Saml2 Metadata generator class
[cascardo/ipsilon.git] / ipsilon / providers / saml2 / metadata.py
1 #!/usr/bin/python
2 #
3 # Copyright (C) 2014  Simo Sorce <simo@redhat.com>
4 #
5 # see file 'COPYING' for use and warranty information
6 #
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.
11 #
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.
16 #
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/>.
19
20 from ipsilon.providers.saml2.certs import Certificate
21 from lxml import etree
22 import lasso
23
24
25 EDESC = '{%s}EntityDescriptor' % lasso.SAML2_METADATA_HREF
26 NSMAP = {
27     'md': lasso.SAML2_METADATA_HREF,
28     'ds': lasso.DS_HREF
29 }
30
31 IDPDESC = 'IDPSSODescriptor'
32 SPDESC = 'SPSSODescriptor'
33
34 IDP_ROLE = 'idp'
35 SP_ROLE = 'sp'
36
37 SSO_SERVICE = 'SingleSignOnService'
38 LOGOUT_SERVICE = 'SingleLogoutService'
39 ASSERTION_SERVICE = 'AssertionConsumerService'
40
41
42 def mdElement(_parent, _tag, **kwargs):
43     tag = '{%s}%s' % (lasso.SAML2_METADATA_HREF, _tag)
44     return etree.SubElement(_parent, tag, **kwargs)
45
46
47 def dsElement(_parent, _tag, **kwargs):
48     tag = '{%s}%s' % (lasso.DS_HREF, _tag)
49     return etree.SubElement(_parent, tag, **kwargs)
50
51
52 class Metadata(object):
53
54     def __init__(self, role=None):
55         self.root = etree.Element(EDESC, nsmap=NSMAP)
56         self.entityid = None
57         self.role = None
58         self.set_role(role)
59
60     def set_entity_id(self, url):
61         self.entityid = url
62         self.root.set('entityID', url)
63
64     def set_role(self, role):
65         if role is None:
66             return
67         elif role == IDP_ROLE:
68             description = IDPDESC
69         elif role == SP_ROLE:
70             description = SPDESC
71         else:
72             raise ValueError('invalid role: %s' % role)
73         self.role = mdElement(self.root, description)
74         self.role.set('protocolSupportEnumeration', lasso.SAML2_PROTOCOL_HREF)
75         return self.role
76
77     def add_cert(self, certdata, use):
78         desc = mdElement(self.role, 'KeyDescriptor')
79         desc.set('use', use)
80         info = dsElement(desc, 'KeyInfo')
81         data = dsElement(info, 'X509Data')
82         cert = dsElement(data, 'X509Certificate')
83         cert.text = certdata
84
85     def add_certs(self, signcert=None, enccert=None):
86         if signcert:
87             self.add_cert(signcert.get_cert(), 'signing')
88         if enccert:
89             self.add_cert(enccert.get_cert(), 'encryption')
90
91     def add_service(self, svctype, binding, location):
92         svc = mdElement(self.role, svctype)
93         svc.set('Binding', binding)
94         svc.set('Location', location)
95
96     def add_allowed_name_format(self, name_format):
97         nameidfmt = mdElement(self.role, 'NameIDFormat')
98         nameidfmt.text = name_format
99
100     def output(self, path):
101         data = etree.tostring(self.root, xml_declaration=True,
102                               encoding='UTF-8', pretty_print=True)
103         with open(path, 'w') as f:
104             f.write(data)
105
106
107 if __name__ == '__main__':
108     from ipsilon.providers.saml2.provider import NAMEID_MAP
109     import tempfile
110     import shutil
111     import os
112
113     tmpdir = tempfile.mkdtemp()
114
115     try:
116         # Test IDP generation
117         sign_cert = Certificate(tmpdir)
118         sign_cert.generate('idp-signing-cert', 'idp.ipsilon.example.com')
119         enc_cert = Certificate(tmpdir)
120         enc_cert.generate('idp-encryption-cert', 'idp.ipsilon.example.com')
121         idp = Metadata()
122         idp.set_entity_id('https://ipsilon.example.com/idp/metadata')
123         idp.set_role(IDP_ROLE)
124         idp.add_certs(sign_cert, enc_cert)
125         idp.add_service(SSO_SERVICE, lasso.SAML2_METADATA_BINDING_POST,
126                         'https://ipsilon.example.com/idp/saml2/POST')
127         idp.add_service(SSO_SERVICE, lasso.SAML2_METADATA_BINDING_REDIRECT,
128                         'https://ipsilon.example.com/idp/saml2/Redirect')
129         for k in NAMEID_MAP:
130             idp.add_allowed_name_format(NAMEID_MAP[k])
131         md_file = os.path.join(tmpdir, 'metadata.xml')
132         idp.output(md_file)
133         with open(md_file) as fd:
134             text = fd.read()
135         print '==================== IDP ===================='
136         print text
137         print '============================================='
138
139         # Test SP generation
140         sign_cert = Certificate(tmpdir)
141         sign_cert.generate('sp-signing-cert', 'sp.ipsilon.example.com')
142         sp = Metadata()
143         sp.set_entity_id('https://ipsilon.example.com/samlsp/metadata')
144         sp.set_role(SP_ROLE)
145         sp.add_certs(sign_cert)
146         sp.add_service(LOGOUT_SERVICE, lasso.SAML2_METADATA_BINDING_REDIRECT,
147                        'https://ipsilon.example.com/samlsp/logout')
148         sp.add_service(ASSERTION_SERVICE, lasso.SAML2_METADATA_BINDING_POST,
149                        'https://ipsilon.example.com/samlsp/postResponse')
150         md_file = os.path.join(tmpdir, 'metadata.xml')
151         sp.output(md_file)
152         with open(md_file) as fd:
153             text = fd.read()
154         print '===================== SP ===================='
155         print text
156         print '============================================='
157
158     finally:
159         shutil.rmtree(tmpdir)