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 'Default NameID used by Service Providers.',
74 SAML2_NAMEID_MAP.keys(),
78 'Allowed NameIDs for this Service Provider.',
79 SAML2_NAMEID_MAP.keys(),
80 self.allowed_nameids),
83 'The user that owns this Service Provider',
87 'Defines how to map attributes before returning them to'
88 ' the SP. Setting this overrides the global values.',
89 self.attribute_mappings),
92 'Defines a list of allowed attributes, applied after mapping.'
93 ' Setting this overrides the global values.',
94 self.allowed_attributes),
98 def provider_id(self):
99 return self._properties['id']
103 return self._properties['name']
106 def name(self, value):
107 self._staging['name'] = value
111 if 'owner' in self._properties:
112 return self._properties['owner']
117 def owner(self, value):
118 self._staging['owner'] = value
121 def allowed_nameids(self):
122 if 'allowed nameids' in self._properties:
123 allowed = self._properties['allowed nameids']
124 return [x.strip() for x in allowed.split(',')]
126 return self.cfg.default_allowed_nameids
128 @allowed_nameids.setter
129 def allowed_nameids(self, value):
130 if not isinstance(value, list):
131 raise ValueError("Must be a list")
132 self._staging['allowed nameids'] = ','.join(value)
135 def default_nameid(self):
136 if 'default nameid' in self._properties:
137 return self._properties['default nameid']
139 return self.cfg.default_nameid
141 @default_nameid.setter
142 def default_nameid(self, value):
143 self._staging['default nameid'] = value
146 def attribute_mappings(self):
147 if 'attribute mappings' in self._properties:
148 attr_map = pconfig.MappingList('temp', 'temp', None)
149 attr_map.import_value(str(self._properties['attribute mappings']))
150 return attr_map.get_value()
154 @attribute_mappings.setter
155 def attribute_mappings(self, attr_map):
156 if isinstance(attr_map, pconfig.MappingList):
157 value = attr_map.export_value()
159 temp = pconfig.MappingList('temp', 'temp', None)
160 temp.set_value(attr_map)
161 value = temp.export_value()
162 self._staging['attribute mappings'] = value
165 def allowed_attributes(self):
166 if 'allowed_attributes' in self._properties:
167 attr_map = pconfig.ComplexList('temp', 'temp', None)
168 attr_map.import_value(str(self._properties['allowed_attributes']))
169 return attr_map.get_value()
173 @allowed_attributes.setter
174 def allowed_attributes(self, attr_map):
175 if isinstance(attr_map, pconfig.ComplexList):
176 value = attr_map.export_value()
178 temp = pconfig.ComplexList('temp', 'temp', None)
179 temp.set_value(attr_map)
180 value = temp.export_value()
181 self._staging['allowed_attributes'] = value
183 def save_properties(self):
184 data = self.cfg.get_data(name='id', value=self.provider_id)
186 raise InvalidProviderId('Could not find SP data')
187 idval = data.keys()[0]
189 data[idval] = self._staging
190 self.cfg.save_data(data)
191 data = self.cfg.get_data(idval=idval)
192 self._properties = data[idval]
193 self._staging = dict()
195 def refresh_config(self):
197 Create a new config object for displaying in the UI based on
198 the current set of properties.
203 def get_valid_nameid(self, nip):
204 self.debug('Requested NameId [%s]' % (nip.format,))
205 if nip.format is None:
206 return SAML2_NAMEID_MAP[self.default_nameid]
208 allowed = self.allowed_nameids
209 self.debug('Allowed NameIds %s' % (repr(allowed)))
210 for nameid in allowed:
211 if nip.format == SAML2_NAMEID_MAP[nameid]:
213 raise NameIdNotAllowed(nip.format)
215 def permanently_delete(self):
216 data = self.cfg.get_data(name='id', value=self.provider_id)
218 raise InvalidProviderId('Could not find SP data')
219 idval = data.keys()[0]
220 self.cfg.del_datum(idval)
222 def normalize_username(self, username):
223 if 'strip domain' in self._properties:
224 return username.split('@', 1)[0]
227 def is_valid_name(self, value):
228 if re.search(VALID_IN_NAME, value):
232 def is_valid_nameid(self, value):
233 if value in SAML2_NAMEID_MAP:
237 def valid_nameids(self):
238 return SAML2_NAMEID_MAP.keys()
241 class ServiceProviderCreator(object):
243 def __init__(self, config):
246 def create_from_buffer(self, name, metabuf):
247 '''Test and add data'''
249 if re.search(VALID_IN_NAME, name):
250 raise InvalidProviderId("Name must contain only "
251 "numbers and letters")
253 test = lasso.Server()
254 test.addProviderFromBuffer(lasso.PROVIDER_ROLE_SP, metabuf)
255 newsps = test.get_providers()
257 raise InvalidProviderId("Metadata must contain one Provider")
259 spid = newsps.keys()[0]
260 data = self.cfg.get_data(name='id', value=spid)
262 raise InvalidProviderId("Provider Already Exists")
263 datum = {'id': spid, 'name': name, 'type': 'SP', 'metadata': metabuf}
264 self.cfg.new_datum(datum)
266 data = self.cfg.get_data(name='id', value=spid)
268 raise InvalidProviderId("Internal Error")
269 idval = data.keys()[0]
270 data = self.cfg.get_data(idval=idval)
272 self.cfg.idp.add_provider(sp)
274 return ServiceProvider(self.cfg, spid)
277 class IdentityProvider(Log):
278 def __init__(self, config, sessionfactory):
279 self.server = lasso.Server(config.idp_metadata_file,
282 config.idp_certificate_file)
283 self.server.role = lasso.PROVIDER_ROLE_IDP
284 self.sessionfactory = sessionfactory
286 def add_provider(self, sp):
287 self.server.addProviderFromBuffer(lasso.PROVIDER_ROLE_SP,
289 self.debug('Added SP %s' % sp['name'])
291 def get_login_handler(self, dump=None):
293 return lasso.Login.newFromDump(self.server, dump)
295 return lasso.Login(self.server)
297 def get_providers(self):
298 return self.server.get_providers()
300 def get_logout_handler(self, dump=None):
302 return lasso.Logout.newFromDump(self.server, dump)
304 return lasso.Logout(self.server)