comparison libervia_server/__init__.py @ 415:fadbba1d793f

server_side: added support for SSL and related parameters: Full parameters list: -t, --connection_type= 'http', 'https' or 'both' (to launch both servers). [default: both] -p, --port= The port number to listen HTTP on. [default: 8080] -s, --port_https= The port number to listen HTTPS on. [default: 8443] -c, --ssl_certificate= PEM certificate with both private and public parts. [default: libervia.pem] -r, --redirect_to_https= automatically redirect from HTTP to HTTPS. [default: 0] -w, --security_warning= warn user that he is about to connect on HTTP. [default: 1]
author souliane <souliane@mailoo.org>
date Tue, 18 Mar 2014 15:59:38 +0100
parents 7a8991cda2fa
children 2bd609d7dd65
comparison
equal deleted inserted replaced
414:ae598511850d 415:fadbba1d793f
23 from twisted.internet import reactor, defer 23 from twisted.internet import reactor, defer
24 from twisted.web import server 24 from twisted.web import server
25 from twisted.web import error as weberror 25 from twisted.web import error as weberror
26 from twisted.web.static import File 26 from twisted.web.static import File
27 from twisted.web.resource import Resource, NoResource 27 from twisted.web.resource import Resource, NoResource
28 from twisted.web.util import Redirect 28 from twisted.web.util import Redirect, redirectTo
29 from twisted.python.components import registerAdapter 29 from twisted.python.components import registerAdapter
30 from twisted.python.failure import Failure 30 from twisted.python.failure import Failure
31 from twisted.words.protocols.jabber.jid import JID 31 from twisted.words.protocols.jabber.jid import JID
32 from txjsonrpc.web import jsonrpc 32 from txjsonrpc.web import jsonrpc
33 from txjsonrpc import jsonrpclib 33 from txjsonrpc import jsonrpclib
34 34
35 from logging import debug, info, warning, error 35 from logging import debug, info, warning, error
36 import re, glob 36 import re
37 import os.path, sys 37 import glob
38 import tempfile, shutil, uuid 38 import os.path
39 import sys
40 import tempfile
41 import shutil
42 import uuid
39 from zope.interface import Interface, Attribute, implements 43 from zope.interface import Interface, Attribute, implements
40 from xml.dom import minidom 44 from xml.dom import minidom
45 from httplib import HTTPS_PORT
41 46
42 from constants import Const 47 from constants import Const
43 from libervia_server.blog import MicroBlog 48 from libervia_server.blog import MicroBlog
44 from sat_frontends.bridge.DBus import DBusBridgeFrontend, BridgeExceptionNoService 49 from sat_frontends.bridge.DBus import DBusBridgeFrontend, BridgeExceptionNoService
45 from sat.core.i18n import _, D_ 50 from sat.core.i18n import _, D_
46 from sat.tools.xml_tools import paramsXML2XMLUI 51 from sat.tools.xml_tools import paramsXML2XMLUI
52 try:
53 import OpenSSL
54 from twisted.internet import ssl
55 ssl_available = True
56 except:
57 ssl_available = False
47 58
48 59
49 class ISATSession(Interface): 60 class ISATSession(Interface):
50 profile = Attribute("Sat profile") 61 profile = Attribute("Sat profile")
51 jid = Attribute("JID associated with the profile") 62 jid = Attribute("JID associated with the profile")
551 def render(self, request): 562 def render(self, request):
552 """ 563 """
553 Render method with some hacks: 564 Render method with some hacks:
554 - if login is requested, try to login with form data 565 - if login is requested, try to login with form data
555 - except login, every method is jsonrpc 566 - except login, every method is jsonrpc
556 - user doesn't need to be authentified for isRegistered or registerParams, but must be for all other methods 567 - user doesn't need to be authentified for explicitely listed methods, but must be for all others
557 """ 568 """
558 if request.postpath==['login']: 569 if request.postpath==['login']:
559 return self.login(request) 570 return self.login(request)
560 _session = request.getSession() 571 _session = request.getSession()
561 parsed = jsonrpclib.loads(request.content.read()) 572 parsed = jsonrpclib.loads(request.content.read())
562 method = parsed.get("method") 573 method = parsed.get("method")
563 if method not in ['isRegistered', 'registerParams', 'getMenus']: 574 if method not in ['isRegistered', 'registerParams', 'getMenus']:
564 #if we don't call these methods, we need to be identified 575 #if we don't call these methods, we need to be identified
565 profile = ISATSession(_session).profile 576 profile = ISATSession(_session).profile
566 if not profile: 577 if not profile:
567 #user is not identified, we return a jsonrpc fault 578 #user is not identified, we return a jsonrpc fault
568 fault = jsonrpclib.Fault(Const.ERRNUM_LIBERVIA, "Not allowed") #FIXME: define some standard error codes for libervia 579 fault = jsonrpclib.Fault(Const.ERRNUM_LIBERVIA, "Not allowed") #FIXME: define some standard error codes for libervia
735 self.profiles_waiting[profile] = self.request 746 self.profiles_waiting[profile] = self.request
736 self.sat_host.bridge.connect(profile) 747 self.sat_host.bridge.connect(profile)
737 return server.NOT_DONE_YET 748 return server.NOT_DONE_YET
738 749
739 def jsonrpc_isRegistered(self): 750 def jsonrpc_isRegistered(self):
740 """Tell if the user is already registered""" 751 """
752 @return: a couple (registered, message) with:
753 - registered: True if the user is already registered, False otherwise
754 - message: a security warning message if registered is False *and* the connection is unsecure, None otherwise
755 """
741 _session = self.request.getSession() 756 _session = self.request.getSession()
742 profile = ISATSession(_session).profile 757 profile = ISATSession(_session).profile
743 return bool(profile) 758 if bool(profile):
759 return (True, None)
760 return (False, self.__getSecurityWarning())
744 761
745 def jsonrpc_registerParams(self): 762 def jsonrpc_registerParams(self):
746 """Register the frontend specific parameters""" 763 """Register the frontend specific parameters"""
747 params = """ 764 params = """
748 <params> 765 <params>
763 780
764 def jsonrpc_getMenus(self): 781 def jsonrpc_getMenus(self):
765 """Return the parameters XML for profile""" 782 """Return the parameters XML for profile"""
766 # XXX: we put this method in Register because we get menus before being logged 783 # XXX: we put this method in Register because we get menus before being logged
767 return self.sat_host.bridge.getMenus('', Const.SECURITY_LIMIT) 784 return self.sat_host.bridge.getMenus('', Const.SECURITY_LIMIT)
785
786 def __getSecurityWarning(self):
787 """@return: a security warning message, or None if the connection is secure"""
788 if self.request.URLPath().scheme == 'https' or not self.sat_host.security_warning:
789 return None
790 text = D_("You are about to connect to an unsecured service.")
791 if self.sat_host.connection_type == 'both':
792 new_port = (':%s' % self.sat_host.port_https) if self.sat_host.port_https != HTTPS_PORT else ''
793 url = "https://%s" % self.request.URLPath().netloc.replace(':%s' % self.sat_host.port, new_port)
794 text += D_('<br />Secure version of this website: <a href="%(url)s">%(url)s</a>') % {'url': url}
795 return text
768 796
769 797
770 class SignalHandler(jsonrpc.JSONRPC): 798 class SignalHandler(jsonrpc.JSONRPC):
771 799
772 def __init__(self, sat_host): 800 def __init__(self, sat_host):
955 """ 983 """
956 profile = ISATSession(request.getSession()).profile 984 profile = ISATSession(request.getSession()).profile
957 return ("setAvatar", filepath, profile) 985 return ("setAvatar", filepath, profile)
958 986
959 987
988 def coerceConnectionType(value): # called from Libervia.OPT_PARAMETERS
989 allowed_values = ('http', 'https', 'both')
990 if value not in allowed_values:
991 raise ValueError("Invalid parameter value, not in %s" % str(allowed_values))
992 return value
993
994
960 class Libervia(service.Service): 995 class Libervia(service.Service):
961 996
962 def __init__(self, port=8080): 997 OPT_PARAMETERS = [['connection_type', 't', 'https', "'http', 'https' or 'both' (to launch both servers).", coerceConnectionType],
998 ['port', 'p', 8080, 'The port number to listen HTTP on.', int],
999 ['port_https', 's', 8443, 'The port number to listen HTTPS on.', int],
1000 ['ssl_certificate', 'c', 'libervia.pem', 'PEM certificate with both private and public parts.', str],
1001 ['redirect_to_https', 'r', 1, 'automatically redirect from HTTP to HTTPS.', int],
1002 ['security_warning', 'w', 1, 'warn user that he is about to connect on HTTP.', int],
1003 ]
1004
1005 def __init__(self, *args, **kwargs):
1006 if not kwargs:
1007 # During the loading of the twisted plugins, we just need the default values.
1008 # This part is not executed when the plugin is actually started.
1009 for name, value in [(option[0], option[2]) for option in self.OPT_PARAMETERS]:
1010 kwargs[name] = value
1011
1012 self.connection_type = kwargs['connection_type']
1013 self.port = kwargs['port']
1014 self.port_https = kwargs['port_https']
1015 self.ssl_certificate = kwargs['ssl_certificate']
1016 self.redirect_to_https = kwargs['redirect_to_https']
1017 self.security_warning = kwargs['security_warning']
963 self._cleanup = [] 1018 self._cleanup = []
964 self.port = port
965 root = ProtectedFile(Const.LIBERVIA_DIR) 1019 root = ProtectedFile(Const.LIBERVIA_DIR)
966 self.signal_handler = SignalHandler(self) 1020 self.signal_handler = SignalHandler(self)
967 _register = Register(self) 1021 _register = Register(self)
968 _upload_radiocol = UploadManagerRadioCol(self) 1022 _upload_radiocol = UploadManagerRadioCol(self)
969 _upload_avatar = UploadManagerAvatar(self) 1023 _upload_avatar = UploadManagerAvatar(self)
1013 @param *args: list of arguments of the callback 1067 @param *args: list of arguments of the callback
1014 @param **kwargs: list of keyword arguments of the callback""" 1068 @param **kwargs: list of keyword arguments of the callback"""
1015 self._cleanup.insert(0, (callback, args, kwargs)) 1069 self._cleanup.insert(0, (callback, args, kwargs))
1016 1070
1017 def startService(self): 1071 def startService(self):
1018 reactor.listenTCP(self.port, self.site) 1072 if self.connection_type in ('https', 'both'):
1073 if not ssl_available:
1074 raise(ImportError(_("Python module pyOpenSSL is not installed!")))
1075 try:
1076 with open(self.ssl_certificate) as keyAndCert:
1077 try:
1078 cert = ssl.PrivateCertificate.loadPEM(keyAndCert.read())
1079 except OpenSSL.crypto.Error as e:
1080 error(_("The file '%s' must contain both private and public parts of the certificate") % self.ssl_certificate)
1081 raise e
1082 except IOError as e:
1083 error(_("The file '%s' doesn't exist") % self.ssl_certificate)
1084 raise e
1085 reactor.listenSSL(self.port_https, self.site, cert.options())
1086 if self.connection_type in ('http', 'both'):
1087 if self.connection_type == 'both' and self.redirect_to_https:
1088 reactor.listenTCP(self.port, server.Site(RedirectToHTTPS(self.port, self.port_https)))
1089 else:
1090 reactor.listenTCP(self.port, self.site)
1019 1091
1020 def stopService(self): 1092 def stopService(self):
1021 print "launching cleaning methods" 1093 print "launching cleaning methods"
1022 for callback, args, kwargs in self._cleanup: 1094 for callback, args, kwargs in self._cleanup:
1023 callback(*args, **kwargs) 1095 callback(*args, **kwargs)
1026 reactor.run() 1098 reactor.run()
1027 1099
1028 def stop(self): 1100 def stop(self):
1029 reactor.stop() 1101 reactor.stop()
1030 1102
1103
1104 class RedirectToHTTPS(Resource):
1105
1106 def __init__(self, old_port, new_port):
1107 Resource.__init__(self)
1108 self.isLeaf = True
1109 self.old_port = old_port
1110 self.new_port = new_port
1111
1112 def render(self, request):
1113 netloc = request.URLPath().netloc.replace(':%s' % self.old_port, ':%s' % self.new_port)
1114 url = "https://" + netloc + request.uri
1115 return redirectTo(url, request)
1116
1117
1031 registerAdapter(SATSession, server.Session, ISATSession) 1118 registerAdapter(SATSession, server.Session, ISATSession)
1032 application = service.Application(Const.APP_NAME) 1119 application = service.Application(Const.APP_NAME)
1033 service = Libervia() 1120 service = Libervia()
1034 service.setServiceParent(application) 1121 service.setServiceParent(application)