View | Details | Raw Unified | Return to bug 52272 | Differences between
and this patch

Collapse All | Expand All

(-)management/univention-management-console/src/univention/management/console/protocol/server.py (-12 / +17 lines)
 Lines 38-43   Defines the basic class for an UMC server. Link Here 
38
import os
38
import os
39
import errno
39
import errno
40
import fcntl
40
import fcntl
41
import time
41
import socket
42
import socket
42
import resource
43
import resource
43
import traceback
44
import traceback
 Lines 70-75   class MagicBucket(object): Link Here 
70
71
71
	def __init__(self):
72
	def __init__(self):
72
		self.__states = {}
73
		self.__states = {}
74
		notifier.timer_add(1000, self._timeout_connections)
73
75
74
	def new(self, client, socket):
76
	def new(self, client, socket):
75
		"""Is called by the Server object to announce a new incoming
77
		"""Is called by the Server object to announce a new incoming
 Lines 83-101   class MagicBucket(object): Link Here 
83
		state.session.signal_connect('success', notifier.Callback(self._response, state))
85
		state.session.signal_connect('success', notifier.Callback(self._response, state))
84
		self.__states[socket] = state
86
		self.__states[socket] = state
85
		notifier.socket_add(socket, self._receive)
87
		notifier.socket_add(socket, self._receive)
86
		self._timeout_connection(state)
87
88
88
	def _timeout_connection(self, state):
89
	def _timeout_connections(self):
89
		"""Closes the connection after a specified timeout"""
90
		"""Closes the connection after a specified timeout"""
90
		state.time_remaining -= 1
91
		timed_out = [sock for sock, state in self.__states.items() if state.timed_out()]
92
		for sock in timed_out:
93
			CORE.process('Session timed out. (sock=%r)' % (sock,))
94
			self._cleanup(sock)
91
95
92
		if state.time_remaining <= 0 and not state.requests and not state.session.has_active_module_processes():
96
		return True
93
			CORE.process('Session timed out.')
94
			self._cleanup(state.socket)
95
		else:
96
			# count down the timer second-wise (in order to avoid problems when
97
			# changing the system time, e.g. via rdate)
98
			notifier.timer_add(1000, lambda: self._timeout_connection(state))
99
97
100
	def exit(self):
98
	def exit(self):
101
		'''Closes all open connections.'''
99
		'''Closes all open connections.'''
 Lines 513-519   class State(object): Link Here 
513
	:param fd socket: file descriptor or socket object
511
	:param fd socket: file descriptor or socket object
514
	"""
512
	"""
515
513
516
	__slots__ = ('client', 'socket', 'buffer', 'requests', 'resend_queue', 'session', 'time_remaining')
514
	__slots__ = ('client', 'socket', 'buffer', 'requests', 'resend_queue', 'session', 'session_end_time')
517
515
518
	def __init__(self, client, socket):
516
	def __init__(self, client, socket):
519
		self.client = client
517
		self.client = client
 Lines 525-531   class State(object): Link Here 
525
		self.reset_connection_timeout()
523
		self.reset_connection_timeout()
526
524
527
	def reset_connection_timeout(self):
525
	def reset_connection_timeout(self):
528
		self.time_remaining = SERVER_CONNECTION_TIMEOUT
526
		self.session_end_time = int(time.time() + SERVER_CONNECTION_TIMEOUT)
527
528
	def timed_out(self):
529
		return not self.requests and not self.session.has_active_module_processes() and self.time_remaining <= 0
530
531
	@property
532
	def time_remaining(self):
533
		return int(self.session_end_time - time.time())
529
534
530
	def __repr__(self):
535
	def __repr__(self):
531
		return '<State(%s %r buffer=%d requests=%d time_remaining=%r)>' % (self.client, self.socket, len(self.buffer), len(self.requests), self.time_remaining)
536
		return '<State(%s %r buffer=%d requests=%d time_remaining=%r)>' % (self.client, self.socket, len(self.buffer), len(self.requests), self.time_remaining)
532
   Bug #52272: UMC-Webserver: cleanup all sessions with one timer
537
   Bug #52272: UMC-Webserver: cleanup all sessions with one timer
533
   
538
   
534
   The UMC-Web-Server creates a timer which counts down the session second wise for each session to check if it can be destroyed.
539
   The UMC-Web-Server creates a timer which counts down the session second wise for each session to check if it can be destroyed.
535
   
540
   
536
   When 1000 uses are logged in currently, every second 1000 timer-callbacks are called.
541
   When 1000 uses are logged in currently, every second 1000 timer-callbacks are called.
537
   We should register one timer which iterates over all sessions instead.
542
   We should register one timer which iterates over all sessions instead.
(-)management/univention-management-console/univention-management-console-web-server (-57 / +24 lines)
 Lines 304-309   class UMCP_Dispatcher(object): Link Here 
304
		except KeyError:
304
		except KeyError:
305
			CORE.info('Session %r not found' % (sessionid,))
305
			CORE.info('Session %r not found' % (sessionid,))
306
306
307
	@classmethod
308
	def session_timeout_timer(cls):
309
		now = time.time()
310
		for sessionid, user in list(Ressource.sessions.items()):
311
			session = UMCP_Dispatcher.sessions.get(sessionid)
312
			if (not session or not session._requestid2response_queue) and user.timed_out(now):
313
				CORE.info('session %r timed out' % (sessionid,))
314
				Ressource.sessions.pop(user.sessionid, None)
315
				user.on_logout()
316
		return True  # execute again!
317
307
318
308
class UploadManager(dict):
319
class UploadManager(dict):
309
320
 Lines 359-393   class QueueRequest(object): Link Here 
359
370
360
class User(object):
371
class User(object):
361
372
362
	__slots__ = ('sessionid', 'username', 'password', 'saml', '_time_remaining')
373
	__slots__ = ('sessionid', 'username', 'password', 'saml', '_timeout')
363
374
364
	def __init__(self, sessionid, username, password, saml=None):
375
	def __init__(self, sessionid, username, password, saml=None):
365
		self.sessionid = sessionid
376
		self.sessionid = sessionid
366
		self.username = username
377
		self.username = username
367
		self.password = password
378
		self.password = password
368
		self.saml = saml
379
		self.saml = saml
369
		self._time_remaining = _session_timeout
370
		self.reset_timeout()
380
		self.reset_timeout()
371
381
372
	def reset_timeout(self):
382
	def reset_timeout(self):
373
		self._time_remaining = self.session_validity
383
		self._timeout = time.time() + _session_timeout
374
384
375
	@property
385
	def timed_out(self, now):
376
	def session_validity(self):
386
		return self.session_end_time < now
377
		if self.saml is not None:
378
			return self.time_remaining
379
		return _session_timeout
380
387
381
	@property
388
	@property
382
	def time_remaining(self):
389
	def session_end_time(self):
383
		remaining = []
390
		if self.is_saml_user() and self.saml.not_on_or_after:
384
		if self.saml is not None:
391
			return self.saml.not_on_or_after
385
			remaining.append(self.saml.time_remaining)
392
		return self._timeout
386
		remaining.append(self._time_remaining)
387
		try:
388
			return min(remaining)
389
		except ValueError:  # no SAML, no client
390
			return 0
391
393
392
	def is_saml_user(self):
394
	def is_saml_user(self):
393
		# self.saml indicates that it was originally a
395
		# self.saml indicates that it was originally a
 Lines 412-420   class User(object): Link Here 
412
		else:
414
		else:
413
			return None
415
			return None
414
416
415
	def timed_out(self):
416
		return self.saml.timed_out()
417
418
	def __repr__(self):
417
	def __repr__(self):
419
		return '<User(%s, %s, %s)>' % (self.username, self.sessionid, self.saml is not None)
418
		return '<User(%s, %s, %s)>' % (self.username, self.sessionid, self.saml is not None)
420
419
 Lines 429-445   class SAMLUser(object): Link Here 
429
		self.message = message
428
		self.message = message
430
		self.username = u''.join(response.ava['uid'])
429
		self.username = u''.join(response.ava['uid'])
431
430
432
	@property
433
	def time_remaining(self):
434
		if self.not_on_or_after == 0:
435
			return 0
436
		return int(self.not_on_or_after - time.time())
437
438
	def timed_out(self):
439
		if self.not_on_or_after == 0:
440
			return False
441
		return self.time_remaining < 0
442
443
431
444
traceback_pattern = re.compile(r'(Traceback.*most recent call|File.*line.*in.*\d)')
432
traceback_pattern = re.compile(r'(Traceback.*most recent call|File.*line.*in.*\d)')
445
433
 Lines 668-674   class Ressource(object): Link Here 
668
656
669
	def check_saml_session_validity(self):
657
	def check_saml_session_validity(self):
670
		user = self.get_user()
658
		user = self.get_user()
671
		if user and user.saml is not None and user.time_remaining < 1:
659
		if user and user.saml is not None and user.timed_out(time.time()):
672
			raise UMC_HTTPError(UNAUTHORIZED)
660
			raise UMC_HTTPError(UNAUTHORIZED)
673
661
674
	def set_cookies(self, *cookies, **kwargs):
662
	def set_cookies(self, *cookies, **kwargs):
 Lines 695-702   class Ressource(object): Link Here 
695
		olduser = self.get_user()
683
		olduser = self.get_user()
696
684
697
		user = User(sessionid, username, password, saml or olduser and olduser.saml)
685
		user = User(sessionid, username, password, saml or olduser and olduser.saml)
698
		self._session_timeout_timer(user)
699
700
		self.sessions[sessionid] = user
686
		self.sessions[sessionid] = user
701
		self.set_cookies(('UMCSessionId', sessionid), ('UMCUsername', username))
687
		self.set_cookies(('UMCSessionId', sessionid), ('UMCUsername', username))
702
		return user
688
		return user
 Lines 715-743   class Ressource(object): Link Here 
715
		if not value or value not in self.sessions:
701
		if not value or value not in self.sessions:
716
			return
702
			return
717
		user = self.sessions[value]
703
		user = self.sessions[value]
718
		if user.time_remaining <= 0:
704
		if user.timed_out(time.time()):
719
			return
705
			return
720
		return user
706
		return user
721
707
722
	def _session_timeout_timer(self, user):
723
		"""In order to avoid problems when the system time is changed (e.g.,
724
		via rdate), we register a timer event that counts down the session
725
		timeout second-wise."""
726
727
		# count down the remaining time
728
		user._time_remaining -= 1
729
730
		session = UMCP_Dispatcher.sessions.get(user.sessionid)
731
		if user._time_remaining <= 0 and (not session or not session._requestid2response_queue):
732
			self._log('info', 'session timed out')
733
			self.sessions.pop(user.sessionid, None)
734
			user.on_logout()
735
			return
736
737
		# count down the timer second-wise (in order to avoid problems when
738
		# changing the system time, e.g. via rdate)
739
		notifier.timer_add(1000, lambda: self._session_timeout_timer(user))
740
741
708
742
class CPgeneric(Ressource):
709
class CPgeneric(Ressource):
743
710
 Lines 902-909   class CPGet(CPgeneric): Link Here 
902
			raise UMC_HTTPError(UNAUTHORIZED)
869
			raise UMC_HTTPError(UNAUTHORIZED)
903
		info['username'] = user.username
870
		info['username'] = user.username
904
		info['auth_type'] = user.saml and 'SAML'
871
		info['auth_type'] = user.saml and 'SAML'
905
		info['remaining'] = user.time_remaining
872
		info['remaining'] = int(user.session_end_time - time.time())
906
		info['validity'] = user.session_validity
907
		return json.dumps({"status": 200, "result": info, "message": ""}).encode('ASCII')
873
		return json.dumps({"status": 200, "result": info, "message": ""}).encode('ASCII')
908
874
909
	@cherrypy.expose
875
	@cherrypy.expose
 Lines 1035-1041   class CPAuth(CPgeneric): Link Here 
1035
		CORE.info('CPAuth/auth/sso: got new auth request (%s:%s <=> %s)' % (get_ip_address(), remote.port, remote.name))
1001
		CORE.info('CPAuth/auth/sso: got new auth request (%s:%s <=> %s)' % (get_ip_address(), remote.port, remote.name))
1036
1002
1037
		user = self.get_user()
1003
		user = self.get_user()
1038
		if not user or not user.saml or user.timed_out():
1004
		if not user or not user.saml or user.timed_out(time.time()):
1039
			# redirect user to login page in case he's not authenticated or his session timed out
1005
			# redirect user to login page in case he's not authenticated or his session timed out
1040
			raise HTTPRedirect('/univention/saml/')
1006
			raise HTTPRedirect('/univention/saml/')
1041
1007
 Lines 1638-1643   class UMC_HTTP_Daemon(DaemonRunner): Link Here 
1638
			notifier.init(notifier.GENERIC)
1604
			notifier.init(notifier.GENERIC)
1639
			notifier.dispatch.MIN_TIMER = get_int('umc/http/dispatch-interval', notifier.dispatch.MIN_TIMER)
1605
			notifier.dispatch.MIN_TIMER = get_int('umc/http/dispatch-interval', notifier.dispatch.MIN_TIMER)
1640
			notifier.dispatcher_add(UMCP_Dispatcher.check_queue)
1606
			notifier.dispatcher_add(UMCP_Dispatcher.check_queue)
1607
			notifier.timer_add(1000, UMCP_Dispatcher.session_timeout_timer)
1641
			notifier.loop()
1608
			notifier.loop()
1642
		except (SystemExit, KeyboardInterrupt) as exc:
1609
		except (SystemExit, KeyboardInterrupt) as exc:
1643
			# stop the web server
1610
			# stop the web server

Return to bug 52272