Mercurial > libervia-web
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) |