← Back to team overview

openlp-core team mailing list archive

[Merge] lp:~raoul-snyman/openlp/ssl into lp:openlp

 

Raoul Snyman has proposed merging lp:~raoul-snyman/openlp/ssl into lp:openlp.

Requested reviews:
  Tim Bentley (trb143)

For more details, see:
https://code.launchpad.net/~raoul-snyman/openlp/ssl/+merge/106536

Implement an HTTPS server for the web remote and the Android remote.
-- 
https://code.launchpad.net/~raoul-snyman/openlp/ssl/+merge/106536
Your team OpenLP Core is subscribed to branch lp:openlp.
=== modified file 'openlp/core/utils/__init__.py'
--- openlp/core/utils/__init__.py	2012-05-01 12:58:22 +0000
+++ openlp/core/utils/__init__.py	2012-05-20 18:06:19 +0000
@@ -88,6 +88,7 @@
     VersionDir = 5
     CacheDir = 6
     LanguageDir = 7
+    SharedData = 8
 
     # Base path where data/config/cache dir is located
     BaseDir = None
@@ -151,7 +152,8 @@
         if dir_type == AppLocation.DataDir:
             return os.path.join(unicode(os.getenv(u'APPDATA'), encoding),
                 u'openlp', u'data')
-        elif dir_type == AppLocation.LanguageDir:
+        elif dir_type == AppLocation.LanguageDir or \
+            dir_type == AppLocation.SharedData:
             return os.path.split(openlp.__file__)[0]
         return os.path.join(unicode(os.getenv(u'APPDATA'), encoding),
             u'openlp')
@@ -159,12 +161,14 @@
         if dir_type == AppLocation.DataDir:
             return os.path.join(unicode(os.getenv(u'HOME'), encoding),
                 u'Library', u'Application Support', u'openlp', u'Data')
-        elif dir_type == AppLocation.LanguageDir:
+        elif dir_type == AppLocation.LanguageDir or \
+             dir_type == AppLocation.SharedData:
             return os.path.split(openlp.__file__)[0]
         return os.path.join(unicode(os.getenv(u'HOME'), encoding),
             u'Library', u'Application Support', u'openlp')
     else:
-        if dir_type == AppLocation.LanguageDir:
+        if dir_type == AppLocation.LanguageDir or \
+           dir_type == AppLocation.SharedData:
             return os.path.join(u'/usr', u'share', u'openlp')
         if XDG_BASE_AVAILABLE:
             if dir_type == AppLocation.ConfigDir:

=== modified file 'openlp/plugins/remotes/lib/httpserver.py'
--- openlp/plugins/remotes/lib/httpserver.py	2012-04-20 19:36:10 +0000
+++ openlp/plugins/remotes/lib/httpserver.py	2012-05-20 18:06:19 +0000
@@ -27,8 +27,8 @@
 
 """
 The :mod:`http` module contains the API web server. This is a lightweight web
-server used by remotes to interact with OpenLP. It uses JSON to communicate with
-the remotes.
+server used by remotes to interact with OpenLP. It uses JSON to communicate
+with the remotes.
 
 *Routes:*
 
@@ -126,6 +126,7 @@
 
 log = logging.getLogger(__name__)
 
+
 class HttpResponse(object):
     """
     A simple object to encapsulate a pseudo-http response.
@@ -144,14 +145,60 @@
             self.code = code
 
 
-class HttpServer(object):
+class SslServer(QtNetwork.QTcpServer):
+    """
+    SslServer is a class that implements an HTTPS server.
+    """
+    sslCertificate = None
+    sslPrivateKey = None
+    connections = []
+
+    @staticmethod
+    def checkCertificates():
+        cert_path = AppLocation.get_directory(AppLocation.SharedData)
+        crt_exists = os.path.exists(os.path.join(cert_path, u'openlp.crt'))
+        key_exists = os.path.exists(os.path.join(cert_path, u'openlp.key'))
+        return crt_exists and key_exists
+
+    def incomingConnection(self, socket_descriptor):
+        """
+        This method overrides the default one in :method:`incomingConnection`
+        to provide the SSL socket support needed for HTTPS.
+        """
+        log.debug(u'Incoming HTTPS connection')
+        cert_path = AppLocation.get_directory(AppLocation.SharedData)
+        if not SslServer.sslCertificate:
+            ssl_cert_data = QtCore.QByteArray(
+                open(os.path.join(cert_path, u'openlp.crt'), u'rb').read())
+            SslServer.sslCertificate = QtNetwork.QSslCertificate(ssl_cert_data)
+        if not SslServer.sslPrivateKey:
+            ssl_key_data = QtCore.QByteArray(
+                open(os.path.join(cert_path, u'openlp.key'), u'rb').read())
+            SslServer.sslPrivateKey = QtNetwork.QSslKey(ssl_key_data,
+                QtNetwork.QSsl.Rsa)
+        server_socket = QtNetwork.QSslSocket()
+        if server_socket.setSocketDescriptor(socket_descriptor):
+            server_socket.setPrivateKey(SslServer.sslPrivateKey)
+            server_socket.setLocalCertificate(SslServer.sslCertificate)
+            server_socket.setPeerVerifyMode(QtNetwork.QSslSocket.VerifyNone)
+            server_socket.startServerEncryption()
+            self.connections.append(server_socket)
+            self.addPendingConnection(server_socket)
+
+
+class HttpServer(QtCore.QObject):
     """
     Ability to control OpenLP via a web browser.
     """
+
+    http_server = None
+    https_server = None
+
     def __init__(self, plugin):
         """
         Initialise the httpserver, and start the server.
         """
+        QtCore.QObject.__init__(self)
         log.debug(u'Initialise httpserver')
         self.plugin = plugin
         self.html_dir = os.path.join(
@@ -160,33 +207,82 @@
         self.connections = []
         self.current_item = None
         self.current_slide = None
-        self.start_tcp()
-
-    def start_tcp(self):
-        """
-        Start the http server, use the port in the settings default to 4316.
-        Listen out for slide and song changes so they can be broadcast to
-        clients. Listen out for socket connections.
-        """
-        log.debug(u'Start TCP server')
-        port = QtCore.QSettings().value(
-            self.plugin.settingsSection + u'/port',
-            QtCore.QVariant(4316)).toInt()[0]
-        address = QtCore.QSettings().value(
-            self.plugin.settingsSection + u'/ip address',
-            QtCore.QVariant(u'0.0.0.0')).toString()
-        self.server = QtNetwork.QTcpServer()
-        self.server.listen(QtNetwork.QHostAddress(address), port)
         QtCore.QObject.connect(Receiver.get_receiver(),
             QtCore.SIGNAL(u'slidecontroller_live_changed'),
             self.slide_change)
         QtCore.QObject.connect(Receiver.get_receiver(),
             QtCore.SIGNAL(u'slidecontroller_live_started'),
             self.item_change)
-        QtCore.QObject.connect(self.server,
+        QtCore.QObject.connect(Receiver.get_receiver(),
+            QtCore.SIGNAL(u'remotes_config_updated'),
+            self.update_servers)
+        self.start_tcp()
+        if SslServer.checkCertificates() and \
+            QtCore.QSettings().value(self.plugin.settingsSection + \
+                u'/https enabled', QtCore.QVariant(False)).toBool():
+            self.start_ssl()
+
+    def update_servers(self):
+        """
+        Restart the server(s) if the configuration changes.
+        """
+        log.debug(u'Config changed, updating servers')
+        https_enabled = QtCore.QSettings().value(
+            self.plugin.settingsSection + u'/https enabled',
+            QtCore.QVariant(False)).toBool() and SslServer.checkCertificates()
+        # Restart the HTTP server
+        self.http_server.close()
+        self.http_server = None
+        self.start_tcp()
+        # Check the status of the HTTPS server and restart it
+        if not self.https_server and https_enabled:
+            self.start_ssl()
+        elif https_enabled and self.https_server:
+            self.https_server.close()
+            self.https_server = None
+            self.start_ssl()
+        elif self.https_server and not https_enabled:
+            self.https_server.close()
+            self.https_server = None
+
+    def start_tcp(self):
+        """
+        Start the HTTP server, use the port in the settings default to 4316.
+        Listen out for slide and song changes so they can be broadcast to
+        clients. Listen out for socket connections.
+        """
+        log.debug(u'Start TCP server')
+        settings = QtCore.QSettings()
+        settings.beginGroup(self.plugin.settingsSection)
+        port = settings.value(u'port', QtCore.QVariant(4316)).toInt()[0]
+        address = settings.value(
+            u'ip address', QtCore.QVariant(u'0.0.0.0')).toString()
+        settings.endGroup()
+        self.http_server = QtNetwork.QTcpServer()
+        self.http_server.listen(QtNetwork.QHostAddress(address), port)
+        QtCore.QObject.connect(self.http_server,
             QtCore.SIGNAL(u'newConnection()'), self.new_connection)
         log.debug(u'TCP listening on port %d' % port)
 
+    def start_ssl(self):
+        """
+        Start the HTTPS server, use the port in the settings default to 4317.
+        Listen out for slide and song changes so they can be broadcast to
+        clients. Listen out for socket connections.
+        """
+        log.debug(u'Start SSL server')
+        settings = QtCore.QSettings()
+        settings.beginGroup(self.plugin.settingsSection)
+        port = settings.value(u'ssl port', QtCore.QVariant(4317)).toInt()[0]
+        address = settings.value(
+            u'ip address', QtCore.QVariant(u'0.0.0.0')).toString()
+        settings.endGroup()
+        self.https_server = SslServer()
+        self.https_server.listen(QtNetwork.QHostAddress(address), port)
+        QtCore.QObject.connect(self.https_server,
+            QtCore.SIGNAL(u'newConnection()'), self.new_connection)
+        log.debug(u'SSL listening on port %d' % port)
+
     def slide_change(self, row):
         """
         Slide change listener. Store the item and tell the clients.
@@ -205,7 +301,8 @@
         communication.
         """
         log.debug(u'new http connection')
-        socket = self.server.nextPendingConnection()
+        server = self.sender()
+        socket = server.nextPendingConnection()
         if socket:
             self.connections.append(HttpConnection(self, socket))
 
@@ -213,7 +310,7 @@
         """
         The connection has been closed. Clean up
         """
-        log.debug(u'close http connection')
+        log.debug(u'close connection')
         if connection in self.connections:
             self.connections.remove(connection)
 
@@ -222,7 +319,9 @@
         Close down the http server.
         """
         log.debug(u'close http server')
-        self.server.close()
+        self.http_server.close()
+        if self.https_server:
+            self.https_server.close()
 
 
 class HttpConnection(object):
@@ -252,10 +351,17 @@
             (r'^/api/(.*)/live$', self.go_live),
             (r'^/api/(.*)/add$', self.add_to_service)
         ]
-        QtCore.QObject.connect(self.socket, QtCore.SIGNAL(u'readyRead()'),
-            self.ready_read)
-        QtCore.QObject.connect(self.socket, QtCore.SIGNAL(u'disconnected()'),
-            self.disconnected)
+        if isinstance(socket, QtNetwork.QSslSocket):
+            QtCore.QObject.connect(self.socket, QtCore.SIGNAL(u'encrypted()'),
+                self.encrypted)
+            QtCore.QObject.connect(self.socket,
+                QtCore.SIGNAL(u'sslErrors(const QList<QSslError> &)'),
+                self.sslErrors)
+        else:
+            QtCore.QObject.connect(self.socket, QtCore.SIGNAL(u'readyRead()'),
+                self.ready_read)
+            QtCore.QObject.connect(self.socket,
+                QtCore.SIGNAL(u'disconnected()'), self.disconnected)
         self.translate()
 
     def _get_service_items(self):
@@ -309,17 +415,32 @@
             'options': translate('RemotePlugin.Mobile', 'Options')
         }
 
+    def encrypted(self):
+        """
+        Only setup these slots when the data is encrypted.
+        """
+        QtCore.QObject.connect(self.socket, QtCore.SIGNAL(u'readyRead()'),
+            self.ready_read)
+        QtCore.QObject.connect(self.socket, QtCore.SIGNAL(u'disconnected()'),
+            self.disconnected)
+
+    def sslErrors(self, errors):
+        for error in errors:
+            log.warn(unicode(error.errorString()))
+        self.socket.ignoreSslErrors()
+
     def ready_read(self):
         """
         Data has been sent from the client. Respond to it
         """
-        log.debug(u'ready to read socket')
+        log.debug(u'Ready to read socket')
         if self.socket.canReadLine():
             data = str(self.socket.readLine())
             try:
-                log.debug(u'received: ' + data)
+                log.debug(u'Received: ' + data)
             except UnicodeDecodeError:
                 # Malicious request containing non-ASCII characters.
+                self.send_response(HttpResponse(code='400 Bad Request'))
                 self.close()
                 return
             words = data.split(' ')
@@ -342,23 +463,28 @@
             else:
                 self.send_response(HttpResponse(code='404 Not Found'))
             self.close()
+        else:
+            return
 
     def serve_file(self, filename=None):
         """
-        Send a file to the socket. For now, just a subset of file types
-        and must be top level inside the html folder.
-        If subfolders requested return 404, easier for security for the present.
+        Send a file to the socket. For now, only a subset of file types will
+        be send, and all files must be at the top level inside the html folder.
+        If sub-folders are requested return 404, easier for security for the
+        present.
 
         Ultimately for i18n, this could first look for xx/file.html before
         falling back to file.html... where xx is the language, e.g. 'en'
         """
-        log.debug(u'serve file request %s' % filename)
+        log.debug(u'serve file request (original) %s' % filename)
         if not filename:
             filename = u'index.html'
         elif filename == u'stage':
             filename = u'stage.html'
+        log.debug(u'serve file request (updated) %s' % filename)
         path = os.path.normpath(os.path.join(self.parent.html_dir, filename))
         if not path.startswith(self.parent.html_dir):
+            log.debug(u'File not found, returning 404')
             return HttpResponse(code=u'404 Not Found')
         ext = os.path.splitext(filename)[1]
         html = None
@@ -404,8 +530,8 @@
             u'slide': self.parent.current_slide or 0,
             u'item': self.parent.current_item._uuid \
                 if self.parent.current_item else u'',
-            u'twelve':QtCore.QSettings().value(
-            u'remotes/twelve hour', QtCore.QVariant(True)).toBool(),
+            u'twelve': QtCore.QSettings().value(
+                u'remotes/twelve hour', QtCore.QVariant(True)).toBool(),
             u'blank': self.parent.plugin.liveController.blankScreen.\
                 isChecked(),
             u'theme': self.parent.plugin.liveController.themeScreen.\
@@ -436,7 +562,7 @@
             try:
                 text = json.loads(
                     self.url_params[u'data'][0])[u'request'][u'text']
-            except KeyError, ValueError:
+            except (KeyError, ValueError):
                 return HttpResponse(code=u'400 Bad Request')
             text = urllib.unquote(text)
             Receiver.send_message(u'alerts_text', [text])
@@ -484,7 +610,7 @@
             if self.url_params and self.url_params.get(u'data'):
                 try:
                     data = json.loads(self.url_params[u'data'][0])
-                except KeyError, ValueError:
+                except (KeyError, ValueError):
                     return HttpResponse(code=u'400 Bad Request')
                 log.info(data)
                 # This slot expects an int within a list.
@@ -500,14 +626,16 @@
         event = u'servicemanager_%s' % action
         if action == u'list':
             return HttpResponse(
-                json.dumps({u'results': {u'items': self._get_service_items()}}),
+                json.dumps({
+                    u'results': {u'items': self._get_service_items()}
+                }),
                 {u'Content-Type': u'application/json'})
         else:
             event += u'_item'
         if self.url_params and self.url_params.get(u'data'):
             try:
                 data = json.loads(self.url_params[u'data'][0])
-            except KeyError, ValueError:
+            except (KeyError, ValueError):
                 return HttpResponse(code=u'400 Bad Request')
             Receiver.send_message(event, data[u'request'][u'id'])
         else:
@@ -543,7 +671,7 @@
         """
         try:
             text = json.loads(self.url_params[u'data'][0])[u'request'][u'text']
-        except KeyError, ValueError:
+        except (KeyError, ValueError):
             return HttpResponse(code=u'400 Bad Request')
         text = urllib.unquote(text)
         plugin = self.parent.plugin.pluginManager.get_plugin_by_name(type)
@@ -562,7 +690,7 @@
         """
         try:
             id = json.loads(self.url_params[u'data'][0])[u'request'][u'id']
-        except KeyError, ValueError:
+        except (KeyError, ValueError):
             return HttpResponse(code=u'400 Bad Request')
         plugin = self.parent.plugin.pluginManager.get_plugin_by_name(type)
         if plugin.status == PluginStatus.Active and plugin.mediaItem:
@@ -575,7 +703,7 @@
         """
         try:
             id = json.loads(self.url_params[u'data'][0])[u'request'][u'id']
-        except KeyError, ValueError:
+        except (KeyError, ValueError):
             return HttpResponse(code=u'400 Bad Request')
         plugin = self.parent.plugin.pluginManager.get_plugin_by_name(type)
         if plugin.status == PluginStatus.Active and plugin.mediaItem:
@@ -590,6 +718,7 @@
         http += '\r\n'
         self.socket.write(http)
         self.socket.write(response.content)
+        self.socket.flush()
 
     def disconnected(self):
         """

=== modified file 'openlp/plugins/remotes/lib/remotetab.py'
--- openlp/plugins/remotes/lib/remotetab.py	2012-03-18 09:33:05 +0000
+++ openlp/plugins/remotes/lib/remotetab.py	2012-05-20 18:06:19 +0000
@@ -26,8 +26,10 @@
 ###############################################################################
 
 from PyQt4 import QtCore, QtGui, QtNetwork
+import os.path
 
 from openlp.core.lib import SettingsTab, translate, Receiver
+from openlp.core.utils import AppLocation
 
 ZERO_URL = u'0.0.0.0'
 
@@ -41,7 +43,9 @@
     def setupUi(self):
         self.setObjectName(u'RemoteTab')
         SettingsTab.setupUi(self)
+        # Main server settings
         self.serverSettingsGroupBox = QtGui.QGroupBox(self.leftColumn)
+        self.serverSettingsGroupBox.setContentsMargins(8, 8, 8, 8)
         self.serverSettingsGroupBox.setObjectName(u'serverSettingsGroupBox')
         self.serverSettingsLayout = QtGui.QFormLayout(
             self.serverSettingsGroupBox)
@@ -60,27 +64,68 @@
         self.twelveHourCheckBox = QtGui.QCheckBox(self.serverSettingsGroupBox)
         self.twelveHourCheckBox.setObjectName(u'twelveHourCheckBox')
         self.serverSettingsLayout.addRow(self.twelveHourCheckBox)
-        self.portLabel = QtGui.QLabel(self.serverSettingsGroupBox)
-        self.portLabel.setObjectName(u'portLabel')
-        self.portSpinBox = QtGui.QSpinBox(self.serverSettingsGroupBox)
-        self.portSpinBox.setMaximum(32767)
-        self.portSpinBox.setObjectName(u'portSpinBox')
-        QtCore.QObject.connect(self.portSpinBox,
-            QtCore.SIGNAL(u'valueChanged(int)'), self.setUrls)
-        self.serverSettingsLayout.addRow(self.portLabel, self.portSpinBox)
-        self.remoteUrlLabel = QtGui.QLabel(self.serverSettingsGroupBox)
-        self.remoteUrlLabel.setObjectName(u'remoteUrlLabel')
-        self.remoteUrl = QtGui.QLabel(self.serverSettingsGroupBox)
-        self.remoteUrl.setObjectName(u'remoteUrl')
-        self.remoteUrl.setOpenExternalLinks(True)
-        self.serverSettingsLayout.addRow(self.remoteUrlLabel, self.remoteUrl)
-        self.stageUrlLabel = QtGui.QLabel(self.serverSettingsGroupBox)
-        self.stageUrlLabel.setObjectName(u'stageUrlLabel')
-        self.stageUrl = QtGui.QLabel(self.serverSettingsGroupBox)
-        self.stageUrl.setObjectName(u'stageUrl')
-        self.stageUrl.setOpenExternalLinks(True)
-        self.serverSettingsLayout.addRow(self.stageUrlLabel, self.stageUrl)
         self.leftLayout.addWidget(self.serverSettingsGroupBox)
+        self.httpSettingsGroupBox = QtGui.QGroupBox(self.leftColumn)
+        self.httpSettingsGroupBox.setObjectName(u'httpSettingsGroupBox')
+        self.httpSettingsLayout = QtGui.QFormLayout(
+            self.httpSettingsGroupBox)
+        self.httpSettingsLayout.setObjectName(u'httpSettingsLayout')
+        self.httpPortLabel = QtGui.QLabel(self.httpSettingsGroupBox)
+        self.httpPortLabel.setObjectName(u'httpPortLabel')
+        self.httpPortSpinBox = QtGui.QSpinBox(self.httpSettingsGroupBox)
+        self.httpPortSpinBox.setMaximum(32767)
+        self.httpPortSpinBox.setObjectName(u'httpPortSpinBox')
+        self.httpSettingsLayout.addRow(
+            self.httpPortLabel, self.httpPortSpinBox)
+        self.remoteHttpUrlLabel = QtGui.QLabel(self.httpSettingsGroupBox)
+        self.remoteHttpUrlLabel.setObjectName(u'remoteHttpUrlLabel')
+        self.remoteHttpUrl = QtGui.QLabel(self.httpSettingsGroupBox)
+        self.remoteHttpUrl.setObjectName(u'remoteHttpUrl')
+        self.remoteHttpUrl.setOpenExternalLinks(True)
+        self.httpSettingsLayout.addRow(
+            self.remoteHttpUrlLabel, self.remoteHttpUrl)
+        self.stageHttpUrlLabel = QtGui.QLabel(self.httpSettingsGroupBox)
+        self.stageHttpUrlLabel.setObjectName(u'stageHttpUrlLabel')
+        self.stageHttpUrl = QtGui.QLabel(self.httpSettingsGroupBox)
+        self.stageHttpUrl.setObjectName(u'stageHttpUrl')
+        self.stageHttpUrl.setOpenExternalLinks(True)
+        self.httpSettingsLayout.addRow(
+            self.stageHttpUrlLabel, self.stageHttpUrl)
+        self.leftLayout.addWidget(self.httpSettingsGroupBox)
+        self.httpsSettingsGroupBox = QtGui.QGroupBox(self.leftColumn)
+        self.httpsSettingsGroupBox.setCheckable(True)
+        self.httpsSettingsGroupBox.setChecked(False)
+        self.httpsSettingsGroupBox.setObjectName(u'httpsSettingsGroupBox')
+        self.httpsSettingsLayout = QtGui.QFormLayout(
+            self.httpsSettingsGroupBox)
+        self.httpsSettingsLayout.setObjectName(u'httpsSettingsLayout')
+        self.httpsErrorLabel = QtGui.QLabel(self.httpsSettingsGroupBox)
+        self.httpsErrorLabel.setVisible(False)
+        self.httpsErrorLabel.setWordWrap(True)
+        self.httpsErrorLabel.setObjectName(u'httpsErrorLabel')
+        self.httpsSettingsLayout.addRow(self.httpsErrorLabel)
+        self.httpsPortLabel = QtGui.QLabel(self.httpsSettingsGroupBox)
+        self.httpsPortLabel.setObjectName(u'httpsPortLabel')
+        self.httpsPortSpinBox = QtGui.QSpinBox(self.httpsSettingsGroupBox)
+        self.httpsPortSpinBox.setMaximum(32767)
+        self.httpsPortSpinBox.setObjectName(u'httpsPortSpinBox')
+        self.httpsSettingsLayout.addRow(
+            self.httpsPortLabel, self.httpsPortSpinBox)
+        self.remoteHttpsUrl = QtGui.QLabel(self.httpsSettingsGroupBox)
+        self.remoteHttpsUrl.setObjectName(u'remoteHttpsUrl')
+        self.remoteHttpsUrl.setOpenExternalLinks(True)
+        self.remoteHttpsUrlLabel = QtGui.QLabel(self.httpsSettingsGroupBox)
+        self.remoteHttpsUrlLabel.setObjectName(u'remoteHttpsUrlLabel')
+        self.httpsSettingsLayout.addRow(
+            self.remoteHttpsUrlLabel, self.remoteHttpsUrl)
+        self.stageHttpsUrlLabel = QtGui.QLabel(self.httpsSettingsGroupBox)
+        self.stageHttpsUrlLabel.setObjectName(u'stageHttpsUrlLabel')
+        self.stageHttpsUrl = QtGui.QLabel(self.httpsSettingsGroupBox)
+        self.stageHttpsUrl.setObjectName(u'stageHttpsUrl')
+        self.stageHttpsUrl.setOpenExternalLinks(True)
+        self.httpsSettingsLayout.addRow(
+            self.stageHttpsUrlLabel, self.stageHttpsUrl)
+        self.leftLayout.addWidget(self.httpsSettingsGroupBox)
         self.androidAppGroupBox = QtGui.QGroupBox(self.rightColumn)
         self.androidAppGroupBox.setObjectName(u'androidAppGroupBox')
         self.rightLayout.addWidget(self.androidAppGroupBox)
@@ -99,6 +144,10 @@
         self.qrLayout.addWidget(self.qrDescriptionLabel)
         self.leftLayout.addStretch()
         self.rightLayout.addStretch()
+        QtCore.QObject.connect(self.httpPortSpinBox,
+            QtCore.SIGNAL(u'valueChanged(int)'), self.setUrls)
+        QtCore.QObject.connect(self.httpsPortSpinBox,
+            QtCore.SIGNAL(u'valueChanged(int)'), self.setUrls)
         QtCore.QObject.connect(self.twelveHourCheckBox,
             QtCore.SIGNAL(u'stateChanged(int)'),
             self.onTwelveHourCheckBoxChanged)
@@ -108,11 +157,25 @@
             translate('RemotePlugin.RemoteTab', 'Server Settings'))
         self.addressLabel.setText(translate('RemotePlugin.RemoteTab',
             'Serve on IP address:'))
-        self.portLabel.setText(translate('RemotePlugin.RemoteTab',
-            'Port number:'))
-        self.remoteUrlLabel.setText(translate('RemotePlugin.RemoteTab',
-            'Remote URL:'))
-        self.stageUrlLabel.setText(translate('RemotePlugin.RemoteTab',
+        self.httpSettingsGroupBox.setTitle(
+            translate('RemotePlugin.RemoteTab', 'HTTP Server'))
+        self.httpPortLabel.setText(translate('RemotePlugin.RemoteTab',
+            'Port number:'))
+        self.remoteHttpUrlLabel.setText(translate('RemotePlugin.RemoteTab',
+            'Remote URL:'))
+        self.stageHttpUrlLabel.setText(translate('RemotePlugin.RemoteTab',
+            'Stage view URL:'))
+        self.httpsSettingsGroupBox.setTitle(
+            translate('RemotePlugin.RemoteTab', 'HTTPS Server'))
+        self.httpsErrorLabel.setText(translate('RemotePlugin.RemoteTab',
+            'Could not find an SSL certificate. The HTTPS server will not be '
+            'available unless an SSL certificate is found. Please see the '
+            'manual for more information.'))
+        self.httpsPortLabel.setText(translate('RemotePlugin.RemoteTab',
+            'Port number:'))
+        self.remoteHttpsUrlLabel.setText(translate('RemotePlugin.RemoteTab',
+            'Remote URL:'))
+        self.stageHttpsUrlLabel.setText(translate('RemotePlugin.RemoteTab',
             'Stage view URL:'))
         self.twelveHourCheckBox.setText(
             translate('RemotePlugin.RemoteTab',
@@ -125,7 +188,7 @@
             'download</a> to install the Android app from the Market.'))
 
     def setUrls(self):
-        ipAddress = u'localhost'
+        ip_address = u'localhost'
         if self.addressEdit.text() == ZERO_URL:
             ifaces = QtNetwork.QNetworkInterface.allInterfaces()
             for iface in ifaces:
@@ -138,41 +201,79 @@
                     ip = addr.ip()
                     if ip.protocol() == 0 and \
                         ip != QtNetwork.QHostAddress.LocalHost:
-                        ipAddress = ip.toString()
+                        ip_address = ip.toString()
                         break
         else:
-            ipAddress = self.addressEdit.text()
-        url = u'http://%s:%s/' % (ipAddress, self.portSpinBox.value())
-        self.remoteUrl.setText(u'<a href="%s">%s</a>' % (url, url))
-        url = url + u'stage'
-        self.stageUrl.setText(u'<a href="%s">%s</a>' % (url, url))
+            ip_address = self.addressEdit.text()
+        http_url = u'http://%s:%s/' % \
+            (ip_address, self.httpPortSpinBox.value())
+        https_url = u'https://%s:%s/' % \
+            (ip_address, self.httpsPortSpinBox.value())
+        self.remoteHttpUrl.setText(u'<a href="%s">%s</a>' % \
+            (http_url, http_url))
+        self.remoteHttpsUrl.setText(u'<a href="%s">%s</a>' % \
+            (https_url, https_url))
+        http_url += u'stage'
+        https_url += u'stage'
+        self.stageHttpUrl.setText(u'<a href="%s">%s</a>' % \
+            (http_url, http_url))
+        self.stageHttpsUrl.setText(u'<a href="%s">%s</a>' % \
+            (https_url, https_url))
 
     def load(self):
-        self.portSpinBox.setValue(
-            QtCore.QSettings().value(self.settingsSection + u'/port',
-                QtCore.QVariant(4316)).toInt()[0])
+        settings = QtCore.QSettings()
+        settings.beginGroup(self.settingsSection)
+        self.httpPortSpinBox.setValue(
+            settings.value(u'port', QtCore.QVariant(4316)).toInt()[0])
+        self.httpsPortSpinBox.setValue(
+            settings.value(u'ssl port', QtCore.QVariant(4317)).toInt()[0])
         self.addressEdit.setText(
-            QtCore.QSettings().value(self.settingsSection + u'/ip address',
-                QtCore.QVariant(ZERO_URL)).toString())
-        self.twelveHour = QtCore.QSettings().value(
-            self.settingsSection + u'/twelve hour',
-            QtCore.QVariant(True)).toBool()
+            settings.value(
+                u'ip address', QtCore.QVariant(ZERO_URL)).toString())
+        self.twelveHour = settings.value(
+            u'twelve hour', QtCore.QVariant(True)).toBool()
+        shared_data = AppLocation.get_directory(AppLocation.SharedData)
+        if not os.path.exists(os.path.join(shared_data, u'openlp.crt')) or \
+            not os.path.exists(os.path.join(shared_data, u'openlp.key')):
+            self.httpsSettingsGroupBox.setChecked(False)
+            self.httpsSettingsGroupBox.setEnabled(False)
+            self.httpsErrorLabel.setVisible(True)
+        else:
+            self.httpsSettingsGroupBox.setChecked(settings.value(
+                u'https enabled', QtCore.QVariant(False)).toBool())
+            self.httpsSettingsGroupBox.setEnabled(True)
+            self.httpsErrorLabel.setVisible(False)
+        settings.endGroup()
         self.twelveHourCheckBox.setChecked(self.twelveHour)
         self.setUrls()
 
     def save(self):
         changed = False
-        if QtCore.QSettings().value(self.settingsSection + u'/ip address',
-            QtCore.QVariant(ZERO_URL).toString() != self.addressEdit.text() or
-            QtCore.QSettings().value(self.settingsSection + u'/port',
-            QtCore.QVariant(4316).toInt()[0]) != self.portSpinBox.value()):
+        settings = QtCore.QSettings()
+        settings.beginGroup(self.settingsSection)
+        ip_address = settings.value(
+            u'ip address', QtCore.QVariant(ZERO_URL).toString())
+        http_port = settings.value(
+            u'port', QtCore.QVariant(4316).toInt()[0])
+        https_port = settings.value(
+            u'ssl port', QtCore.QVariant(4317).toInt()[0])
+        https_enabled = settings.value(
+            u'https enabled', QtCore.QVariant(False)).toBool()
+        if ip_address != self.addressEdit.text() or \
+            http_port != self.httpPortSpinBox.value() or\
+            https_port != self.httpsPortSpinBox.value() or \
+            https_enabled != self.httpsSettingsGroupBox.isChecked():
             changed = True
-        QtCore.QSettings().setValue(self.settingsSection + u'/port',
-            QtCore.QVariant(self.portSpinBox.value()))
-        QtCore.QSettings().setValue(self.settingsSection + u'/ip address',
-            QtCore.QVariant(self.addressEdit.text()))
-        QtCore.QSettings().setValue(self.settingsSection + u'/twelve hour',
-            QtCore.QVariant(self.twelveHour))
+        settings.setValue(
+            u'port', QtCore.QVariant(self.httpPortSpinBox.value()))
+        settings.setValue(
+            u'ssl port', QtCore.QVariant(self.httpsPortSpinBox.value()))
+        settings.setValue(
+            u'ip address', QtCore.QVariant(self.addressEdit.text()))
+        settings.setValue(u'twelve hour', QtCore.QVariant(self.twelveHour))
+        settings.setValue(u'https enabled',
+            QtCore.QVariant(self.httpsSettingsGroupBox.isChecked()))
+        settings.endGroup()
         if changed:
             Receiver.send_message(u'remotes_config_updated')
 


Follow ups