Example #1
0
class ServerDialog(QDialog, Ui_ServerDialogDesign):

    # The threadpool for the workers
    _threadPool = []
    # And it's lock
    _threadLock = RLock()

    def __init__(self, server=None, parent=None):
        """The `ServerDialog` constructor.

        Parameters:

        - `serverList`: a `ServerList` instance containing the list of
          available servers.
        - `server`: the name of a server. If provided, this server will
          be selected in the serverList view.
        """
        super(ServerDialog, self).__init__(parent)
        self.setupUi(self)

        self.networkIcon.setPixmap(pixmapFromTheme(
            'preferences-system-network',
            ':/icons/48/preferences-system-network')
        )
        self.authIcon.setPixmap(pixmapFromTheme(
            'preferences-other',
            ':/icons/48/preferences-other')
        )
        self.securityIcon.setPixmap(pixmapFromTheme(
            'preferences-system',
            ':/icons/48/preferences-system')
        )
        
        self.__serverList = ServerList() #Load the serverlist from disk.
        self.__serverListCopy = None # When we click "Save", the current list is saved here
        
        # The list actually returned to the caller. Could be the copy or current active list
        self.__returnList = None 

        # Create the model used by the views and connect signals for registering
        # changes (to define the "Cancel"-buttons behaviour).
        self.slm = ServerListModel(self.__serverList, self)
        self.slm.dataChanged.connect(self.wasChanged)
        self.slm.rowsInserted.connect(self.wasChanged)
        self.slm.rowsRemoved.connect(self.wasChanged)
        
        # Used for determining if we should confirm cancel
        self.isChanged = False

        # The serverListView works on the servermodel
        self.serverListView.setModel(self.slm)

        # Enable/disable editing depending on if we have a server to edit
        if self.slm.hasServers():
            self.tabWidget.setEnabled(True)
            self.testConnectionButton.setEnabled(True)
        else:
            self.tabWidget.setEnabled(False)
            self.testConnectionButton.setEnabled(False)

        self.splitter.setStretchFactor(1, 0)

        # Update list of baseDNs on serverchange
        self.serverListView.selectionModel().selectionChanged.connect(self.setBaseDN) #Same as below

        # Map columns of the model to fields in the gui
        self.mapper = QDataWidgetMapper()
        self.mapper.setModel(self.slm)
        
        # The delegate handles the comboboxes and to-from the list of custom baseDNs
        # (delegate is used manually for the baseDNs)
        self.serverDelegate = ServerDelegate()
        self.mapper.setItemDelegate(self.serverDelegate)
        self.mapper.addMapping(self.hostEdit, 1)
        self.mapper.addMapping(self.portSpinBox, 2)
        self.mapper.addMapping(self.bindAnonBox, 3)
        self.mapper.addMapping(self.baseDNBox, 4)
        self.mapper.addMapping(self.bindAsEdit, 6)
        self.mapper.addMapping(self.passwordEdit, 7)
        self.mapper.addMapping(self.encryptionBox, 8)
        self.mapper.addMapping(self.mechanismBox, 9)
        self.mapper.addMapping(self.aliasBox, 10)
        self.mapper.addMapping(self.useClientCertBox, 11)
        self.mapper.addMapping(self.certFileEdit, 12)
        self.mapper.addMapping(self.certKeyfileEdit, 13)
        self.mapper.addMapping(self.validateBox, 14)

        # Workaround to ensure model being updated (Mac OS X bug)
        self.aliasBox.clicked.connect(self.aliasBox.setFocus)
        self.baseDNBox.clicked.connect(self.baseDNBox.setFocus)
        self.bindAnonBox.clicked.connect(self.bindAnonBox.setFocus)
        self.useClientCertBox.clicked.connect(self.useClientCertBox.setFocus)

        # Select the first servers (as the serverlistview does)
        self.mapper.setCurrentIndex(0)
        self.setBaseDN()

        # Let the mapper know when another server is selected in the list
        self.serverListView.selectionModel().currentRowChanged.connect(self.mapper.setCurrentModelIndex)
        
        # Enable checks for SSL enabled but with a non-standard port.
        self.encryptionBox.activated[int].connect(self.checkSSLport)

        # Used by the connection-test
        self.testProgress = QProgressDialog("Trying to connect to server.",
                "Abort",
                0, 0,
                self)
        self.testProgress.setWindowModality(Qt.WindowModal)

        # If a servername is supplied we try to get its index, 
        # And make it selected, else we select the first server
        # in the model)
        if not server is None:
            serverIndex = self.__serverList.getIndexByName(server)
            if serverIndex == -1:
                serverIndex = 0
            index = self.serverListView.model().index(serverIndex, 0)
        else:
            index = self.serverListView.model().index(0, 0)
        # Select it in the view
        self.serverListView.selectionModel().select(index, QItemSelectionModel.ClearAndSelect)
        self.serverListView.selectionModel().setCurrentIndex(index, QItemSelectionModel.ClearAndSelect)
        
    def checkSSLport(self, index):
        """ If SSL is choosen with a port other than 636, confirm this with the user
        """
        if index == ServerEncryptionMethod.SSL and self.portSpinBox.value() != 636:
            ans = QMessageBox.information(self, QCoreApplication.translate("ServerDialog","SSL")
                ,QCoreApplication.translate("ServerDialog","You have choosen to use SSL but with a port other than 636.\n Do you want this automatically changed?")
                ,QMessageBox.Yes|QMessageBox.No)
            if ans == QMessageBox.Yes:
                self.portSpinBox.setValue(636)
        if index == ServerEncryptionMethod.Unencrypted and self.portSpinBox.value() != 389:
            ans = QMessageBox.information(self, QCoreApplication.translate("ServerDialog","Unencrypted")
                ,QCoreApplication.translate("ServerDialog","You have choosen to use unencryped LDAP but with a port other than 389.\n Do you want this automatically changed?")
                ,QMessageBox.Yes|QMessageBox.No)
            if ans == QMessageBox.Yes:
                self.portSpinBox.setValue(389)

    def wasChanged(self):
        """Slot to register that some server settings is changed.
        """
        self.isChanged = True

    def addBaseDN(self):
        """Slot for adding a base DN
        """
        tmpBaseDN = unicode(self.baseDNEdit.text()).strip()
        if tmpBaseDN == u"":
            return
        self.baseDNListWidget.addItem(QListWidgetItem(tmpBaseDN)) #Add to list

        # Save the list of baseDNs
        serverIndex = self.serverListView.selectedIndexes()[0]
        index = self.slm.createIndex(serverIndex.row(), 5) # 5 = column for baseDNs
        self.serverDelegate.setModelData(self.baseDNListWidget, self.slm, index) #save to model
        self.baseDNEdit.clear() #Clear textfield
        self.mapper.submit() #Force push to model

    def deleteBaseDN(self):
        """Slot for deleting a base DN
        """
        # Delete every selected baseDN
        for tmpItem in self.baseDNListWidget.selectedItems():
            if not (None == tmpItem):
                index = self.baseDNListWidget.indexFromItem(tmpItem) #get the index to the basedn
                d = self.baseDNListWidget.takeItem(index.row()) #delete (actually steal) the baseDN from the list
                if d != 0:
                    del d # Per the QT-docs, someone needs to delete it

        # Save to model (see addBaseDN())
        serverIndex = self.serverListView.selectedIndexes()[0]
        index = self.slm.createIndex(serverIndex.row(), 5)
        self.serverDelegate.setModelData(self.baseDNListWidget, self.slm, index)
        self.mapper.submit() #Force push changes to model

    def setBaseDN(self):
        """Slot for setting the base DN-list.
        """
        serverIndex = self.serverListView.selectedIndexes()
        if len(serverIndex) > 0:
            index = self.slm.createIndex(serverIndex[0].row(), 5)
            self.serverDelegate.setEditorData(self.baseDNListWidget, index)

    def addServer(self):
        """Create a new ServerObject and add it to the model, and thus
        the server list.
        """
        name, ok = QInputDialog.getText(self, self.tr('Add server'), self.tr('Name:'))
        if ok:
            if len(name) < 1 or self.__serverList.getServerObject(name) != None:
                QMessageBox.information(self, self.tr('Error'), self.tr("Invalid name or already used."))
                return

            sO = ServerObject()
            sO.name = unicode(name)

            # Insert into the model
            m = self.serverListView.model()
            success, index = m.addServer(sO)
            if success:
                # Display the added server
                self.serverListView.selectionModel().select(index, QItemSelectionModel.ClearAndSelect) #Select it
                self.serverListView.selectionModel().setCurrentIndex(index, QItemSelectionModel.ClearAndSelect) #Mark it as current      
                self.mapper.setCurrentIndex(index.row()) # Update the mapper
                self.tabWidget.setEnabled(True) # Make sure editing is enabled
                self.testConnectionButton.setEnabled(True)

    def deleteServer(self):
        """Delete a server from the model/list
        """
        if self.serverListView.selectionModel().currentIndex().row() < 0:
            #No server selected
            return

        re = QMessageBox.question(self, self.tr('Delete'),
                     self.tr("Are you sure?"), 
                     QMessageBox.Yes, 
                     QMessageBox.No)

        if re == QMessageBox.Yes:
            index = self.serverListView.selectionModel().currentIndex() #Currently selected

            # Delete the server
            if self.serverListView.model().delServerAtIndex(index):
                # When deleting, the view gets updated and selects a new current.
                # Get it and give it to the mapper
                newIndex = self.serverListView.selectionModel().currentIndex()
                self.mapper.setCurrentIndex(newIndex.row())

        if not self.slm.hasServers():
            self.tabWidget.setEnabled(False) #Disable editing if no servers left
            self.testConnectionButton.setEnabled(False)

    def saveServerlist(self):
        """Called when the Save-button is clicked
        """
        self.mapper.submit()

        if not self.baseDNsOK():
            if not self.isBaseDNsOkMessage():
                # If we got here, we DO NOT WANT TO SAVE/QUIT
                return False

        self.__serverList.writeServerList()
        self.__serverListCopy = copy.deepcopy(self.__serverList)

        return True

    def reject(self):
        """Called when the users clicks cancel or presses escape
        """
        # If no changes: just quit
        if not self.isChanged:
            QDialog.reject(self)
            return


        # Really quit?
        r = QMessageBox.question(self, self.tr("Exit?"), self.tr("Are you sure you want to exit the server editor?\n Any unsaved changes will be lost!"), QMessageBox.Ok | QMessageBox.Cancel)
        if not r == QMessageBox.Ok:
            # Don't quit
            return

        # If "save" has been clicked, return the saved list by calling accept()
        if self.__serverListCopy: #This is non-None if Save has been clicked.
            self.__returnList = self.__serverListCopy # Return the saved on instead
            QDialog.accept(self) # Closes the window while indicating the caller needs to get the new list (self.__returnList)
            return
        QDialog.reject(self)

    def accept(self):
        """Called when OK-button is clicked
        """
        if not self.saveServerlist():
            # DO NOT QUIT
            return
        self.__returnList = self.__serverList
        QDialog.accept(self)

    def isBaseDNsOkMessage(self):
        r = QMessageBox.question(self,
            self.tr("BaseDNs not defined"),
            self.tr("One or more server(s) are setup to use custom base DNs without specifying any.\nDo you still want to save?"),
            QMessageBox.Yes | QMessageBox.No)
        if r == QMessageBox.No:
            return False
        else:
            return True

    def baseDNsOK(self):
        for server in self.__serverList.getTable(): 
            if server.autoBase == False and len(server.baseDN) < 1:
                return False
        return True

    def getResult(self):
        return self.__returnList

    def certFileDialog(self):
        """Slot for selecting a certificate file.
        """
        certFile = QFileDialog.getOpenFileName(self, self.trUtf8('Select certificate file'), '')

        if not certFile is None:
            self.certFileEdit.setText(certFile)
            self.mapper.submit()

    def certKeyfileDialog(self):
        """Slot for selecting a certificate keyfile.
        """
        certKeyfile = QFileDialog.getOpenFileName(self, self.trUtf8('Select certificate keyfile'), '')

        if not certKeyfile is None:
            self.certKeyfileEdit.setText(certKeyfile)
            self.mapper.submit()

    def testConnection(self):
        """
        Tries to bind to the currently selected server.
        """
        currentServerId = self.serverListView.currentIndex().row()
        sO = self.__serverList.getServerObjectByIndex(currentServerId)

        # Busy-dialog
        self.testProgress.reset()
        self.testProgress.show()
        
        # Try to bind
        conn = LumaConnectionWrapper(sO, self)
        conn.bindFinished.connect(self.testFinished)
        conn.bindAsync(sO.name) #Send the serverName as identifier

    @pyqtSlot(bool, Exception, str)
    def testFinished(self, success, exception, serverName):
        # Unparent the LumaConnectionParent from this object
        # so that it is GCed (that is the only reference to it.)
        self.sender().setParent(None)

        self.testProgress.hide()

        if self.testProgress.wasCanceled():
            return

        if success:
            # Success-message
            QMessageBox.information(self, serverName, unicode(self.tr("Bind to {0} successful!")).format(serverName))
        else:
            # Error-message
            if exception[0]["desc"] == "Invalid credentials":
                QMessageBox.warning(self, serverName,
                        unicode(self.tr("Bind to {0} failed:\n{1}\n\n(You do not have to spesify passwords here -- you will be asked when needed.)")).format(serverName,exception[0]["desc"]))
                return
            QMessageBox.warning(self, serverName, unicode(self.tr("Bind to {0} failed:\n{1}")).format(serverName, exception[0]["desc"]))