예제 #1
0
class LdapWidget(DirectoryWidget):
    def __init__(self, config, specific_config, mainwindow, parent=None):
        assert config is not None
        assert specific_config is not None
        DirectoryWidget.__init__(self, config, specific_config, mainwindow, parent)
        self.buildInterface(config)
        self.updateView(self.specific_config)

    def buildInterface(self, config):
        #<server uri>
        self.uri = QLineEdit()
        self.texts.add(self.uri)
        self.connect(self.uri, SIGNAL('textChanged(QString)'), self.setUri)
        self.connect(self.uri, SIGNAL('textChanged(QString)'), self.signalModified)
        self.connect(self.uri, SIGNAL('textEdited(QString)'), self.helpURI)
        self.connect(self.uri, SIGNAL('editingFinished()'), self.noHelpURI)
        self.connect(self.uri, SIGNAL('editingFinished()'), self.updateUri)
        self.form.addRow(tr('LDAP Server(s) uri'), self.uri)

        self.uri.setToolTip(help_uri_tooltip)
        self.connect(self.uri, SIGNAL('returnPressed()'), self.signalModified)

        self.uri_message_area = MessageArea()
        self.empty_uri_label = QLabel()
        self.form.addRow(self.empty_uri_label, self.uri_message_area)
        self.empty_uri_label.hide()
        self.uri_message_area.hide()
        self.uri_message_area.setMessage(help_uri_title, help_uri_message_area)

        self.numeric_uri_warning = MessageArea()
        empty  = QLabel()
        self.form.addRow(empty, self.numeric_uri_warning)
        empty.hide()
        self.numeric_uri_warning.hide()
        self.numeric_uri_warning.warning(numeric_warning_title, numeric_warning)
        self.numeric_uri_warning.setWidth(60)

        #</server uri>

        #<other fields>
        for args in self._genTextFieldsData():
            text_input = self.addTextInput(*args[1:])
            setattr(self, args[0], text_input)
            self.connect(text_input, SIGNAL('editingFinished(QString)'), self.valid)
        #</other fields>

        self.ssl_box = self.mkSslBox()
        self.connect(self.ssl_box, SIGNAL('toggled(bool)'), self.toggleSsl)
        self.form.addRow(self.ssl_box)

        self.form.addRow(separator())

        test_widget = QPushButton(tr("Test this configuration"))
        self.form.addRow("", test_widget)
        test_widget.connect(test_widget, SIGNAL('clicked()'), self.test_ldap)

    def _genTextFieldsData(self):
        return (
            ('dn_users', tr('LDAP DN for users'), self.setDnUsers),
            ('dn_groups', tr('LDAP DN for groups'), self.setDnGroups),
            ('user', tr('DN used to log in on the LDAP server'), self.setUser),
            ('password', tr('Password used to log in on the LDAP server'), self.setPassword, False)
        )

    def test_ldap(self):
        dc, base, uri, filter, password = self.specific_config.generateTest()
        if uri is None or uri == '':
            QMessageBox.critical(
                self,
                "Missing data",
                "Please fill URI field"
                )
            return
        if dc is None:
            dc == ''

        filter, ok = QInputDialog.getText(
            self,
            tr("LDAP Filter"),
            tr("Please enter a filter:"),
            QLineEdit.Normal,
            filter
            )

        if not ok:
            return

        async = self.mainwindow.client.async()
        async.call(
            'nuauth',
            'testLDAP',
            dc, base, uri, unicode(filter), password,
            callback = self.success_test,
            errback = self.error_test
            )

    def success_test(self, result):

        ans_no = tr("OK")
        ans_yes = tr("Show the server's answer")
        show_details = QMessageBox.question(
            self,
            tr('LDAP test results'),
            tr('LDAP test completed without error'),
            ans_no,
            ans_yes
            )

        if show_details == 0:
            return

        title = tr('LDAP test results')
        QMessageBox.information(
            self,
            title,
            '<span><h2>%s</h2><pre>%s</pre></span>' % (title, unicode(result))
            )

    def error_test(self, result):
        basic_text = tr('LDAP test completed with an error')
        formatted_text = u"""\
<span>
%s
<pre>
%s
</pre>
</span>
""" % (basic_text, unicode(result))
        QMessageBox.warning(
            self,
            tr('LDAP test results'),
            formatted_text
            )

    def helpURI(self, text):
        self.uri_message_area.show()

    def noHelpURI(self):
        self.uri_message_area.hide()

    def toggleSsl(self, value):
        if value:
            self.selectCustomOrNupki(NUPKI)
        else:
            self.specific_config.custom_or_nupki = SSL_DISABLED
        self.signalModified()

    def setUri(self, uris_text):
        self.specific_config.setUri(self.readString(uris_text))
        self.signalModified()

        self.numeric_uri_warning.setVisible(ip_in_ldapuri(self.specific_config.uri))

    def setUser(self, user):
        self.specific_config.user = self.readString(user)

    def setPassword(self, password):
        self.specific_config.password = self.readString(password)

    def setDnUsers(self, dn):
        self.specific_config.dn_users = self.readString(dn)

    def setDnGroups(self, dn):
        self.specific_config.dn_groups = self.readString(dn)

    def mkSslBox(self):
        group = QGroupBox(tr("Check server certificate"))
        group.setCheckable(True)
        box = QVBoxLayout(group)

        #id 0
        nupki = QRadioButton(tr("Upload certificate"))
        #id 1
        custom = QRadioButton(tr("Use an internal PKI"))

        hbox = QHBoxLayout()
        box.addLayout(hbox)
        self.nupki_or_custom = QButtonGroup()
        self.connect(self.nupki_or_custom, SIGNAL('buttonClicked(int)'), self.toggleCustomOrNupki)
        for index, radio in enumerate((custom, nupki)):
            hbox.addWidget(radio)
            self.nupki_or_custom.addButton(radio, index)

        self.file_selector_widget = QWidget()
        vbox = QVBoxLayout(self.file_selector_widget)
        selector_label = QLabel(tr("Manually uploaded LDAP certificate"))
        vbox.addWidget(selector_label)
        add_cert_trigger = AddButton(text=tr("Upload a certificate"))
        vbox.addWidget(add_cert_trigger)
        vbox.addWidget(separator())

        self.has_cert_message = QLabel(
            tr("There is no manually uploaded server certificate")
            )
        self.del_cert = RemButton(
            tr("Delete certificate file from server")
            )
        vbox.addWidget(self.has_cert_message)
        vbox.addWidget(self.del_cert)

        self.connect(add_cert_trigger, SIGNAL('clicked()'), self.upload_server_cert)

        self.connect(self.del_cert, SIGNAL('clicked()'), self.delete_server_cert)

        self.nupki_message = MessageArea()
        self.nupki_message.setMessage(tr("Warning"),
            tr(
                "There is no server certificate in the internal PKI.<br/>"
                "Please import or generate one using an internal PKI."
            )
            )

        for anti_button, widget in ((custom, self.nupki_message), (nupki, self.file_selector_widget)):
            box.addWidget(widget)
            self.connect(anti_button, SIGNAL('toggled(bool)'), widget.setVisible)

        self.selectCustomOrNupki(CUSTOM)

        return group

    def upload_server_cert(self):
        dialog = UploadDialog(
            selector_label=tr("LDAP certificate"),
            filter=tr("Certificate file (*.crt *.pem *)")
            )
        accepted = dialog.exec_()

        if accepted != QDialog.Accepted:
            return

        filename = dialog.filename

        if not filename:
            return
        with open(filename, 'rb') as fd:
            content = fd.read()

        content = encodeFileContent(content)

        self.mainwindow.addToInfoArea(tr('Uploading of a certificate file for the ldap server'))
        async = self.mainwindow.client.async()
        async.call("nuauth", "upload_ldap_server_cert", content,
            callback = self.success_upload,
            errback = self.error_upload
            )

    def success_upload(self, value):
        self.mainwindow.addToInfoArea(tr('[LDAP server cert upload] Success!'))
        self.specific_config.server_cert_set = True
        self.setServerCert()

        self.signalModified()

    def error_upload(self, value):
        self.mainwindow.addToInfoArea(tr('[LDAP server cert upload] Error!'), COLOR_ERROR)
        self.mainwindow.addToInfoArea(tr('[LDAP server cert upload] %s') % value, COLOR_ERROR)

    def setServerCert(self):
        if self.specific_config.server_cert_set:
            self.del_cert.show()
            self.has_cert_message.hide()
        else:
            self.del_cert.hide()
            self.has_cert_message.show()

    def delete_server_cert(self):
        confirm_box = QMessageBox(self)
        confirm_box.setText(
            tr(
                "Please confirm the deletion of the "
                "manually uploaded LDAP server certificate."
            )
            )
        confirm_box.setInformativeText(
            tr("Do you really want to delete the certificate file?")
            )
        confirm_box.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
        confirm_box.setDefaultButton(QMessageBox.Cancel)
        confirm = confirm_box.exec_()

        if confirm != QMessageBox.Ok:
            return

        async = self.mainwindow.client.async()
        async.call("nuauth", "delete_ldap_server_cert",
            callback = self.success_delete,
            errback = self.error_delete
            )

    def success_delete(self, value):
        self.mainwindow.addToInfoArea(tr('[LDAP server cert deletion] Success!'))
        self.specific_config.server_cert_set = False
        self.setServerCert()

        self.signalModified()

    def error_delete(self, value):
        self.mainwindow.addToInfoArea(tr('[LDAP server cert deletion] Error!'), COLOR_ERROR)
        self.mainwindow.addToInfoArea(tr('[LDAP server cert deletion] %s') % value, COLOR_ERROR)

    def toggleCustomOrNupki(self, id):
        if id == 0:
            self.specific_config.custom_or_nupki = NUPKI
        else:
            self.specific_config.custom_or_nupki = CUSTOM
        self.signalModified()

    def selectCustomOrNupki(self, custom_or_nupki):
        if custom_or_nupki == CUSTOM:
            self.file_selector_widget.setEnabled(True)
            self.nupki_message.hide()
            id = 1
        else:
            custom_or_nupki = NUPKI
            self.file_selector_widget.hide()
            id = 0
        self.nupki_or_custom.button(id).setChecked(True)

    def setSslData(self, config):
        ssl_enabled = (SSL_DISABLED != config.custom_or_nupki)
        self.ssl_box.setChecked(ssl_enabled)
        if ssl_enabled:
            self.selectCustomOrNupki(config.custom_or_nupki)

    def updateUri(self, config=None):
        if config is None:
            config = self.specific_config
        self.setText(self.uri, config.uri)

    def updateView(self, config=None):
        if config is None:
            config = self.specific_config
        self.updateUri(config=config)
        self.setSslData(config)
        #never copy this one:
        self.setDefaultText(self.dn_users, self.specific_config.dn_users)
        self.setDefaultText(self.dn_groups, self.specific_config.dn_groups)
        self.setDefaultText(self.user, config.user)
        self.setDefaultText(self.password, config.password)
        self.setServerCert()
예제 #2
0
class ResolvFrontend(ScrollArea):
    COMPONENT = 'resolv'
    IDENTIFIER = 'dns'
    LABEL = tr('Hostname and DNS')
    REQUIREMENTS = ('resolv', 'hostname')
    FIXED_WIDTH = 40
    ICON = ':/icons/computeur.png'

    def __init__(self, client, parent=None):
        ScrollArea.__init__(self)
        self.client = client
        self.mainwindow = parent
        self._modified = False
        self.cfg = {}
        self.host_loaded = False
        self.resolv_loaded = False
        self._can_edit_hostname = True
        self.qhostnamecfg = QHostnameObject.getInstance()
        self.qresolvcfg = QResolvObject.getInstance()

        main_widget = QWidget()
        self.setWidget(main_widget)
        self.setWidgetResizable(True)

        self.form = QFormLayout(main_widget)

        self.form.addRow(QLabel("<h1>%s</h1>" % tr('Hostname')))

        host_box = self.mkHostBox()
        self.form.addRow(host_box)

        self.form.addItem(
            QSpacerItem(1, 1, QSizePolicy.Minimum, QSizePolicy.Expanding)
            )

        self.form.addRow(QLabel("<h1>%s</h1>" % tr('DNS')))
        self.dns1 = self.addField(tr("Server 1"), IpEdit)
        self.dns2_label = QLabel(tr("Server 2"))
        self.dns2 = self.addField(self.dns2_label, IpEdit)

        self.form.addRow(QLabel("<h1>%s</h1>" % tr('Active configuration test')))

        self.test_group = _mkTestGroup()
        self.form.addRow(self.test_group)
        self.connect(self.test_group.test_button, SIGNAL('clicked()'), self.full_test)

        self.mainwindow.writeAccessNeeded(
            self.dns1,
            self.dns2,
            self.edit_hostname,
            self.edit_domain
            )

        self.message = MessageArea()
        self.message.setWidth(80)
        self.error_message = ''

        self.form.addRow(self.message)

        self.resetConf()
        if not self.host_loaded and not self.resolv_loaded:
            raise NuConfModuleDisabled

        if self.resolv_loaded:
            self._connectsignals()

    def _connectsignals(self):
        self.connect(self.dns1, SIGNAL('editingFinished()'),
            partial(self.setDns, self.dns1, self.qresolvcfg.resolvcfg.setNameserver1))
        self.connect(self.dns2, SIGNAL('editingFinished()'),
            partial(self.setDns, self.dns2, self.qresolvcfg.resolvcfg.setNameserver2))
        self.connect(self.dns1, SIGNAL('textEdited(QString)'), lambda x: self.setModified(True))
        self.connect(self.dns2, SIGNAL('textEdited(QString)'), lambda x: self.setModified(True))

    @staticmethod
    def get_calls():
        """
        services called by initial multicall
        """
        return (
            ('resolv', 'getResolvConfig'),
            ('resolv', 'getRunningConfig'),
            ('hostname', 'getHostnameConfig'),
        )

    def setDns(self, edit_dns, set_dns):
        set_dns(edit_dns.text())

    def mkHostBox(self):
        box = QGroupBox(tr("Hostname and domain configuration"))
        form = QFormLayout(box)
        form.setLabelAlignment(Qt.AlignLeft)

        self.hostname = QLabel()
        self.edit_hostname = EditButton(flat=False)
        self.connect(self.edit_hostname, SIGNAL('clicked()'), self._edit_hostname)
        form.addRow(self.hostname, self.edit_hostname)

        self.domain = QLabel()
        self.edit_domain = EditButton(flat=False)
        self.connect(self.edit_domain, SIGNAL('clicked()'), self._edit_domain)
        form.addRow(self.domain, self.edit_domain)

        self.qhostnamecfg.registerCallbacks(
            lambda: True,
            self.updateHostname)

        self.qresolvcfg.registerCallbacks(
            lambda: True,
            self.updateDomain)

        return box

    def updateHostname(self):
        if not self.host_loaded:
            hostname_text = "<i>%s</i>" % tr("not fetched",
                "The hostname is normally fetched from the server, and this "
                "text handles the case when it failed. "
                "The text 'not fetched' is written in italics."
                )
            self._can_edit_hostname = False
        else:
            changeable = self.qhostnamecfg.hostnamecfg.changeable
            self._can_edit_hostname = changeable != NOT_CHANGEABLE
            hostname_text = "%s %s" % (
                    tr("Hostname:", "Displaying the hostname"),
                    self.qhostnamecfg.hostnamecfg.hostname
                )
            if changeable == NOT_CHANGEABLE:
                self.mainwindow.addToInfoArea(_HOSTNAME_NOT_CHANGEABLE, COLOR_VERBOSE)

        self.hostname.setText(hostname_text)

    def updateDomain(self):
        self.domain.setText(tr("Domain: %s") % self.qresolvcfg.resolvcfg.domain)

    def __editFieldDialog(self, title, label, text):
        """
        return text, ok
        """
        #Signature: text, ok = QInputDialog.getText(
        #    QWidget parent,
        #    QString title,
        #    QString label,
        #    QLineEdit.EchoMode echo = QLineEdit.Normal,
        #    QString text = QString(),
        #    Qt.WindowFlags f = 0)
        return QInputDialog.getText(
            self,
            title,
            label,
            QLineEdit.Normal,
            text
            )

    def _continue_edit(self, title, message):
        continuing = QMessageBox.question(
            self,
            title,
            message,
            QMessageBox.Cancel|QMessageBox.Ok,
            QMessageBox.Cancel, #default button
            )
        return continuing == QMessageBox.Ok

    def _edit_hostname(self):
        changeable = self.qhostnamecfg.hostnamecfg.changeable

        if not self._can_edit_hostname:
            QMessageBox.critical(
                self,
                _CANNOT_CHANGE_HOSTNAME_TITLE,
                _CANNOT_CHANGE_HOSTNAME,
                )
            return

        if changeable == CHANGE_DISCOURAGED:
            if not self._continue_edit(
                tr("About hostname change", "popup title"),
                _HOSTNAME_CHANGE_DISCLAIMER
                ):
                    return

        hostname, ok = self.__editFieldDialog(
            tr("Edit hostname"),
            tr("Enter new hostname"),
            self.qhostnamecfg.hostnamecfg.hostname
            )

        if ok:
            hostname = unicode(hostname).strip()
            if hostname == self.qhostnamecfg.hostnamecfg.hostname:
                return
            self.qhostnamecfg.pre_modify()
            self.qhostnamecfg.hostnamecfg.hostname = hostname
            self.qhostnamecfg.post_modify()
            self.setModified()

    def _edit_domain(self):
        changeable = self.qhostnamecfg.hostnamecfg.changeable

        if changeable == CHANGE_DISCOURAGED:
            if not self._continue_edit(
                tr("About domain change", "popup title"),
                _DOMAIN_CHANGE_DISCLAIMER
                ):
                    return
        domain, ok = self.__editFieldDialog(
            tr("Edit domain"),
            tr("Enter new domain name"),
            self.qresolvcfg.resolvcfg.domain
            )

        if ok:
            domain = unicode(domain).strip()
            if domain == self.qresolvcfg.resolvcfg.domain:
                return
            self.qresolvcfg.pre_modify()
            self.qresolvcfg.resolvcfg.setDomain(domain)
            self.qresolvcfg.post_modify()
            self.setModified()

    def default_test(self):
        async = self.client.async()
        async.call('resolv', 'test',
            {},
            callback=self.defaultTest_success,
            errback=self.test_error
            )
        self.mainwindow.addToInfoArea(tr("Testing current DNS server..."))

    def full_test(self):
        fields = {
            self.test_group.server_input: tr("test server field"),
            self.test_group.query_input: tr("test query field"),
        }

        default_test = True

        for item in fields:
            if not item.isEmpty():

                default_test = False

                if not item.isValid():
                    self.mainwindow.addToInfoArea(
                        tr("Wrong value in %s, can not test.") % fields[item]
                    )
                    return

        if default_test:
            self.default_test()
            return

        self.deactivate_tests()

        async = self.client.async()
        async.call('resolv', 'test',
            {
                'server': unicode(self.test_group.server_input.text()).strip(),
                'query': unicode(self.test_group.query_input.text()).strip()
            },
            callback=self.test_success,
            errback=self.test_error
            )
        self.mainwindow.addToInfoArea(tr("Testing DNS server with custom parameters..."))

    def defaultTest_success(self, result):
        self.mainwindow.addToInfoArea(tr("[DNS Test] Success: The server is available (empty query)."))
        self.reactivate_tests()

    def test_success(self, result):
        self.mainwindow.addToInfoArea(tr("[DNS Test] Success!"))
        dialog = DnsResult(result, self)
        dialog.exec_()
        self.reactivate_tests()

    def test_error(self, err):
        err = exceptionAsUnicode(err)
        self.mainwindow.addWarningMessage(
            tr("[DNS Test] Error! %s") % err
            )
        self.reactivate_tests()

    def deactivate_tests(self):
        self.test_group.test_button.setEnabled(False)

    def reactivate_tests(self):
        self.test_group.test_button.setEnabled(True)

    def addField(self, field_name, inputcls):
        field = inputcls(self)
        field.setFixedWidth(field.fontMetrics().averageCharWidth() * self.FIXED_WIDTH)
        self.form.addRow(field_name, field)
        return field

    def isModified(self):
        return self._modified

    def setModified(self, modif=True):
        if modif:
            self.mainwindow.setModified(self, True)
        self._modified = modif

    def resetConf(self):
        self._reset_hostname()
        self._reset_resolv()

        self.updateHostname()
        self.updateDomain()
        self.dns1.setText(self.qresolvcfg.resolvcfg.nameserver1)
        self.dns1.validColor()
        self.dns2.setText(self.qresolvcfg.resolvcfg.nameserver2)
        self.dns2.validColor()

        self.message.setNoMessage()
        self.setModified(False)

        self._reset_tests_texts()

    def isValid(self):
        self.message.setNoMessage()
        self.error_message = ''
        hasInvalidData = False
        for widget in [self.dns1, self.dns2]:
            if not widget.isValid():
                hasInvalidData = True
                error = "'%s' : must be '%s'<br />" % (widget.text(), widget.getFieldInfo())
                self.error_message += error
                self.message.setMessage('', "<ul>%s" % error, status=MessageArea.WARNING)

        confs = {
                tr("hostname"): self.qhostnamecfg.cfg,
                tr("DNS"): self.qresolvcfg.cfg
                }

        for name, item in confs.iteritems():
            if not item.isValid():
                hasInvalidData = True
                error = tr("%s configuration is invalid<br />") % name
                self.message.setMessage('', error, status=MessageArea.WARNING)
                self.error_message += error

        if not hasInvalidData:
            error = self.qresolvcfg.resolvcfg.isInvalid()
            if error:
                hasInvalidData = True
                self.error_message = error
                self.message.setMessage('', error, status=MessageArea.WARNING)

        if hasInvalidData:
            self.mainwindow.addToInfoArea(tr("'Hostname and DNS : invalid configuration'"))
            return False
        else:
            return True

    def saveConf(self, message):
        if self.host_loaded:
            self.client.call('hostname', 'setShortHostname', self.qhostnamecfg.hostnamecfg.hostname, message)
        if self.resolv_loaded:
            self.client.call('resolv', 'setResolvConfig', self.qresolvcfg.resolvcfg.serialize(), message)

        self.setModified(False)
        self.mainwindow.addToInfoArea(tr("'Hostname and DNS : configuration saved'"))

    def _reset_hostname(self):
        """
        Specifically resets values for hostname config.
        sets self.host_loaded according to success in doing so.
        """
        self.host_loaded = self._reset_helper(
            'hostname',
            'getHostnameConfig',
            self.qhostnamecfg,
            tr("Hostname interface enabled"),
            tr("Hostname interface disabled: backend not loaded")
        )

    def _reset_resolv(self):
        """
        Specifically resets values for DNS resolution config.
        sets self.resolv_loaded according to success in doing so.
        """
        self.resolv_loaded = self._reset_helper(
            'resolv',
            'getResolvConfig',
            self.qresolvcfg,
            tr("DNS interface enabled"),
            tr("DNS interface disabled: backend not loaded")
        )

    def _reset_tests_texts(self):
        serialized = self._fetch_serialized('resolv', 'getRunningConfig')

        if serialized is not None:
            running_cfg = deserialize(serialized)
            servers = {'DNS_SERVERS': ' and '.join(
                tuple(running_cfg.iterNameServers())
                )}
            text = _FULL_TEST_TEXT % servers
        else:
            self.message.warning(
                tr("Warning on tests", "Testing DNS resolution from the server"),
                tr(
                    "Tests are run with the configuration that is applied on "
                    "the appliance. This configuration can not be displayed "
                    "(probably because the software version of the appliance does "
                    "not provide this functionality)."
                  )
                )
            text = _FULL_TEST_TEXT_NOINFO

        self.test_group.full_test_info.setText("%s %s" % (_INFO_IMG, text))