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

(-)univention-management-console-web-server (-119 / +75 lines)
 Lines 43-50    Link Here 
43
import tempfile
43
import tempfile
44
import time
44
import time
45
import threading
45
import threading
46
import urllib
47
import urlparse
48
import uuid
46
import uuid
49
47
50
import notifier
48
import notifier
 Lines 328-336    Link Here 
328
		msg = '%s (%s:%s) %s' % (self.name,  self.get_ip_address(), remote.port, _msg)
326
		msg = '%s (%s:%s) %s' % (self.name,  self.get_ip_address(), remote.port, _msg)
329
		self._logOptions.get(loglevel, CORE.info)(msg)
327
		self._logOptions.get(loglevel, CORE.info)(msg)
330
328
331
	def get_request(self, request, json):
329
	def get_request(self, path, args):
332
		req = umcp.Request( [ 'generic' ], opts = {} )
330
		return umcp.Request( [ 'generic' ], opts = {} )
333
		return req
334
331
335
	def get_ip_address(self):
332
	def get_ip_address(self):
336
		"""get the IP address of client by last entry in X-FORWARDED-FOR header"""
333
		"""get the IP address of client by last entry in X-FORWARDED-FOR header"""
 Lines 353-387    Link Here 
353
		self._log(99, 'sessionid="%s"' % (sessionid))
350
		self._log(99, 'sessionid="%s"' % (sessionid))
354
		return sessionid
351
		return sessionid
355
352
353
	def load_json(self, body):
354
		try:
355
			json = simplejson.loads(body)
356
			if not isinstance(json, dict):
357
				raise cherrypy.HTTPError(httplib.BAD_REQUEST, 'JSON document have to be object')
358
		except ValueError:
359
			self._log('error', 'cannot parse JSON body')
360
			raise cherrypy.HTTPError(httplib.BAD_REQUEST, 'Invalid JSON document')
361
		return json
362
356
	@cherrypy.expose
363
	@cherrypy.expose
357
	def default( self, *args, **kwargs ):
364
	def default( self, *args, **kwargs ):
358
		remote = cherrypy.request.remote
359
		self._log('info', 'got new request')
365
		self._log('info', 'got new request')
360
366
361
		# get the session id from the request
367
		# get the session id from the request
362
		sessionid = self.get_session_id()
368
		sessionid = self.get_session_id()
363
		req = None
364
369
365
		if cherrypy.request.headers.get( 'Content-Type', '' ).startswith( 'application/json' ): # normal request
370
		if cherrypy.request.headers.get( 'Content-Type', '' ).startswith( 'application/json' ): # normal (json) request
366
			if not cherrypy.request.headers.get(u"Content-Length", u""):
371
			# get body and parse json
367
				json = ''
372
			body = '{}'
368
				self._log('warn', 'missing Content-Length header')
373
			if cherrypy.request.method in cherrypy.request.methods_with_bodies:
369
			else:
374
				if not cherrypy.request.headers.get(u"Content-Length"):
370
				# get body and parse json
375
					self._log('warn', 'missing Content-Length header')
371
				body = ''
376
					raise cherrypy.HTTPError(httplib.LENGTH_REQUIRED, 'Missing Content-Length header')
372
				if cherrypy.request.body:
377
				body = cherrypy.request.body.read()
373
					body = cherrypy.request.body.read()
374
378
375
				try:
379
			json = self.load_json(body)
376
					json = simplejson.loads(body)
377
				except ValueError:
378
					self._log('error', 'cannot parse JSON body')
379
					raise cherrypy.HTTPError(httplib.BAD_REQUEST, 'Invalid JSON document')
380
		else:
380
		else:
381
			json = ''
381
			# request is not json
382
			json = kwargs
382
383
384
		return self.get_response(sessionid, args, json)
385
386
	def get_response(self, sessionid, path, args):
383
		# create new UMCP request
387
		# create new UMCP request
384
		req = self.get_request( cherrypy.request, json )
388
		req = self.get_request( '/'.join(path), args )
385
389
386
		# create new response queue
390
		# create new response queue
387
		response_queue = Queue.Queue()
391
		response_queue = Queue.Queue()
 Lines 394-448    Link Here 
394
		response = response_queue.get()
398
		response = response_queue.get()
395
		self._log('info', 'got response(0x%x) from queue(0x%x): status=%s (sessionid="%s")' % (id(response), id(response_queue), response.status, sessionid))
399
		self._log('info', 'got response(0x%x) from queue(0x%x): status=%s (sessionid="%s")' % (id(response), id(response_queue), response.status, sessionid))
396
400
397
		if response.status == umcp.SUCCESS:
401
		if 200 <= response.status < 300:
398
			update_session(cherrypy.response, sessionid)
402
			update_session(cherrypy.response, sessionid)
399
			return simplejson.dumps(response.body)
403
			cherrypy.response.status = response.status
400
		elif response.mimetype != umcp.MIMETYPE_JSON:
401
			update_session(cherrypy.response, sessionid)
402
			cherrypy.response.headers[ 'Content-Type' ] = response.mimetype
404
			cherrypy.response.headers[ 'Content-Type' ] = response.mimetype
405
			if response.mimetype == umcp.MIMETYPE_JSON:
406
				return simplejson.dumps(response.body)
403
			return response.body
407
			return response.body
404
408
409
		# TODO: 3xx handling
410
405
		# something bad happened
411
		# something bad happened
406
		self._log('error', 'response status code: %s' % response.status)
412
		self._log('error', 'response status code: %s' % response.status)
407
		self._log('error', 'response message: %s' % response.message)
413
		self._log('error', 'response message: %s' % response.message)
408
		message = response.message
414
		raise cherrypy.HTTPError(response.status, response.message)
409
		if isinstance(message, basestring):
410
			message = cherrypy._cperror._escape(message.replace('"', "'"))
411
		raise cherrypy.HTTPError(response.status, message)
412
415
413
416
414
class CPGet(CPgeneric):
417
class CPGet(CPgeneric):
415
	requestprefix = '/get'
418
	def get_request(self, path, args):
416
419
		if not path:
417
	def get_request(self, request, json):
420
			self._log('error', 'get_request: path is empty')
418
		args = request.path_info
419
420
		if args.startswith( self.requestprefix ):
421
			args = args[ len(self.requestprefix)+1 : ]
422
423
		if not args:
424
			self._log('error', 'get_request: args is empty')
425
			raise cherrypy.HTTPError(httplib.NOT_FOUND)
421
			raise cherrypy.HTTPError(httplib.NOT_FOUND)
426
422
427
		req = umcp.Request( 'GET', arguments = [ args ], options = json.get( 'options', {} ) )
423
		return umcp.Request( 'GET', arguments = [ path ], options = args.get( 'options', {} ) )
428
424
429
		return req
430
425
431
432
class CPSet(CPgeneric):
426
class CPSet(CPgeneric):
433
	requestprefix = '/set'
427
	def get_request(self, path, args):
428
		return umcp.Request( 'SET', options = args.get( 'options', {} ) )
434
429
435
	def get_request(self, request, json):
436
		return umcp.Request( 'SET', options = json.get( 'options', {} ) )
437
430
438
439
class CPUpload( CPgeneric ):
431
class CPUpload( CPgeneric ):
440
	requestprefix = '/upload'
432
	requestprefix = '/upload'
441
433
442
	def get_request(self, request, fields ):
434
	def get_request(self, custom_command, fields ):
443
		self._log( 'info', 'Handle upload command' )
435
		self._log( 'info', 'Handle upload command' )
444
		global _upload_manager
436
		global _upload_manager
445
		custom_command = request.path_info[ len( self.requestprefix ) + 1 : ]
446
		if custom_command:
437
		if custom_command:
447
			self._log( 'info', 'Send upload to module function' )
438
			self._log( 'info', 'Send upload to module function' )
448
			req = umcp.Request( 'COMMAND', arguments = [ custom_command ] )
439
			req = umcp.Request( 'COMMAND', arguments = [ custom_command ] )
 Lines 485-494    Link Here 
485
476
486
	@cherrypy.expose
477
	@cherrypy.expose
487
	def default( self, *args, **kwargs ):
478
	def default( self, *args, **kwargs ):
488
		remote = cherrypy.request.remote
489
		self._log('info', 'got new request')
479
		self._log('info', 'got new request')
490
480
491
		# check for a valid session key
481
		# check for a valid session key in GET/POST data
492
		if 'X-UMC-Session-Id' in kwargs:
482
		if 'X-UMC-Session-Id' in kwargs:
493
			# allow for X-UMC-Sesssion-Id as entry in a multi-form (=upload) request
483
			# allow for X-UMC-Sesssion-Id as entry in a multi-form (=upload) request
494
			sessionid = kwargs['X-UMC-Session-Id']
484
			sessionid = kwargs['X-UMC-Session-Id']
 Lines 500-561    Link Here 
500
		if not cherrypy.request.headers.get( 'Content-Type', '' ).startswith( 'multipart/form-data' ):
490
		if not cherrypy.request.headers.get( 'Content-Type', '' ).startswith( 'multipart/form-data' ):
501
			raise cherrypy.HTTPError(httplib.BAD_REQUEST, 'Content type and URL do not match')
491
			raise cherrypy.HTTPError(httplib.BAD_REQUEST, 'Content type and URL do not match')
502
492
493
		return self.get_response(sessionid, args, kwargs)
494
503
		# check if the request is a iframe upload
495
		# check if the request is a iframe upload
504
		iframe = 'iframe' in kwargs and (kwargs['iframe'] not in ('false', False, 0, '0'))
496
		if 'iframe' in kwargs and (kwargs['iframe'] not in ('false', False, 0, '0')):
497
			# this is a workaround to make iframe uploads work, they need the textarea field
498
			cherrypy.response.headers[ 'Content-Type' ] = umcp.MIMETYPE_HTML
499
			return '<html><body><textarea>%s</textarea></body></html>' % (response.body)
505
500
506
		# create new UMCP request
501
		return response
507
		req = self.get_request( cherrypy.request, kwargs )
508
502
509
		# create new response queue
510
		response_queue = Queue.Queue()
511
512
		# send request to UMC server
513
		request = QueueRequest(sessionid, req, response_queue, self.get_ip_address())
514
		UMCP_Dispatcher._queue_send.put(request)
515
516
		self._log('info', 'pushed request(0x%x) to queue(0x%x) - waiting for response (sessionid="%s")' % (id(req), id(response_queue), sessionid))
517
		response = response_queue.get()
518
		self._log('info', 'got response(0x%x) from queue(0x%x): status=%s (sessionid="%s")' % (id(response), id(response_queue), response.status, sessionid))
519
520
		if response.status == umcp.SUCCESS:
521
			update_session(cherrypy.response, sessionid)
522
			if iframe:
523
				# this is a workaround to make iframe uploads work, they need the textarea field
524
				cherrypy.response.headers[ 'Content-Type' ] = umcp.MIMETYPE_HTML
525
				return '<html><body><textarea>%s</textarea></body></html>' % (simplejson.dumps(response.body))
526
527
			return simplejson.dumps(response.body)
528
529
		# something bad happened
530
		self._log('error', 'response status code: %s' % response.status)
531
		self._log('error', 'response message: %s' % response.message)
532
		message = response.message
533
		if isinstance(message, basestring):
534
			message = cherrypy._cperror._escape(message.replace('"', "'"))
535
		raise cherrypy.HTTPError(response.status, message)
536
537
class CPCommand(CPgeneric):
503
class CPCommand(CPgeneric):
538
	requestprefix = '/command'
504
	def get_request(self, path, args):
539
505
		if not path:
540
	def get_request(self, request, json):
506
			self._log('error', 'get_request: path is empty')
541
		parse_result = urlparse.urlparse( request.path_info )
542
		args = parse_result.path
543
544
		if args.startswith( self.requestprefix ):
545
			args = args[ len(self.requestprefix)+1 : ]
546
547
		if not args:
548
			self._log('error', 'get_request: args is empty')
549
			raise cherrypy.HTTPError(httplib.NOT_FOUND)
507
			raise cherrypy.HTTPError(httplib.NOT_FOUND)
550
508
551
		if cherrypy.request.method == 'POST':
509
		req = umcp.Command( [ path ], options = args.get( 'options', {} ) )
552
			req = umcp.Command( [ args ], options = json.get( 'options', {} ) )
510
		if 'flavor' in args:
553
			if 'flavor' in json:
511
			req.flavor = args[ 'flavor' ]
554
				req.flavor = json[ 'flavor' ]
555
		elif cherrypy.request.method == 'GET':
556
			req = umcp.Command( [ args ], options = {} )
557
			for key, value in urlparse.parse_qsl( cherrypy.request.query_string ):
558
				req.options[ key ] = urllib.unquote( value )
559
512
560
		return req
513
		return req
561
514
 Lines 566-587    Link Here 
566
		remote = cherrypy.request.remote
519
		remote = cherrypy.request.remote
567
		CORE.info('CPRoot/auth: got new auth request (%s:%s <=> %s)' % (self.get_ip_address(), remote.port, remote.name))
520
		CORE.info('CPRoot/auth: got new auth request (%s:%s <=> %s)' % (self.get_ip_address(), remote.port, remote.name))
568
521
569
		if not cherrypy.request.headers.get(u"Content-Length", u""):
522
		content_length = cherrypy.request.headers.get(u"Content-Length")
523
		if not content_length:
570
			CORE.process('CPRoot/auth: missing Content-Length header')
524
			CORE.process('CPRoot/auth: missing Content-Length header')
571
			raise cherrypy.HTTPError(httplib.LENGTH_REQUIRED)
525
			raise cherrypy.HTTPError(httplib.LENGTH_REQUIRED)
572
526
573
		# TODO FIXME check body length and stop here if too much data has been sent by client
574
575
		# get body and parse json
527
		# get body and parse json
576
		body = ''
528
		body = ''
577
		if cherrypy.request.body:
529
		if cherrypy.request.method in cherrypy.request.methods_with_bodies:
530
			max_length = 512
531
			if content_length <= max_length:
532
				raise cherrypy.HTTPError(httplib.REQUEST_ENTITY_TOO_LARGE, 'Request data is to large, allowed length is %d' % max_length)
578
			body = cherrypy.request.body.read()
533
			body = cherrypy.request.body.read()
579
534
580
		try:
535
		json = self.load_json(body)
581
			json = simplejson.loads(body)
582
		except ValueError:
583
			CORE.process('CPRoot/auth: cannot parse JSON body')
584
			raise cherrypy.HTTPError(httplib.BAD_REQUEST, 'Invalid JSON document')
585
536
586
		CORE.info('CPRoot/command: request: command=%s' % cherrypy.request.path_info )
537
		CORE.info('CPRoot/command: request: command=%s' % cherrypy.request.path_info )
587
538
 Lines 608-626    Link Here 
608
			return ""
559
			return ""
609
560
610
		CORE.process('CPRoot/auth: username: %s, status code: %s' % (json.get('username'), response.status))
561
		CORE.process('CPRoot/auth: username: %s, status code: %s' % (json.get('username'), response.status))
611
		message = response.message
562
		raise cherrypy.HTTPError(response.status, response.message)
612
		if isinstance(message, basestring):
613
			message = cherrypy._cperror._escape(message.replace('"', "'"))
614
		raise cherrypy.HTTPError(response.status, message)
615
563
616
class CPRoot(object):
564
class CPRoot(object):
617
	def index(self):
565
	@cherrypy.expose
566
	def index(self, **kw):
618
		"""
567
		"""
619
		http://localhost:<ucr:umc/http/port>/
568
		http://localhost:<ucr:umc/http/port>/
620
		"""
569
		"""
621
		raise cherrypy.HTTPError(httplib.NOT_FOUND)
570
		raise cherrypy.HTTPError(httplib.NOT_FOUND)
622
	index.exposed = True
623
571
572
def default_error_page(**kwargs):
573
	cherrypy.response.headers['Content-type'] = umcp.MIMETYPE_JSON
574
	# escape json and html
575
	for key, value in kwargs.items():
576
		kwargs[key] = cherrypy._cperror._escape(str(value), True)
577
	return cherrypy._cperror._HTTPErrorTemplate % kwargs
578
624
def run_cherrypy():
579
def run_cherrypy():
625
	# TODO FIXME Folgenden Configeintrag einbauen, wenn loglevel in (0,1,2)
580
	# TODO FIXME Folgenden Configeintrag einbauen, wenn loglevel in (0,1,2)
626
	# 'server.environment': 'production',
581
	# 'server.environment': 'production',
 Lines 631-638    Link Here 
631
		'engine.autoreload_on': False,
586
		'engine.autoreload_on': False,
632
		'tools.response_headers.on': True,
587
		'tools.response_headers.on': True,
633
		'tools.response_headers.headers': [
588
		'tools.response_headers.headers': [
634
			('Content-Type', umcp.MIMETYPE_PLAIN)
589
			('Content-Type', umcp.MIMETYPE_JSON)
635
		]
590
		],
591
		'error_page.default': default_error_page
636
	} )
592
	} )
637
	cherrypy.tools.proxy(base=None, local='X-Forwarded-Host', remote='X-Forwarded-For', scheme='X-Forwarded-Proto')
593
	cherrypy.tools.proxy(base=None, local='X-Forwarded-Host', remote='X-Forwarded-For', scheme='X-Forwarded-Proto')
638
594

Return to bug 27850