Fix exposed functions
[cascardo/ipsilon.git] / ipsilon / providers / saml2 / admin.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 import cherrypy
21 from ipsilon.util.page import Page
22 from ipsilon.providers.saml2.provider import ServiceProvider
23 from ipsilon.providers.saml2.provider import ServiceProviderCreator
24 from ipsilon.providers.saml2.provider import InvalidProviderId
25 import re
26 import requests
27
28
29 VALID_IN_NAME = r'[^\ a-zA-Z0-9]'
30
31
32 class NewSPAdminPage(Page):
33
34     def __init__(self, site, parent):
35         super(NewSPAdminPage, self).__init__(site, form=True)
36         self.parent = parent
37         self.title = 'New Service Provider'
38         self.backurl = parent.url
39         self.url = '%s/new' % (parent.url,)
40
41     def form_new(self, message=None, message_type=None):
42         return self._template('admin/providers/saml2_sp_new.html',
43                               title=self.title,
44                               message=message,
45                               message_type=message_type,
46                               name='saml2_sp_new_form',
47                               backurl=self.backurl, action=self.url)
48
49     def GET(self, *args, **kwargs):
50         return self.form_new()
51
52     def POST(self, *args, **kwargs):
53
54         if self.user.is_admin:
55             # TODO: allow authenticated user to create SPs on their own
56             #       set the owner in that case
57             name = None
58             meta = None
59             if 'content-type' not in cherrypy.request.headers:
60                 self._debug("Invalid request, missing content-type")
61                 message = "Malformed request"
62                 message_type = "error"
63                 return self.form_new(message, message_type)
64             ctype = cherrypy.request.headers['content-type'].split(';')[0]
65             if ctype != 'multipart/form-data':
66                 self._debug("Invalid form type (%s), trying to cope" % (
67                             cherrypy.request.content_type,))
68             for key, value in kwargs.iteritems():
69                 if key == 'name':
70                     if re.search(VALID_IN_NAME, value):
71                         message = "Invalid name!" \
72                                   " Use only numbers and letters"
73                         message_type = "error"
74                         return self.form_new(message, message_type)
75
76                     name = value
77                 elif key == 'metatext':
78                     if len(value) > 0:
79                         meta = value
80                 elif key == 'metafile':
81                     if hasattr(value, 'content_type'):
82                         meta = value.fullvalue()
83                     else:
84                         self._debug("Invalid format for 'meta'")
85                 elif key == 'metaurl':
86                     if len(value) > 0:
87                         try:
88                             r = requests.get(value)
89                             r.raise_for_status()
90                             meta = r.content
91                         except Exception, e:  # pylint: disable=broad-except
92                             self._debug("Failed to fetch metadata: " + repr(e))
93                             message = "Failed to fetch metadata: " + repr(e)
94                             message_type = "error"
95                             return self.form_new(message, message_type)
96
97             if name and meta:
98                 try:
99                     spc = ServiceProviderCreator(self.parent.cfg)
100                     sp = spc.create_from_buffer(name, meta)
101                     sp_page = self.parent.add_sp(name, sp)
102                     message = "SP Successfully added"
103                     message_type = "success"
104                     return sp_page.form_standard(message, message_type)
105                 except InvalidProviderId, e:
106                     message = str(e)
107                     message_type = "error"
108                 except Exception, e:  # pylint: disable=broad-except
109                     self._debug(repr(e))
110                     message = "Failed to create Service Provider!"
111                     message_type = "error"
112             else:
113                 message = "A name and a metadata file must be provided"
114                 message_type = "error"
115         else:
116             message = "Unauthorized"
117             message_type = "error"
118
119         return self.form_new(message, message_type)
120
121
122 class InvalidValueFormat(Exception):
123     pass
124
125
126 class UnauthorizedUser(Exception):
127     pass
128
129
130 class SPAdminPage(Page):
131
132     def __init__(self, sp, site, parent):
133         super(SPAdminPage, self).__init__(site, form=True)
134         self.parent = parent
135         self.sp = sp
136         self.title = sp.name
137         self.backurl = parent.url
138         self.url = '%s/sp/%s' % (parent.url, sp.name)
139
140     def form_standard(self, message=None, message_type=None, newurl=None):
141         return self._template('admin/providers/saml2_sp.html',
142                               message=message,
143                               message_type=message_type,
144                               title=self.title,
145                               name='saml2_sp_%s_form' % self.sp.name,
146                               backurl=self.backurl, action=self.url,
147                               data=self.sp, newurl=newurl)
148
149     def GET(self, *args, **kwargs):
150         return self.form_standard()
151
152     def change_name(self, key, value):
153
154         if value == self.sp.name:
155             return False
156
157         if self.user.is_admin or self.user.name == self.sp.owner:
158             if re.search(VALID_IN_NAME, value):
159                 err = "Invalid name! Use only numbers and letters"
160                 raise InvalidValueFormat(err)
161
162             self._debug("Replacing %s: %s -> %s" % (key, self.sp.name, value))
163             return {'name': value, 'rename': [self.sp.name, value]}
164         else:
165             raise UnauthorizedUser("Unauthorized to rename Service Provider")
166
167     def change_owner(self, key, value):
168         if value == self.sp.owner:
169             return False
170
171         if self.user.is_admin:
172             self._debug("Replacing %s: %s -> %s" % (key, self.sp.owner, value))
173             return {'owner': value}
174         else:
175             raise UnauthorizedUser("Unauthorized to set owner value")
176
177     def change_default_nameid(self, key, value):
178         if value == self.sp.default_nameid:
179             return False
180
181         if self.user.is_admin:
182             self._debug("Replacing %s: %s -> %s" % (key,
183                                                     self.sp.default_nameid,
184                                                     value))
185             if not self.sp.is_valid_nameid(value):
186                 raise InvalidValueFormat('Invalid default nameid value')
187             return {'default_nameid': value}
188         else:
189             raise UnauthorizedUser("Unauthorized to set default nameid value")
190
191     def change_allowed_nameids(self, key, value):
192         v = set([x.strip() for x in value.split(',')])
193         if v == set(self.sp.allowed_nameids):
194             return False
195
196         if self.user.is_admin:
197             self._debug("Replacing %s: %s -> %s" % (key,
198                                                     self.sp.allowed_nameids,
199                                                     list(v)))
200             for x in v:
201                 if not self.sp.is_valid_nameid(x):
202                     l = ', '.join(self.sp.valid_nameids())
203                     err = 'Invalid nameid [%s]. Available [%s].' % (x, l)
204                     raise InvalidValueFormat(err)
205             return {'allowed_nameids': list(v)}
206         else:
207             raise UnauthorizedUser("Unauthorized to set alowed nameids values")
208
209     def POST(self, *args, **kwargs):
210
211         message = "Nothing was modified."
212         message_type = "info"
213         results = dict()
214
215         try:
216             for key, value in kwargs.iteritems():
217                 if key == 'name':
218                     r = self.change_name(key, value)
219                     if r:
220                         results.update(r)
221                 elif key == 'owner':
222                     r = self.change_owner(key, value)
223                     if r:
224                         results.update(r)
225
226                 elif key == 'default_nameid':
227                     r = self.change_default_nameid(key, value)
228                     if r:
229                         results.update(r)
230
231                 elif key == 'allowed_nameids':
232                     r = self.change_allowed_nameids(key, value)
233                     if r:
234                         results.update(r)
235
236         except InvalidValueFormat, e:
237             message = str(e)
238             message_type = "warning"
239             return self.form_standard(message, message_type)
240         except UnauthorizedUser, e:
241             message = str(e)
242             message_type = "error"
243             return self.form_standard(message, message_type)
244         except Exception, e:  # pylint: disable=broad-except
245             self._debug("Error: %s" % repr(e))
246             message = "Internal Error"
247             message_type = "error"
248             return self.form_standard(message, message_type)
249
250         if len(results) > 0:
251             try:
252                 if 'name' in results:
253                     self.sp.name = results['name']
254                 if 'owner' in results:
255                     self.sp.owner = results['owner']
256                 if 'default_nameid' in results:
257                     self.sp.default_nameid = results['default_nameid']
258                 if 'allowed_nameids' in results:
259                     self.sp.allowed_nameids = results['allowed_nameids']
260                 self.sp.save_properties()
261                 if 'rename' in results:
262                     rename = results['rename']
263                     self.url = '%s/sp/%s' % (self.parent.url, rename[1])
264                     self.parent.rename_sp(rename[0], rename[1])
265                 message = "Properties successfully changed"
266                 message_type = "success"
267             except Exception:  # pylint: disable=broad-except
268                 message = "Failed to save data!"
269                 message_type = "error"
270
271         return self.form_standard(message, message_type, self.url)
272
273     def delete(self):
274         self.parent.del_sp(self.sp.name)
275         self.sp.permanently_delete()
276         return self.parent.root()
277     delete.public_function = True
278
279
280 class AdminPage(Page):
281     def __init__(self, site, config):
282         super(AdminPage, self).__init__(site)
283         self.name = 'admin'
284         self.cfg = config
285         self.providers = []
286         self.menu = []
287         self.url = None
288         self.sp = Page(self._site)
289
290     def add_sp(self, name, sp):
291         page = SPAdminPage(sp, self._site, self)
292         self.sp.add_subtree(name, page)
293         self.providers.append(sp)
294         return page
295
296     def rename_sp(self, oldname, newname):
297         page = getattr(self.sp, oldname)
298         self.sp.del_subtree(oldname)
299         self.sp.add_subtree(newname, page)
300
301     def del_sp(self, name):
302         try:
303             page = getattr(self.sp, name)
304             self.providers.remove(page.sp)
305             self.sp.del_subtree(name)
306         except Exception, e:  # pylint: disable=broad-except
307             self._debug("Failed to remove provider %s: %s" % (name, str(e)))
308
309     def add_sps(self):
310         if self.cfg.idp:
311             for p in self.cfg.idp.get_providers():
312                 try:
313                     sp = ServiceProvider(self.cfg, p)
314                     self.del_sp(sp.name)
315                     self.add_sp(sp.name, sp)
316                 except Exception, e:  # pylint: disable=broad-except
317                     self._debug("Failed to find provider %s: %s" % (p, str(e)))
318
319     def mount(self, page):
320         self.menu = page.menu
321         self.url = '%s/%s' % (page.url, self.name)
322         self.add_sps()
323         self.add_subtree('new', NewSPAdminPage(self._site, self))
324         page.add_subtree(self.name, self)
325
326     def root(self, *args, **kwargs):
327         return self._template('admin/providers/saml2.html',
328                               title='SAML2 Administration',
329                               providers=self.providers,
330                               baseurl=self.url,
331                               menu=self.menu)