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 ['Owner', 'Default NameID', 'Allowed NameIDs',
196 'Attribute Mapping', 'Allowed Attributes']:
197 if not self.user.is_admin:
198 raise UnauthorizedUser(
199 "Unauthorized to set %s" % key
202 # Make changes in current config
203 for name, option in conf.iteritems():
204 value = new_db_values.get(name, False)
205 # A value of None means remove from the data store
206 if value is False or value == []:
209 if not self.sp.is_valid_name(value):
210 raise InvalidValueFormat(
211 'Invalid name! Use only numbers and'
215 self.url = '%s/sp/%s' % (self.parent.url, value)
216 self.parent.rename_sp(option.get_value(), value)
217 elif name == 'User Owner':
218 self.sp.owner = value
219 elif name == 'Default NameID':
220 self.sp.default_nameid = value
221 elif name == 'Allowed NameIDs':
222 self.sp.allowed_nameids = value
223 elif name == 'Attribute Mapping':
224 self.sp.attribute_mappings = value
225 elif name == 'Allowed Attributes':
226 self.sp.allowed_attributes = value
227 except InvalidValueFormat, e:
229 message_type = ADMIN_STATUS_WARN
230 return self.root_with_msg(message, message_type)
231 except UnauthorizedUser, e:
233 message_type = ADMIN_STATUS_ERROR
234 return self.root_with_msg(message, message_type)
235 except Exception as e: # pylint: disable=broad-except
236 self.debug("Error: %s" % repr(e))
237 message = "Internal Error"
238 message_type = ADMIN_STATUS_ERROR
239 return self.root_with_msg(message, message_type)
242 self.sp.save_properties()
243 message = "Properties successfully changed"
244 message_type = ADMIN_STATUS_OK
245 except Exception as e: # pylint: disable=broad-except
246 self.error('Failed to save data: %s' % e)
247 message = "Failed to save data!"
248 message_type = ADMIN_STATUS_ERROR
250 self.sp.refresh_config()
252 return self.root_with_msg(message=message,
253 message_type=message_type)
256 self.parent.del_sp(self.sp.name)
257 self.sp.permanently_delete()
258 return self.parent.root()
259 delete.public_function = True
262 class Saml2AdminPage(AdminPage):
263 def __init__(self, site, config):
264 super(Saml2AdminPage, self).__init__(site)
270 self.sp = AdminPage(self._site)
272 def add_sp(self, name, sp):
273 page = SPAdminPage(sp, self._site, self)
274 self.sp.add_subtree(name, page)
275 self.providers.append(sp)
278 def rename_sp(self, oldname, newname):
279 page = getattr(self.sp, oldname)
280 self.sp.del_subtree(oldname)
281 self.sp.add_subtree(newname, page)
283 def del_sp(self, name):
285 page = getattr(self.sp, name)
286 self.providers.remove(page.sp)
287 self.sp.del_subtree(name)
288 except Exception, e: # pylint: disable=broad-except
289 self.debug("Failed to remove provider %s: %s" % (name, str(e)))
293 for p in self.cfg.idp.get_providers():
295 sp = ServiceProvider(self.cfg, p)
297 self.add_sp(sp.name, sp)
298 except Exception, e: # pylint: disable=broad-except
299 self.debug("Failed to find provider %s: %s" % (p, str(e)))
301 def mount(self, page):
302 self.menu = page.menu
303 self.url = '%s/%s' % (page.url, self.name)
305 self.add_subtree('new', NewSPAdminPage(self._site, self))
306 page.add_subtree(self.name, self)
308 def root(self, *args, **kwargs):
309 return self._template('admin/providers/saml2.html',
310 title='SAML2 Administration',
311 providers=self.providers,