← 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:
  OpenLP Core (openlp-core)

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

Implement an HTTPS server for the web remote and the Android remote.
-- 
https://code.launchpad.net/~raoul-snyman/openlp/ssl/+merge/106461
Your team OpenLP Core is requested to review the proposed merge of lp:~raoul-snyman/openlp/ssl into 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-18 20:18: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-18 20:18: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,7 +145,41 @@
             self.code = code
 
 
-class HttpServer(object):
+class SslServer(QtNetwork.QTcpServer):
+    """
+    SslServer is a class that implements an HTTPS server.
+    """
+    sslCertificate = None
+    sslPrivateKey = None
+    connections = []
+
+    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.
     """
@@ -152,6 +187,7 @@
         """
         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(
@@ -161,32 +197,58 @@
         self.current_item = None
         self.current_slide = None
         self.start_tcp()
+        self.start_ssl()
 
     def start_tcp(self):
         """
-        Start the http server, use the port in the settings default to 4316.
+        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)
+        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(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(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(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.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 +267,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 +276,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 +285,8 @@
         Close down the http server.
         """
         log.debug(u'close http server')
-        self.server.close()
+        self.http_server.close()
+        self.https_server.close()
 
 
 class HttpConnection(object):
@@ -252,10 +316,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 +380,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 +428,29 @@
             else:
                 self.send_response(HttpResponse(code='404 Not Found'))
             self.close()
+        else:
+            self.send_response(HttpResponse(code='400 Bad Request'))
+            self.close()
 
     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 +496,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 +528,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 +576,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 +592,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 +637,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 +656,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 +669,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 +684,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-18 20:18:19 +0000
@@ -60,26 +60,46 @@
         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.httpPortLabel = QtGui.QLabel(self.serverSettingsGroupBox)
+        self.httpPortLabel.setObjectName(u'httpPortLabel')
+        self.httpPortSpinBox = QtGui.QSpinBox(self.serverSettingsGroupBox)
+        self.httpPortSpinBox.setMaximum(32767)
+        self.httpPortSpinBox.setObjectName(u'httpPortSpinBox')
+        QtCore.QObject.connect(self.httpPortSpinBox,
+            QtCore.SIGNAL(u'valueChanged(int)'), self.setUrls)
+        self.serverSettingsLayout.addRow(
+            self.httpPortLabel, self.httpPortSpinBox)
+        self.httpsPortLabel = QtGui.QLabel(self.serverSettingsGroupBox)
+        self.httpsPortLabel.setObjectName(u'httpsPortLabel')
+        self.httpsPortSpinBox = QtGui.QSpinBox(self.serverSettingsGroupBox)
+        self.httpsPortSpinBox.setMaximum(32767)
+        self.httpsPortSpinBox.setObjectName(u'httpsPortSpinBox')
+        QtCore.QObject.connect(self.httpsPortSpinBox,
+            QtCore.SIGNAL(u'valueChanged(int)'), self.setUrls)
+        self.serverSettingsLayout.addRow(
+            self.httpsPortLabel, self.httpsPortSpinBox)
         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.remoteHttpUrl = QtGui.QLabel(self.serverSettingsGroupBox)
+        self.remoteHttpUrl.setObjectName(u'remoteHttpUrl')
+        self.remoteHttpUrl.setOpenExternalLinks(True)
+        self.serverSettingsLayout.addRow(
+            self.remoteUrlLabel, self.remoteHttpUrl)
+        self.remoteHttpsUrl = QtGui.QLabel(self.serverSettingsGroupBox)
+        self.remoteHttpsUrl.setObjectName(u'remoteHttpsUrl')
+        self.remoteHttpsUrl.setOpenExternalLinks(True)
+        self.serverSettingsLayout.addRow(u'', self.remoteHttpsUrl)
         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.stageHttpUrl = QtGui.QLabel(self.serverSettingsGroupBox)
+        self.stageHttpUrl.setObjectName(u'stageHttpUrl')
+        self.stageHttpUrl.setOpenExternalLinks(True)
+        self.serverSettingsLayout.addRow(
+            self.stageUrlLabel, self.stageHttpUrl)
+        self.stageHttpsUrl = QtGui.QLabel(self.serverSettingsGroupBox)
+        self.stageHttpsUrl.setObjectName(u'stageHttpsUrl')
+        self.stageHttpsUrl.setOpenExternalLinks(True)
+        self.serverSettingsLayout.addRow(u'', self.stageHttpsUrl)
         self.leftLayout.addWidget(self.serverSettingsGroupBox)
         self.androidAppGroupBox = QtGui.QGroupBox(self.rightColumn)
         self.androidAppGroupBox.setObjectName(u'androidAppGroupBox')
@@ -108,8 +128,10 @@
             translate('RemotePlugin.RemoteTab', 'Server Settings'))
         self.addressLabel.setText(translate('RemotePlugin.RemoteTab',
             'Serve on IP address:'))
-        self.portLabel.setText(translate('RemotePlugin.RemoteTab',
-            'Port number:'))
+        self.httpPortLabel.setText(translate('RemotePlugin.RemoteTab',
+            'HTTP port number:'))
+        self.httpsPortLabel.setText(translate('RemotePlugin.RemoteTab',
+            'HTTPS port number:'))
         self.remoteUrlLabel.setText(translate('RemotePlugin.RemoteTab',
             'Remote URL:'))
         self.stageUrlLabel.setText(translate('RemotePlugin.RemoteTab',
@@ -125,7 +147,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 +160,63 @@
                     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()
+        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])
+        if ip_address != self.addressEdit.text() or \
+            http_port != self.httpPortSpinBox.value() or\
+            https_port != self.httpsPortSpinBox.value():
             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.endGroup()
         if changed:
             Receiver.send_message(u'remotes_config_updated')
 


Follow ups