View | Details | Raw Unified | Return to bug 48925
Collapse All | Expand All

(-)management/univention-management-console/univention-management-console-web-server (-12 / +28 lines)
 Lines 52-58   import functools Link Here 
52
import threading
52
import threading
53
from errno import ESRCH
53
from errno import ESRCH
54
from optparse import OptionParser
54
from optparse import OptionParser
55
from six.moves.urllib_parse import urlparse, urlunsplit
55
from six.moves.urllib_parse import urlparse, urlunsplit, urlencode
56
from six.moves.http_client import REQUEST_ENTITY_TOO_LARGE, LENGTH_REQUIRED, NOT_FOUND, BAD_REQUEST, UNAUTHORIZED, SERVICE_UNAVAILABLE
56
from six.moves.http_client import REQUEST_ENTITY_TOO_LARGE, LENGTH_REQUIRED, NOT_FOUND, BAD_REQUEST, UNAUTHORIZED, SERVICE_UNAVAILABLE
57
57
58
import six
58
import six
 Lines 388-393   class User(object): Link Here 
388
		except ValueError:  # no SAML, no client
388
		except ValueError:  # no SAML, no client
389
			return 0
389
			return 0
390
390
391
	@property
392
	def is_external_user(self):
393
		return self.is_saml_user() and self.saml.issuer != ucr.get('umc/saml/idp-server')
394
391
	def is_saml_user(self):
395
	def is_saml_user(self):
392
		# self.saml indicates that it was originally a
396
		# self.saml indicates that it was originally a
393
		# saml user. but it may have upgraded and got a
397
		# saml user. but it may have upgraded and got a
 Lines 416-428   class User(object): Link Here 
416
420
417
class SAMLUser(object):
421
class SAMLUser(object):
418
422
419
	__slots__ = ('message', 'username', 'not_on_or_after', 'name_id')
423
	__slots__ = ('message', 'username', 'not_on_or_after', 'name_id', 'issuer', 'attributes')
420
424
421
	def __init__(self, response, message):
425
	def __init__(self, response, message):
422
		self.not_on_or_after = response.not_on_or_after
426
		self.not_on_or_after = response.not_on_or_after
423
		self.name_id = code(response.name_id)
427
		self.name_id = code(response.name_id)
424
		self.message = message
428
		self.message = message
425
		self.username = u''.join(response.ava['uid'])
429
		self.username = u''.join(response.ava['uid'])
430
		self.attributes = response.ava.copy()
431
		self.issuer = response.issuer()
426
432
427
	@property
433
	@property
428
	def time_remaining(self):
434
	def time_remaining(self):
 Lines 597-605   class SamlError(UMC_HTTPError): Link Here 
597
	def no_identity_provider(self):
603
	def no_identity_provider(self):
598
		return self._('There is a configuration error in the service provider: No identity provider are set up for use.')
604
		return self._('There is a configuration error in the service provider: No identity provider are set up for use.')
599
605
600
	@error  # TODO: multiple choices redirection status
606
	@error(status=400)  # 300 is more correct, but firefox uses the first link and redirects
601
	def multiple_identity_provider(self, idps, idp_query_param):
607
	def multiple_identity_provider(self, idps, idp_query_param, base_uri):
602
		return self._('Could not pick an identity provider. You can specify one via the query string parameter %(param)r from %(idps)r') % {'param': idp_query_param, 'idps': idps}
608
		uris = [base_uri + ('&', '?')[urlparse(base_uri).query == ''] + urlencode({
609
			idp_query_param: idp
610
		}) for idp in idps]
611
		cherrypy.response.headers['Location'] = ', '.join(uris)
612
		raise HTTPRedirect(uris, 300)  # FIXME
613
		return self._('Could not pick an identity provider. Choose from: %s') % ('\n'.join(uris),)
603
614
604
615
605
class Ressource(object):
616
class Ressource(object):
 Lines 806-812   class CPgeneric(Ressource): Link Here 
806
817
807
		user = self.get_user()
818
		user = self.get_user()
808
		client = UMCP_Dispatcher.sessions.get(sessionid)
819
		client = UMCP_Dispatcher.sessions.get(sessionid)
809
		if user and (user.password or user.saml) and (not client or not client.authenticated):
820
		if user and not user.is_external_user and (user.password or user.saml) and (not client or not client.authenticated):
810
			auth = Request('AUTH')
821
			auth = Request('AUTH')
811
			auth.body = {
822
			auth.body = {
812
				'username': user.username,
823
				'username': user.username,
 Lines 894-899   class CPGet(CPgeneric): Link Here 
894
			raise UMC_HTTPError(UNAUTHORIZED)
905
			raise UMC_HTTPError(UNAUTHORIZED)
895
		info['username'] = user.username
906
		info['username'] = user.username
896
		info['auth_type'] = user.saml and 'SAML'
907
		info['auth_type'] = user.saml and 'SAML'
908
		info['attributes'] = user.saml and user.saml.attributes  # TODO: create a own URL for this?
897
		info['remaining'] = user.time_remaining
909
		info['remaining'] = user.time_remaining
898
		info['validity'] = user.session_validity
910
		info['validity'] = user.session_validity
899
		return json.dumps({"status": 200, "result": info, "message": ""}).encode('ASCII')
911
		return json.dumps({"status": 200, "result": info, "message": ""}).encode('ASCII')
 Lines 1173-1179   class SAML(Ressource): Link Here 
1173
		self.configfile = '/usr/share/univention-management-console/saml/sp.py'
1185
		self.configfile = '/usr/share/univention-management-console/saml/sp.py'
1174
		self.__sp = None
1186
		self.__sp = None
1175
1187
1176
		self.idp_query_param = "IdpQuery"
1188
		self.idp_query_param = "idp"
1177
		self.bindings = [BINDING_HTTP_REDIRECT, BINDING_HTTP_POST, BINDING_HTTP_ARTIFACT]
1189
		self.bindings = [BINDING_HTTP_REDIRECT, BINDING_HTTP_POST, BINDING_HTTP_ARTIFACT]
1178
1190
1179
		self.outstanding_queries = {}
1191
		self.outstanding_queries = {}
 Lines 1206-1212   class SAML(Ressource): Link Here 
1206
		binding, message, relay_state = self._get_saml_message()
1218
		binding, message, relay_state = self._get_saml_message()
1207
1219
1208
		if message is None:
1220
		if message is None:
1209
			return self.do_single_sign_on(relay_state=kwargs.get('location', '/univention/management/'))
1221
			return self.do_single_sign_on(relay_state=kwargs.get('location', '/univention/management/'), use_ucs_identity_provider='external' not in kwargs)
1210
1222
1211
		acs = self.attribute_consuming_service
1223
		acs = self.attribute_consuming_service
1212
		if relay_state == 'iframe-passive':
1224
		if relay_state == 'iframe-passive':
 Lines 1216-1222   class SAML(Ressource): Link Here 
1216
	@cherrypy.expose
1228
	@cherrypy.expose
1217
	def iframe(self, *args, **kwargs):
1229
	def iframe(self, *args, **kwargs):
1218
		cherrypy.request.uri = cherrypy.request.uri.replace('/iframe', '')
1230
		cherrypy.request.uri = cherrypy.request.uri.replace('/iframe', '')
1219
		return self.do_single_sign_on(is_passive='true', relay_state='iframe-passive')
1231
		return self.do_single_sign_on(is_passive='true', relay_state='iframe-passive', use_ucs_identity_provider=True)
1220
1232
1221
	def attribute_consuming_service(self, binding, message, relay_state):
1233
	def attribute_consuming_service(self, binding, message, relay_state):
1222
		response = self.acs(message, binding)
1234
		response = self.acs(message, binding)
 Lines 1355-1361   class SAML(Ressource): Link Here 
1355
1367
1356
			Returns (binding, http-arguments)
1368
			Returns (binding, http-arguments)
1357
		"""
1369
		"""
1358
		identity_provider_entity_id = self.select_identity_provider()
1370
		identity_provider_entity_id = self.select_identity_provider(use_ucs_identity_provider=kwargs.pop('use_ucs_identity_provider', True))
1359
		binding, destination = self.get_identity_provider_destination(identity_provider_entity_id)
1371
		binding, destination = self.get_identity_provider_destination(identity_provider_entity_id)
1360
1372
1361
		relay_state = kwargs.pop('relay_state', None)
1373
		relay_state = kwargs.pop('relay_state', None)
 Lines 1367-1373   class SAML(Ressource): Link Here 
1367
		self.outstanding_queries[sid] = service_provider_url  # cherrypy.request.uri  # TODO: shouldn't this contain service_provider_url?
1379
		self.outstanding_queries[sid] = service_provider_url  # cherrypy.request.uri  # TODO: shouldn't this contain service_provider_url?
1368
		return binding, http_args
1380
		return binding, http_args
1369
1381
1370
	def select_identity_provider(self):
1382
	def select_identity_provider(self, use_ucs_identity_provider=True):
1371
		"""Select an identity provider based on the available identity providers.
1383
		"""Select an identity provider based on the available identity providers.
1372
			If multiple IDP's are set up the client might have specified one in the query string.
1384
			If multiple IDP's are set up the client might have specified one in the query string.
1373
			Otherwise an error is raised where the user can choose one.
1385
			Otherwise an error is raised where the user can choose one.
 Lines 1383-1389   class SAML(Ressource): Link Here 
1383
			return list(idps.keys())[0]
1395
			return list(idps.keys())[0]
1384
		if not idps:
1396
		if not idps:
1385
			raise SamlError().no_identity_provider()
1397
			raise SamlError().no_identity_provider()
1386
		raise SamlError().multiple_identity_provider(list(idps.keys()), self.idp_query_param)
1398
		if use_ucs_identity_provider:
1399
			for key in idps:
1400
				if key == ucr.get('umc/saml/idp-server'):
1401
					return key
1402
		raise SamlError().multiple_identity_provider(list(idps.keys()), self.idp_query_param, cherrypy.request.uri)
1387
1403
1388
	def get_identity_provider_destination(self, entity_id):
1404
	def get_identity_provider_destination(self, entity_id):
1389
		"""Get the destination (with SAML binding) of the specified entity_id.
1405
		"""Get the destination (with SAML binding) of the specified entity_id.

Return to bug 48925