Saml2 initial admin page
[cascardo/ipsilon.git] / ipsilon / providers / saml2idp.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.common import ProviderBase, ProviderPageBase
21 from ipsilon.providers.common import FACILITY
22 from ipsilon.providers.saml2.auth import AuthenticateRequest
23 from ipsilon.providers.saml2.admin import AdminPage
24 from ipsilon.providers.saml2.certs import Certificate
25 from ipsilon.providers.saml2 import metadata
26 from ipsilon.util.user import UserSession
27 from ipsilon.util.plugin import PluginObject
28 import cherrypy
29 import lasso
30 import pwd
31 import os
32
33
34 class Redirect(AuthenticateRequest):
35
36     def GET(self, *args, **kwargs):
37
38         query = cherrypy.request.query_string
39
40         login = self.saml2login(query)
41         return self.auth(login)
42
43
44 class POSTAuth(AuthenticateRequest):
45
46     def POST(self, *args, **kwargs):
47
48         request = kwargs.get(lasso.SAML2_FIELD_REQUEST)
49         relaystate = kwargs.get(lasso.SAML2_FIELD_RELAYSTATE)
50
51         login = self.saml2login(request)
52         login.set_msgRelayState(relaystate)
53         return self.auth(login)
54
55
56 class Continue(AuthenticateRequest):
57
58     def GET(self, *args, **kwargs):
59
60         session = UserSession()
61         user = session.get_user()
62         session.nuke_data('login', 'Return')
63         self.stage = session.get_data('saml2', 'stage')
64
65         if user.is_anonymous:
66             self._debug("User is marked anonymous?!")
67             # TODO: Return to SP with auth failed error
68             raise cherrypy.HTTPError(401)
69
70         self._debug('Continue auth for %s' % user.name)
71
72         dump = session.get_data('saml2', 'Request')
73         if not dump:
74             self._debug("Couldn't find Request dump?!")
75             # TODO: Return to SP with auth failed error
76             raise cherrypy.HTTPError(400)
77
78         try:
79             login = lasso.Login.newFromDump(self.cfg.idp, dump)
80         except Exception, e:  # pylint: disable=broad-except
81             self._debug('Failed to load status from dump: %r' % e)
82
83         if not login:
84             self._debug("Empty Request dump?!")
85             # TODO: Return to SP with auth failed error
86             raise cherrypy.HTTPError(400)
87
88         return self.auth(login)
89
90
91 class SSO(ProviderPageBase):
92
93     def __init__(self, *args, **kwargs):
94         super(SSO, self).__init__(*args, **kwargs)
95         self.Redirect = Redirect(*args, **kwargs)
96         self.POST = POSTAuth(*args, **kwargs)
97         self.Continue = Continue(*args, **kwargs)
98
99
100 class SAML2(ProviderPageBase):
101
102     def __init__(self, *args, **kwargs):
103         super(SAML2, self).__init__(*args, **kwargs)
104
105         # Init IDP data
106         try:
107             self.cfg.idp = lasso.Server(self.cfg.idp_metadata_file,
108                                         self.cfg.idp_key_file,
109                                         None,
110                                         self.cfg.idp_certificate_file)
111             self.cfg.idp.role = lasso.PROVIDER_ROLE_IDP
112         except Exception, e:  # pylint: disable=broad-except
113             self._debug('Failed to enable SAML2 provider: %r' % e)
114             return
115
116         # Import all known applications
117         data = self.cfg.get_data()
118         for idval in data:
119             if 'type' not in data[idval] or data[idval]['type'] != 'SP':
120                 continue
121             path = os.path.join(self.cfg.idp_storage_path, str(idval))
122             sp = data[idval]
123             if 'name' in sp:
124                 name = sp['name']
125             else:
126                 name = str(idval)
127             try:
128                 meta = os.path.join(path, 'metadata.xml')
129                 cert = os.path.join(path, 'certificate.pem')
130                 self.cfg.idp.addProvider(lasso.PROVIDER_ROLE_SP, meta, cert)
131                 self._debug('Added SP %s' % name)
132             except Exception, e:  # pylint: disable=broad-except
133                 self._debug('Failed to add SP %s: %r' % (name, e))
134
135         self.SSO = SSO(*args, **kwargs)
136
137
138 class IdpProvider(ProviderBase):
139
140     def __init__(self):
141         super(IdpProvider, self).__init__('saml2', 'saml2')
142         self.page = None
143         self.description = """
144 Provides SAML 2.0 authentication infrastructure. """
145
146         self._options = {
147             'idp storage path': [
148                 """ Path to data storage accessible by the IdP """,
149                 'string',
150                 '/var/lib/ipsilon/saml2'
151             ],
152             'idp metadata file': [
153                 """ The IdP Metadata file genearated at install time. """,
154                 'string',
155                 'metadata.xml'
156             ],
157             'idp certificate file': [
158                 """ The IdP PEM Certificate genearated at install time. """,
159                 'string',
160                 'certificate.pem'
161             ],
162             'idp key file': [
163                 """ The IdP Certificate Key genearated at install time. """,
164                 'string',
165                 'certificate.key'
166             ],
167             'allow self registration': [
168                 """ Allow authenticated users to register applications. """,
169                 'boolean',
170                 True
171             ],
172             'default allowed nameids': [
173                 """Default Allowed NameIDs for Service Providers. """,
174                 'list',
175                 ['persistent', 'transient', 'email', 'kerberos', 'x509']
176             ],
177             'default nameid': [
178                 """Default NameID used by Service Providers. """,
179                 'string',
180                 'persistent'
181             ],
182             'default email domain': [
183                 """Default email domain, for users missing email property.""",
184                 'string',
185                 'example.com'
186             ]
187         }
188
189     @property
190     def allow_self_registration(self):
191         return self.get_config_value('allow self registration')
192
193     @property
194     def idp_storage_path(self):
195         return self.get_config_value('idp storage path')
196
197     @property
198     def idp_metadata_file(self):
199         return os.path.join(self.idp_storage_path,
200                             self.get_config_value('idp metadata file'))
201
202     @property
203     def idp_certificate_file(self):
204         return os.path.join(self.idp_storage_path,
205                             self.get_config_value('idp certificate file'))
206
207     @property
208     def idp_key_file(self):
209         return os.path.join(self.idp_storage_path,
210                             self.get_config_value('idp key file'))
211
212     @property
213     def default_allowed_nameids(self):
214         return self.get_config_value('default allowed nameids')
215
216     @property
217     def default_nameid(self):
218         return self.get_config_value('default nameid')
219
220     @property
221     def default_email_domain(self):
222         return self.get_config_value('default email domain')
223
224     def get_tree(self, site):
225         self.page = SAML2(site, self)
226         self.admin = AdminPage(site, self)
227         return self.page
228
229
230 class Installer(object):
231
232     def __init__(self):
233         self.name = 'saml2'
234         self.ptype = 'provider'
235
236     def install_args(self, group):
237         group.add_argument('--saml2', choices=['yes', 'no'], default='yes',
238                            help='Configure SAML2 Provider')
239         group.add_argument('--saml2-storage',
240                            default='/var/lib/ipsilon/saml2',
241                            help='SAML2 Provider storage area')
242
243     def configure(self, opts):
244         if opts['saml2'] != 'yes':
245             return
246
247         # Check storage path is present or create it
248         path = opts['saml2_storage']
249         if not os.path.exists(path):
250             os.makedirs(path, 0700)
251
252         # Use the same cert for signing and ecnryption for now
253         cert = Certificate(path)
254         cert.generate('idp', opts['hostname'])
255
256         # Generate Idp Metadata
257         url = 'https://' + opts['hostname'] + '/idp/saml2'
258         meta = metadata.Metadata(metadata.IDP_ROLE)
259         meta.set_entity_id(url + '/metadata')
260         meta.add_certs(cert, cert)
261         meta.add_service(metadata.SSO_SERVICE,
262                          lasso.SAML2_METADATA_BINDING_POST,
263                          url + '/POST')
264         meta.add_service(metadata.SSO_SERVICE,
265                          lasso.SAML2_METADATA_BINDING_REDIRECT,
266                          url + '/Redirect')
267
268         meta.add_allowed_name_format(
269             lasso.SAML2_NAME_IDENTIFIER_FORMAT_TRANSIENT)
270         meta.add_allowed_name_format(
271             lasso.SAML2_NAME_IDENTIFIER_FORMAT_PERSISTENT)
272         meta.add_allowed_name_format(
273             lasso.SAML2_NAME_IDENTIFIER_FORMAT_EMAIL)
274         if 'krb' in opts and opts['krb'] == 'yes':
275             meta.add_allowed_name_format(
276                 lasso.SAML2_NAME_IDENTIFIER_FORMAT_KERBEROS)
277
278         meta.output(os.path.join(path, 'metadata.xml'))
279
280         # Add configuration data to database
281         po = PluginObject()
282         po.name = 'saml2'
283         po.wipe_data()
284
285         po.wipe_config_values(FACILITY)
286         config = {'idp storage path': path,
287                   'idp metadata file': 'metadata.xml',
288                   'idp certificate file': cert.cert,
289                   'idp key file': cert.key}
290         po.set_config(config)
291         po.save_plugin_config(FACILITY)
292
293         # Fixup permissions so only the ipsilon user can read these files
294         pw = pwd.getpwnam(opts['system_user'])
295         for root, dirs, files in os.walk(path):
296             for name in dirs:
297                 target = os.path.join(root, name)
298                 os.chown(target, pw.pw_uid, pw.pw_gid)
299                 os.chmod(target, 0700)
300             for name in files:
301                 target = os.path.join(root, name)
302                 os.chown(target, pw.pw_uid, pw.pw_gid)
303                 os.chmod(target, 0600)