1 # Copyright (C) 2014 Ipsilon project Contributors, for license see COPYING
4 from ipsilon.util import config as pconfig
5 from ipsilon.admin.common import AdminPage
6 from ipsilon.admin.common import ADMIN_STATUS_OK
7 from ipsilon.admin.common import ADMIN_STATUS_ERROR
8 from ipsilon.admin.common import ADMIN_STATUS_WARN
9 from ipsilon.admin.common import get_mapping_list_value
10 from ipsilon.admin.common import get_complex_list_value
11 from ipsilon.providers.saml2.provider import ServiceProvider
12 from ipsilon.providers.saml2.provider import ServiceProviderCreator
13 from ipsilon.providers.saml2.provider import InvalidProviderId
14 from copy import deepcopy
19 class NewSPAdminPage(AdminPage):
21 def __init__(self, site, parent):
22 super(NewSPAdminPage, self).__init__(site, form=True)
24 self.title = 'New Service Provider'
25 self.back = parent.url
26 self.url = '%s/new' % (parent.url,)
28 def form_new(self, message=None, message_type=None):
29 return self._template('admin/providers/saml2_sp_new.html',
32 message_type=message_type,
33 name='saml2_sp_new_form',
34 back=self.back, action=self.url)
36 def GET(self, *args, **kwargs):
37 return self.form_new()
39 def POST(self, *args, **kwargs):
41 if self.user.is_admin:
42 # TODO: allow authenticated user to create SPs on their own
43 # set the owner in that case
46 if 'content-type' not in cherrypy.request.headers:
47 self.debug("Invalid request, missing content-type")
48 message = "Malformed request"
49 message_type = ADMIN_STATUS_ERROR
50 return self.form_new(message, message_type)
51 ctype = cherrypy.request.headers['content-type'].split(';')[0]
52 if ctype != 'multipart/form-data':
53 self.debug("Invalid form type (%s), trying to cope" % (
54 cherrypy.request.content_type,))
55 for key, value in kwargs.iteritems():
58 elif key == 'metatext':
61 elif key == 'metafile':
62 if hasattr(value, 'content_type'):
63 meta = value.fullvalue()
65 self.debug("Invalid format for 'meta'")
66 elif key == 'metaurl':
69 r = requests.get(value)
72 except Exception, e: # pylint: disable=broad-except
73 self.debug("Failed to fetch metadata: " + repr(e))
74 message = "Failed to fetch metadata: " + repr(e)
75 message_type = ADMIN_STATUS_ERROR
76 return self.form_new(message, message_type)
80 spc = ServiceProviderCreator(self.parent.cfg)
81 sp = spc.create_from_buffer(name, meta)
82 sp_page = self.parent.add_sp(name, sp)
83 message = "SP Successfully added"
84 message_type = ADMIN_STATUS_OK
85 return sp_page.root_with_msg(message, message_type)
86 except InvalidProviderId, e:
88 message_type = ADMIN_STATUS_ERROR
89 except Exception, e: # pylint: disable=broad-except
91 message = "Failed to create Service Provider!"
92 message_type = ADMIN_STATUS_ERROR
94 message = "A name and a metadata file must be provided"
95 message_type = ADMIN_STATUS_ERROR
97 message = "Unauthorized"
98 message_type = ADMIN_STATUS_ERROR
100 return self.form_new(message, message_type)
103 class InvalidValueFormat(Exception):
107 class UnauthorizedUser(Exception):
111 class SPAdminPage(AdminPage):
113 def __init__(self, sp, site, parent):
114 super(SPAdminPage, self).__init__(site, form=True)
118 self.url = '%s/sp/%s' % (parent.url, sp.name)
120 self.back = parent.url
122 def root_with_msg(self, message=None, message_type=None):
123 return self._template('admin/option_config.html', title=self.title,
124 menu=self.menu, action=self.url, back=self.back,
125 message=message, message_type=message_type,
126 name='saml2_sp_%s_form' % (self.sp.name),
127 config=self.sp.get_config_obj())
129 def GET(self, *args, **kwargs):
130 return self.root_with_msg()
132 def POST(self, *args, **kwargs):
134 message = "Nothing was modified."
135 message_type = "info"
136 new_db_values = dict()
138 conf = self.sp.get_config_obj()
140 for name, option in conf.iteritems():
143 if isinstance(option, pconfig.List):
144 value = [x.strip() for x in value.split('\n')]
145 # for normal lists we want unordered comparison
146 if set(value) == set(option.get_value()):
148 elif isinstance(option, pconfig.Condition):
151 if isinstance(option, pconfig.Condition):
153 elif isinstance(option, pconfig.Choice):
155 for a in option.get_allowed():
156 aname = '%s_%s' % (name, a)
159 elif isinstance(option, pconfig.MappingList):
160 current = deepcopy(option.get_value())
161 value = get_mapping_list_value(name,
164 # if current value is None do nothing
166 if option.get_value() is None:
168 # else pass and let it continue as None
169 elif isinstance(option, pconfig.ComplexList):
170 current = deepcopy(option.get_value())
171 value = get_complex_list_value(name,
174 # if current value is None do nothing
176 if option.get_value() is None:
178 # else pass and let it continue as None
182 if value != option.get_value():
183 cherrypy.log.error("Storing %s = %s" %
184 (name, value), severity=logging.DEBUG)
185 new_db_values[name] = value
187 if len(new_db_values) != 0:
189 # Validate user can make these changes
190 for (key, value) in new_db_values.iteritems():
192 if (not self.user.is_admin and
193 self.user.name != self.sp.owner):
194 raise UnauthorizedUser("Unauthorized to set owner")
195 elif key in ['User Owner', 'Default NameID',
196 'Allowed NameIDs', 'Attribute Mapping',
197 'Allowed Attributes']:
198 if not self.user.is_admin:
199 raise UnauthorizedUser(
200 "Unauthorized to set %s" % key
203 # Make changes in current config
204 for name, option in conf.iteritems():
205 value = new_db_values.get(name, False)
206 # A value of None means remove from the data store
207 if value is False or value == []:
210 if not self.sp.is_valid_name(value):
211 raise InvalidValueFormat(
212 'Invalid name! Use only numbers and'
216 self.url = '%s/sp/%s' % (self.parent.url, value)
217 self.parent.rename_sp(option.get_value(), value)
218 elif name == 'User Owner':
219 self.sp.owner = value
220 elif name == 'Default NameID':
221 self.sp.default_nameid = value
222 elif name == 'Allowed NameIDs':
223 self.sp.allowed_nameids = value
224 elif name == 'Attribute Mapping':
225 self.sp.attribute_mappings = value
226 elif name == 'Allowed Attributes':
227 self.sp.allowed_attributes = value
228 except InvalidValueFormat, e:
230 message_type = ADMIN_STATUS_WARN
231 return self.root_with_msg(message, message_type)
232 except UnauthorizedUser, e:
234 message_type = ADMIN_STATUS_ERROR
235 return self.root_with_msg(message, message_type)
236 except Exception as e: # pylint: disable=broad-except
237 self.debug("Error: %s" % repr(e))
238 message = "Internal Error"
239 message_type = ADMIN_STATUS_ERROR
240 return self.root_with_msg(message, message_type)
243 self.sp.save_properties()
244 message = "Properties successfully changed"
245 message_type = ADMIN_STATUS_OK
246 except Exception as e: # pylint: disable=broad-except
247 self.error('Failed to save data: %s' % e)
248 message = "Failed to save data!"
249 message_type = ADMIN_STATUS_ERROR
251 self.sp.refresh_config()
253 return self.root_with_msg(message=message,
254 message_type=message_type)
257 self.parent.del_sp(self.sp.name)
258 self.sp.permanently_delete()
259 return self.parent.root()
260 delete.public_function = True
263 class Saml2AdminPage(AdminPage):
264 def __init__(self, site, config):
265 super(Saml2AdminPage, self).__init__(site)
271 self.sp = AdminPage(self._site)
273 def add_sp(self, name, sp):
274 page = SPAdminPage(sp, self._site, self)
275 self.sp.add_subtree(name, page)
276 self.providers.append(sp)
279 def rename_sp(self, oldname, newname):
280 page = getattr(self.sp, oldname)
281 self.sp.del_subtree(oldname)
282 self.sp.add_subtree(newname, page)
284 def del_sp(self, name):
286 page = getattr(self.sp, name)
287 self.providers.remove(page.sp)
288 self.sp.del_subtree(name)
289 except Exception, e: # pylint: disable=broad-except
290 self.debug("Failed to remove provider %s: %s" % (name, str(e)))
294 for p in self.cfg.idp.get_providers():
296 sp = ServiceProvider(self.cfg, p)
298 self.add_sp(sp.name, sp)
299 except Exception, e: # pylint: disable=broad-except
300 self.debug("Failed to find provider %s: %s" % (p, str(e)))
302 def mount(self, page):
303 self.menu = page.menu
304 self.url = '%s/%s' % (page.url, self.name)
306 self.add_subtree('new', NewSPAdminPage(self._site, self))
307 page.add_subtree(self.name, self)
309 def root(self, *args, **kwargs):
310 return self._template('admin/providers/saml2.html',
311 title='SAML2 Administration',
312 providers=self.providers,