1 # Copyright (C) 2014 Ipsilon project Contributors, for license see COPYING
3 from ipsilon.providers.common import ProviderException
4 from ipsilon.util import config as pconfig
5 from ipsilon.util.config import ConfigHelper
6 from ipsilon.tools.saml2metadata import SAML2_NAMEID_MAP, NSMAP
7 from ipsilon.util.log import Log
13 VALID_IN_NAME = r'[^\ a-zA-Z0-9]'
16 class InvalidProviderId(ProviderException):
18 def __init__(self, code):
19 message = 'Invalid Provider ID: %s' % code
20 super(InvalidProviderId, self).__init__(message)
24 class NameIdNotAllowed(Exception):
26 def __init__(self, nid):
27 message = 'Name ID [%s] is not allowed' % nid
28 super(NameIdNotAllowed, self).__init__(message)
29 self.message = message
32 return repr(self.message)
35 class ServiceProviderConfig(ConfigHelper):
37 super(ServiceProviderConfig, self).__init__()
40 class ServiceProvider(ServiceProviderConfig):
42 def __init__(self, config, provider_id):
43 super(ServiceProvider, self).__init__()
45 data = self.cfg.get_data(name='id', value=provider_id)
47 raise InvalidProviderId('multiple matches')
48 idval = data.keys()[0]
49 data = self.cfg.get_data(idval=idval)
50 self._properties = data[idval]
51 self._staging = dict()
53 self.logout_mechs = []
54 xmldoc = etree.XML(str(data[idval]['metadata']))
55 logout = xmldoc.xpath('//md:EntityDescriptor'
57 '/md:SingleLogoutService',
59 for service in logout:
60 self.logout_mechs.append(service.values()[0])
62 def load_config(self):
67 'A nickname used to easily identify the Service Provider.'
68 ' Only alphanumeric characters [A-Z,a-z,0-9] and spaces are'
73 'A description of the SP to show on the Portal.',
76 'Service Provider link',
77 'A link to the Service Provider for the Portal.',
81 'This SP is visible in the Portal.',
85 'Image to display for this SP in the Portal. Scale to '
86 '100x200 for best results.',
90 'Default NameID used by Service Providers.',
91 SAML2_NAMEID_MAP.keys(),
95 'Allowed NameIDs for this Service Provider.',
96 SAML2_NAMEID_MAP.keys(),
97 self.allowed_nameids),
100 'The user that owns this Service Provider',
104 'Defines how to map attributes before returning them to'
105 ' the SP. Setting this overrides the global values.',
106 self.attribute_mappings),
108 'Allowed Attributes',
109 'Defines a list of allowed attributes, applied after mapping.'
110 ' Setting this overrides the global values.',
111 self.allowed_attributes),
115 def provider_id(self):
116 return self._properties['id']
120 return self._properties['name']
123 def name(self, value):
124 self._staging['name'] = value
127 def description(self):
128 return self._properties.get('description', '')
131 def description(self, value):
132 self._staging['description'] = value
136 return self._properties.get('visible', True)
139 def visible(self, value):
140 self._staging['visible'] = value
144 return self._properties.get('imagefile', '')
147 def imagefile(self, value):
148 self._staging['imagefile'] = value
152 return pconfig.url_from_image(self._properties['imagefile'])
156 return self._properties.get('splink', '')
159 def splink(self, value):
160 self._staging['splink'] = value
164 if 'owner' in self._properties:
165 return self._properties['owner']
170 def owner(self, value):
171 self._staging['owner'] = value
174 def allowed_nameids(self):
175 if 'allowed nameids' in self._properties:
176 allowed = self._properties['allowed nameids']
177 return [x.strip() for x in allowed.split(',')]
179 return self.cfg.default_allowed_nameids
181 @allowed_nameids.setter
182 def allowed_nameids(self, value):
183 if not isinstance(value, list):
184 raise ValueError("Must be a list")
185 self._staging['allowed nameids'] = ','.join(value)
188 def default_nameid(self):
189 if 'default nameid' in self._properties:
190 return self._properties['default nameid']
192 return self.cfg.default_nameid
194 @default_nameid.setter
195 def default_nameid(self, value):
196 self._staging['default nameid'] = value
199 def attribute_mappings(self):
200 if 'attribute mappings' in self._properties:
201 attr_map = pconfig.MappingList('temp', 'temp', None)
202 attr_map.import_value(str(self._properties['attribute mappings']))
203 return attr_map.get_value()
207 @attribute_mappings.setter
208 def attribute_mappings(self, attr_map):
209 if isinstance(attr_map, pconfig.MappingList):
210 value = attr_map.export_value()
212 temp = pconfig.MappingList('temp', 'temp', None)
213 temp.set_value(attr_map)
214 value = temp.export_value()
215 self._staging['attribute mappings'] = value
218 def allowed_attributes(self):
219 if 'allowed_attributes' in self._properties:
220 attr_map = pconfig.ComplexList('temp', 'temp', None)
221 attr_map.import_value(str(self._properties['allowed_attributes']))
222 return attr_map.get_value()
226 @allowed_attributes.setter
227 def allowed_attributes(self, attr_map):
228 if isinstance(attr_map, pconfig.ComplexList):
229 value = attr_map.export_value()
231 temp = pconfig.ComplexList('temp', 'temp', None)
232 temp.set_value(attr_map)
233 value = temp.export_value()
234 self._staging['allowed_attributes'] = value
236 def save_properties(self):
237 data = self.cfg.get_data(name='id', value=self.provider_id)
239 raise InvalidProviderId('Could not find SP data')
240 idval = data.keys()[0]
242 data[idval] = self._staging
243 self.cfg.save_data(data)
244 data = self.cfg.get_data(idval=idval)
245 self._properties = data[idval]
246 self._staging = dict()
248 def refresh_config(self):
250 Create a new config object for displaying in the UI based on
251 the current set of properties.
256 def get_valid_nameid(self, nip):
257 self.debug('Requested NameId [%s]' % (nip.format,))
258 if nip.format is None:
259 return SAML2_NAMEID_MAP[self.default_nameid]
261 allowed = self.allowed_nameids
262 self.debug('Allowed NameIds %s' % (repr(allowed)))
263 for nameid in allowed:
264 if nip.format == SAML2_NAMEID_MAP[nameid]:
266 raise NameIdNotAllowed(nip.format)
268 def permanently_delete(self):
269 data = self.cfg.get_data(name='id', value=self.provider_id)
271 raise InvalidProviderId('Could not find SP data')
272 idval = data.keys()[0]
273 self.cfg.del_datum(idval)
275 def normalize_username(self, username):
276 if 'strip domain' in self._properties:
277 return username.split('@', 1)[0]
280 def is_valid_name(self, value):
281 if re.search(VALID_IN_NAME, value):
285 def is_valid_nameid(self, value):
286 if value in SAML2_NAMEID_MAP:
290 def valid_nameids(self):
291 return SAML2_NAMEID_MAP.keys()
294 class ServiceProviderCreator(object):
296 def __init__(self, config):
299 def create_from_buffer(self, name, metabuf, description='',
300 visible=True, imagefile='', splink=''):
301 '''Test and add data'''
303 if re.search(VALID_IN_NAME, name):
304 raise InvalidProviderId("Name must contain only "
305 "numbers and letters")
307 test = lasso.Server()
308 test.addProviderFromBuffer(lasso.PROVIDER_ROLE_SP, metabuf)
309 newsps = test.get_providers()
311 raise InvalidProviderId("Metadata must contain one Provider")
313 spid = newsps.keys()[0]
314 data = self.cfg.get_data(name='id', value=spid)
316 raise InvalidProviderId("Provider Already Exists")
322 'description': description,
324 'imagefile': imagefile,
327 self.cfg.new_datum(datum)
329 data = self.cfg.get_data(name='id', value=spid)
331 raise InvalidProviderId("Internal Error")
332 idval = data.keys()[0]
333 data = self.cfg.get_data(idval=idval)
335 self.cfg.idp.add_provider(sp)
337 return ServiceProvider(self.cfg, spid)
340 class IdentityProvider(Log):
341 def __init__(self, config, sessionfactory):
342 self.server = lasso.Server(config.idp_metadata_file,
345 config.idp_certificate_file)
346 self.server.role = lasso.PROVIDER_ROLE_IDP
347 self.sessionfactory = sessionfactory
349 def add_provider(self, sp):
350 self.server.addProviderFromBuffer(lasso.PROVIDER_ROLE_SP,
352 self.debug('Added SP %s' % sp['name'])
354 def get_login_handler(self, dump=None):
356 return lasso.Login.newFromDump(self.server, dump)
358 return lasso.Login(self.server)
360 def get_providers(self):
361 return self.server.get_providers()
363 def get_logout_handler(self, dump=None):
365 return lasso.Logout.newFromDump(self.server, dump)
367 return lasso.Logout(self.server)