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 if nip is None or nip.format is None:
258 self.debug('No NameId requested, returning default [%s]'
259 % SAML2_NAMEID_MAP[self.default_nameid])
260 return SAML2_NAMEID_MAP[self.default_nameid]
262 self.debug('Requested NameId [%s]' % (nip.format,))
263 allowed = self.allowed_nameids
264 self.debug('Allowed NameIds %s' % (repr(allowed)))
265 for nameid in allowed:
266 if nip.format == SAML2_NAMEID_MAP[nameid]:
268 raise NameIdNotAllowed(nip.format)
270 def permanently_delete(self):
271 data = self.cfg.get_data(name='id', value=self.provider_id)
273 raise InvalidProviderId('Could not find SP data')
274 idval = data.keys()[0]
275 self.cfg.del_datum(idval)
277 def normalize_username(self, username):
278 if 'strip domain' in self._properties:
279 return username.split('@', 1)[0]
282 def is_valid_name(self, value):
283 if re.search(VALID_IN_NAME, value):
287 def is_valid_nameid(self, value):
288 if value in SAML2_NAMEID_MAP:
292 def valid_nameids(self):
293 return SAML2_NAMEID_MAP.keys()
296 class ServiceProviderCreator(object):
298 def __init__(self, config):
301 def create_from_buffer(self, name, metabuf, description='',
302 visible=True, imagefile='', splink=''):
303 '''Test and add data'''
305 if re.search(VALID_IN_NAME, name):
306 raise InvalidProviderId("Name must contain only "
307 "numbers and letters")
309 test = lasso.Server()
310 test.addProviderFromBuffer(lasso.PROVIDER_ROLE_SP, metabuf)
311 newsps = test.get_providers()
313 raise InvalidProviderId("Metadata must contain one Provider")
315 spid = newsps.keys()[0]
316 data = self.cfg.get_data(name='id', value=spid)
318 raise InvalidProviderId("Provider Already Exists")
324 'description': description,
326 'imagefile': imagefile,
329 self.cfg.new_datum(datum)
331 data = self.cfg.get_data(name='id', value=spid)
333 raise InvalidProviderId("Internal Error")
334 idval = data.keys()[0]
335 data = self.cfg.get_data(idval=idval)
337 self.cfg.idp.add_provider(sp)
339 return ServiceProvider(self.cfg, spid)
342 class IdentityProvider(Log):
343 def __init__(self, config, sessionfactory):
344 self.server = lasso.Server(config.idp_metadata_file,
347 config.idp_certificate_file)
348 self.server.role = lasso.PROVIDER_ROLE_IDP
349 self.sessionfactory = sessionfactory
351 def add_provider(self, sp):
352 self.server.addProviderFromBuffer(lasso.PROVIDER_ROLE_SP,
354 self.debug('Added SP %s' % sp['name'])
356 def get_login_handler(self, dump=None):
358 return lasso.Login.newFromDump(self.server, dump)
360 return lasso.Login(self.server)
362 def get_providers(self):
363 return self.server.get_providers()
365 def get_logout_handler(self, dump=None):
367 return lasso.Logout.newFromDump(self.server, dump)
369 return lasso.Logout(self.server)