Mercurial > libervia-web
diff 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 |
line wrap: on
line diff
--- a/libervia_server/__init__.py Fri Mar 21 09:20:27 2014 +0100 +++ b/libervia_server/__init__.py Tue Mar 18 15:59:38 2014 +0100 @@ -25,7 +25,7 @@ from twisted.web import error as weberror from twisted.web.static import File from twisted.web.resource import Resource, NoResource -from twisted.web.util import Redirect +from twisted.web.util import Redirect, redirectTo from twisted.python.components import registerAdapter from twisted.python.failure import Failure from twisted.words.protocols.jabber.jid import JID @@ -33,17 +33,28 @@ from txjsonrpc import jsonrpclib from logging import debug, info, warning, error -import re, glob -import os.path, sys -import tempfile, shutil, uuid +import re +import glob +import os.path +import sys +import tempfile +import shutil +import uuid from zope.interface import Interface, Attribute, implements from xml.dom import minidom +from httplib import HTTPS_PORT from constants import Const from libervia_server.blog import MicroBlog from sat_frontends.bridge.DBus import DBusBridgeFrontend, BridgeExceptionNoService from sat.core.i18n import _, D_ from sat.tools.xml_tools import paramsXML2XMLUI +try: + import OpenSSL + from twisted.internet import ssl + ssl_available = True +except: + ssl_available = False class ISATSession(Interface): @@ -553,14 +564,14 @@ Render method with some hacks: - if login is requested, try to login with form data - except login, every method is jsonrpc - - user doesn't need to be authentified for isRegistered or registerParams, but must be for all other methods + - user doesn't need to be authentified for explicitely listed methods, but must be for all others """ if request.postpath==['login']: return self.login(request) _session = request.getSession() parsed = jsonrpclib.loads(request.content.read()) method = parsed.get("method") - if method not in ['isRegistered', 'registerParams', 'getMenus']: + if method not in ['isRegistered', 'registerParams', 'getMenus']: #if we don't call these methods, we need to be identified profile = ISATSession(_session).profile if not profile: @@ -737,10 +748,16 @@ return server.NOT_DONE_YET def jsonrpc_isRegistered(self): - """Tell if the user is already registered""" + """ + @return: a couple (registered, message) with: + - registered: True if the user is already registered, False otherwise + - message: a security warning message if registered is False *and* the connection is unsecure, None otherwise + """ _session = self.request.getSession() profile = ISATSession(_session).profile - return bool(profile) + if bool(profile): + return (True, None) + return (False, self.__getSecurityWarning()) def jsonrpc_registerParams(self): """Register the frontend specific parameters""" @@ -766,6 +783,17 @@ # XXX: we put this method in Register because we get menus before being logged return self.sat_host.bridge.getMenus('', Const.SECURITY_LIMIT) + def __getSecurityWarning(self): + """@return: a security warning message, or None if the connection is secure""" + if self.request.URLPath().scheme == 'https' or not self.sat_host.security_warning: + return None + text = D_("You are about to connect to an unsecured service.") + if self.sat_host.connection_type == 'both': + new_port = (':%s' % self.sat_host.port_https) if self.sat_host.port_https != HTTPS_PORT else '' + url = "https://%s" % self.request.URLPath().netloc.replace(':%s' % self.sat_host.port, new_port) + text += D_('<br />Secure version of this website: <a href="%(url)s">%(url)s</a>') % {'url': url} + return text + class SignalHandler(jsonrpc.JSONRPC): @@ -957,11 +985,37 @@ return ("setAvatar", filepath, profile) +def coerceConnectionType(value): # called from Libervia.OPT_PARAMETERS + allowed_values = ('http', 'https', 'both') + if value not in allowed_values: + raise ValueError("Invalid parameter value, not in %s" % str(allowed_values)) + return value + + class Libervia(service.Service): - def __init__(self, port=8080): + OPT_PARAMETERS = [['connection_type', 't', 'https', "'http', 'https' or 'both' (to launch both servers).", coerceConnectionType], + ['port', 'p', 8080, 'The port number to listen HTTP on.', int], + ['port_https', 's', 8443, 'The port number to listen HTTPS on.', int], + ['ssl_certificate', 'c', 'libervia.pem', 'PEM certificate with both private and public parts.', str], + ['redirect_to_https', 'r', 1, 'automatically redirect from HTTP to HTTPS.', int], + ['security_warning', 'w', 1, 'warn user that he is about to connect on HTTP.', int], + ] + + def __init__(self, *args, **kwargs): + if not kwargs: + # During the loading of the twisted plugins, we just need the default values. + # This part is not executed when the plugin is actually started. + for name, value in [(option[0], option[2]) for option in self.OPT_PARAMETERS]: + kwargs[name] = value + + self.connection_type = kwargs['connection_type'] + self.port = kwargs['port'] + self.port_https = kwargs['port_https'] + self.ssl_certificate = kwargs['ssl_certificate'] + self.redirect_to_https = kwargs['redirect_to_https'] + self.security_warning = kwargs['security_warning'] self._cleanup = [] - self.port = port root = ProtectedFile(Const.LIBERVIA_DIR) self.signal_handler = SignalHandler(self) _register = Register(self) @@ -1015,7 +1069,25 @@ self._cleanup.insert(0, (callback, args, kwargs)) def startService(self): - reactor.listenTCP(self.port, self.site) + if self.connection_type in ('https', 'both'): + if not ssl_available: + raise(ImportError(_("Python module pyOpenSSL is not installed!"))) + try: + with open(self.ssl_certificate) as keyAndCert: + try: + cert = ssl.PrivateCertificate.loadPEM(keyAndCert.read()) + except OpenSSL.crypto.Error as e: + error(_("The file '%s' must contain both private and public parts of the certificate") % self.ssl_certificate) + raise e + except IOError as e: + error(_("The file '%s' doesn't exist") % self.ssl_certificate) + raise e + reactor.listenSSL(self.port_https, self.site, cert.options()) + if self.connection_type in ('http', 'both'): + if self.connection_type == 'both' and self.redirect_to_https: + reactor.listenTCP(self.port, server.Site(RedirectToHTTPS(self.port, self.port_https))) + else: + reactor.listenTCP(self.port, self.site) def stopService(self): print "launching cleaning methods" @@ -1028,6 +1100,21 @@ def stop(self): reactor.stop() + +class RedirectToHTTPS(Resource): + + def __init__(self, old_port, new_port): + Resource.__init__(self) + self.isLeaf = True + self.old_port = old_port + self.new_port = new_port + + def render(self, request): + netloc = request.URLPath().netloc.replace(':%s' % self.old_port, ':%s' % self.new_port) + url = "https://" + netloc + request.uri + return redirectTo(url, request) + + registerAdapter(SATSession, server.Session, ISATSession) application = service.Application(Const.APP_NAME) service = Libervia()