diff --git a/autotests/keyserverconfigtest.cpp b/autotests/keyserverconfigtest.cpp index 5ab1dd786..ad8a4b4b6 100644 --- a/autotests/keyserverconfigtest.cpp +++ b/autotests/keyserverconfigtest.cpp @@ -1,263 +1,261 @@ /* autotests/keyserverconfigtest.cpp This file is part of libkleopatra's test suite. SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include #include #include using namespace Kleo; namespace QTest { template <> inline char *toString(const KeyserverAuthentication &t) { switch (t) { case KeyserverAuthentication::Anonymous: return qstrdup("Anonymous"); case KeyserverAuthentication::ActiveDirectory: return qstrdup("ActiveDirectory"); case KeyserverAuthentication::Password: return qstrdup("Password"); default: return qstrdup((std::string("invalid value (") + std::to_string(static_cast(t)) + ")").c_str()); } } template <> inline char *toString(const KeyserverConnection &t) { switch (t) { + case KeyserverConnection::Default: return qstrdup("Default"); case KeyserverConnection::Plain: return qstrdup("Plain"); case KeyserverConnection::UseSTARTTLS: return qstrdup("UseSTARTTLS"); case KeyserverConnection::TunnelThroughTLS: return qstrdup("TunnelThroughTLS"); default: return qstrdup((std::string("invalid value (") + std::to_string(static_cast(t)) + ")").c_str()); } } } class KeyserverConfigTest: public QObject { Q_OBJECT private Q_SLOTS: void test_ldap_keyserver_on_active_directory() { const QUrl url{QStringLiteral("ldap://#ntds")}; auto config = KeyserverConfig::fromUrl(url); QVERIFY(config.host().isEmpty()); QCOMPARE(config.port(), -1); QVERIFY(config.user().isEmpty()); QVERIFY(config.password().isEmpty()); QCOMPARE(config.authentication(), KeyserverAuthentication::ActiveDirectory); - QCOMPARE(config.connection(), KeyserverConnection::Plain); + QCOMPARE(config.connection(), KeyserverConnection::Default); QVERIFY(config.ldapBaseDn().isEmpty()); const auto createdUrl = config.toUrl(); QCOMPARE(createdUrl, url); QVERIFY(!createdUrl.hasQuery()); QVERIFY(createdUrl.hasFragment()); } void test_ldap_keyserver_with_authentication_via_active_directory() { const QUrl url{QStringLiteral("ldap://ldap.example.net#ntds")}; auto config = KeyserverConfig::fromUrl(url); QCOMPARE(config.host(), QLatin1String("ldap.example.net")); QCOMPARE(config.port(), -1); QVERIFY(config.user().isEmpty()); QVERIFY(config.password().isEmpty()); QCOMPARE(config.authentication(), KeyserverAuthentication::ActiveDirectory); - QCOMPARE(config.connection(), KeyserverConnection::Plain); + QCOMPARE(config.connection(), KeyserverConnection::Default); QVERIFY(config.ldapBaseDn().isEmpty()); const auto createdUrl = config.toUrl(); QCOMPARE(createdUrl, url); QVERIFY(!createdUrl.hasQuery()); QVERIFY(createdUrl.hasFragment()); } void test_anonymous_ldap_keyserver() { const QUrl url{QStringLiteral("ldap://ldap.example.net")}; auto config = KeyserverConfig::fromUrl(url); QCOMPARE(config.host(), QLatin1String("ldap.example.net")); QCOMPARE(config.port(), -1); QVERIFY(config.user().isEmpty()); QVERIFY(config.password().isEmpty()); QCOMPARE(config.authentication(), KeyserverAuthentication::Anonymous); - QCOMPARE(config.connection(), KeyserverConnection::Plain); + QCOMPARE(config.connection(), KeyserverConnection::Default); QVERIFY(config.ldapBaseDn().isEmpty()); const auto createdUrl = config.toUrl(); QCOMPARE(createdUrl, url); QVERIFY(!createdUrl.hasQuery()); QVERIFY(!createdUrl.hasFragment()); } void test_ldap_keyserver_with_password_authentication() { const QUrl url{QStringLiteral("ldap://user:password@ldap.example.net")}; auto config = KeyserverConfig::fromUrl(url); QCOMPARE(config.host(), QLatin1String("ldap.example.net")); QCOMPARE(config.port(), -1); QCOMPARE(config.user(), QLatin1String("user")); QCOMPARE(config.password(), QLatin1String("password")); QCOMPARE(config.authentication(), KeyserverAuthentication::Password); - QCOMPARE(config.connection(), KeyserverConnection::Plain); + QCOMPARE(config.connection(), KeyserverConnection::Default); QVERIFY(config.ldapBaseDn().isEmpty()); const auto createdUrl = config.toUrl(); QCOMPARE(createdUrl, url); QVERIFY(!createdUrl.hasQuery()); QVERIFY(!createdUrl.hasFragment()); } void test_ldap_keyserver_with_starttls() { const QUrl url{QStringLiteral("ldap://user:password@ldap.example.net#starttls")}; auto config = KeyserverConfig::fromUrl(url); QCOMPARE(config.host(), QLatin1String("ldap.example.net")); QCOMPARE(config.port(), -1); QCOMPARE(config.user(), QLatin1String("user")); QCOMPARE(config.password(), QLatin1String("password")); QCOMPARE(config.authentication(), KeyserverAuthentication::Password); QCOMPARE(config.connection(), KeyserverConnection::UseSTARTTLS); QVERIFY(config.ldapBaseDn().isEmpty()); const auto createdUrl = config.toUrl(); QCOMPARE(createdUrl, url); QVERIFY(!createdUrl.hasQuery()); QVERIFY(createdUrl.hasFragment()); } void test_ldap_keyserver_with_tls_secured_tunnel() { const QUrl url{QStringLiteral("ldap://user:password@ldap.example.net#ldaptls")}; auto config = KeyserverConfig::fromUrl(url); QCOMPARE(config.host(), QLatin1String("ldap.example.net")); QCOMPARE(config.port(), -1); QCOMPARE(config.user(), QLatin1String("user")); QCOMPARE(config.password(), QLatin1String("password")); QCOMPARE(config.authentication(), KeyserverAuthentication::Password); QCOMPARE(config.connection(), KeyserverConnection::TunnelThroughTLS); QVERIFY(config.ldapBaseDn().isEmpty()); const auto createdUrl = config.toUrl(); QCOMPARE(createdUrl, url); QVERIFY(!createdUrl.hasQuery()); QVERIFY(createdUrl.hasFragment()); } void test_ldap_keyserver_with_explicit_plain_connection() { const QUrl url{QStringLiteral("ldap://user:password@ldap.example.net#plain")}; auto config = KeyserverConfig::fromUrl(url); QCOMPARE(config.host(), QLatin1String("ldap.example.net")); QCOMPARE(config.port(), -1); QCOMPARE(config.user(), QLatin1String("user")); QCOMPARE(config.password(), QLatin1String("password")); QCOMPARE(config.authentication(), KeyserverAuthentication::Password); QCOMPARE(config.connection(), KeyserverConnection::Plain); QVERIFY(config.ldapBaseDn().isEmpty()); const auto createdUrl = config.toUrl(); - // connection flag "plain" is omitted - const auto expectedUrl = QUrl{QStringLiteral("ldap://user:password@ldap.example.net")}; - QCOMPARE(createdUrl, expectedUrl); + QCOMPARE(createdUrl, url); QVERIFY(!createdUrl.hasQuery()); - QVERIFY(!createdUrl.hasFragment()); + QVERIFY(createdUrl.hasFragment()); } void test_ldap_keyserver_with_multiple_connection_flags() { // the last flag wins (as in dirmngr/ldapserver.c) const QUrl url{QStringLiteral("ldap://user:password@ldap.example.net#starttls,plain")}; auto config = KeyserverConfig::fromUrl(url); QCOMPARE(config.host(), QLatin1String("ldap.example.net")); QCOMPARE(config.port(), -1); QCOMPARE(config.user(), QLatin1String("user")); QCOMPARE(config.password(), QLatin1String("password")); QCOMPARE(config.authentication(), KeyserverAuthentication::Password); QCOMPARE(config.connection(), KeyserverConnection::Plain); QVERIFY(config.ldapBaseDn().isEmpty()); const auto createdUrl = config.toUrl(); - // at most one connection flag is added, but "plain" is omitted - const auto expectedUrl = QUrl{QStringLiteral("ldap://user:password@ldap.example.net")}; + // only one connection flag is added + const auto expectedUrl = QUrl{QStringLiteral("ldap://user:password@ldap.example.net#plain")}; QCOMPARE(createdUrl, expectedUrl); QVERIFY(!createdUrl.hasQuery()); - QVERIFY(!createdUrl.hasFragment()); + QVERIFY(createdUrl.hasFragment()); } void test_ldap_keyserver_with_not_normalized_flags() { const QUrl url{QStringLiteral("ldap://ldap.example.net#startTLS, NTDS")}; auto config = KeyserverConfig::fromUrl(url); QCOMPARE(config.authentication(), KeyserverAuthentication::ActiveDirectory); QCOMPARE(config.connection(), KeyserverConnection::UseSTARTTLS); const auto createdUrl = config.toUrl(); const auto expectedUrl = QUrl{QStringLiteral("ldap://ldap.example.net#starttls,ntds")}; QCOMPARE(createdUrl, expectedUrl); QVERIFY(!createdUrl.hasQuery()); QVERIFY(createdUrl.hasFragment()); } void test_ldap_keyserver_with_explicit_port() { const QUrl url{QStringLiteral("ldap://user:password@ldap.example.net:4242")}; auto config = KeyserverConfig::fromUrl(url); QCOMPARE(config.host(), QLatin1String("ldap.example.net")); QCOMPARE(config.port(), 4242); QCOMPARE(config.user(), QLatin1String("user")); QCOMPARE(config.password(), QLatin1String("password")); QCOMPARE(config.authentication(), KeyserverAuthentication::Password); - QCOMPARE(config.connection(), KeyserverConnection::Plain); + QCOMPARE(config.connection(), KeyserverConnection::Default); QVERIFY(config.ldapBaseDn().isEmpty()); const auto createdUrl = config.toUrl(); QCOMPARE(createdUrl, url); QVERIFY(!createdUrl.hasQuery()); QVERIFY(!createdUrl.hasFragment()); } void test_ldap_keyserver_with_base_dn() { - // the last flag wins (as in dirmngr/ldapserver.c) const QUrl url{QStringLiteral("ldap://user:password@ldap.example.net?base_dn")}; auto config = KeyserverConfig::fromUrl(url); QCOMPARE(config.host(), QLatin1String("ldap.example.net")); QCOMPARE(config.port(), -1); QCOMPARE(config.user(), QLatin1String("user")); QCOMPARE(config.password(), QLatin1String("password")); QCOMPARE(config.authentication(), KeyserverAuthentication::Password); - QCOMPARE(config.connection(), KeyserverConnection::Plain); + QCOMPARE(config.connection(), KeyserverConnection::Default); QCOMPARE(config.ldapBaseDn(), QLatin1String("base_dn")); const auto createdUrl = config.toUrl(); QCOMPARE(createdUrl, url); QVERIFY(createdUrl.hasQuery()); QVERIFY(!createdUrl.hasFragment()); } void test_url_with_empty_string_as_user_and_password() { KeyserverConfig config; config.setHost(QStringLiteral("anonymous.example.net")); config.setUser(QStringLiteral("")); config.setPassword(QStringLiteral("")); const auto createdUrl = config.toUrl(); QCOMPARE(createdUrl, QUrl{QStringLiteral("ldap://anonymous.example.net")}); QVERIFY(!createdUrl.hasQuery()); QVERIFY(!createdUrl.hasFragment()); } }; QTEST_MAIN(KeyserverConfigTest) #include "keyserverconfigtest.moc" diff --git a/src/kleo/keyserverconfig.cpp b/src/kleo/keyserverconfig.cpp index fcd74e7a0..43fda7a02 100644 --- a/src/kleo/keyserverconfig.cpp +++ b/src/kleo/keyserverconfig.cpp @@ -1,202 +1,205 @@ /* kleo/keyserverconfig.cpp This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include "keyserverconfig.h" #include "utils/algorithm.h" #include #include using namespace Kleo; class KeyserverConfig::Private { public: explicit Private(); QString host; int port = -1; // -1 == use default port KeyserverAuthentication authentication = KeyserverAuthentication::Anonymous; QString user; QString password; - KeyserverConnection connection = KeyserverConnection::Plain; + KeyserverConnection connection = KeyserverConnection::Default; QString baseDn; }; KeyserverConfig::Private::Private() { } KeyserverConfig::KeyserverConfig() : d{std::make_unique()} { } KeyserverConfig::~KeyserverConfig() = default; KeyserverConfig::KeyserverConfig(const KeyserverConfig &other) : d{std::make_unique(*other.d)} { } KeyserverConfig &KeyserverConfig::operator=(const KeyserverConfig &other) { *d = *other.d; return *this; } KeyserverConfig::KeyserverConfig(KeyserverConfig &&other) = default; KeyserverConfig &KeyserverConfig::operator=(KeyserverConfig &&other) = default; KeyserverConfig KeyserverConfig::fromUrl(const QUrl &url) { KeyserverConfig config; config.d->host = url.host(); config.d->port = url.port(); config.d->user = url.userName(); config.d->password = url.password(); if (!config.d->user.isEmpty()) { config.d->authentication = KeyserverAuthentication::Password; } if (url.hasFragment()) { const auto flags = transformInPlace(url.fragment().split(QLatin1Char{','}, Qt::SkipEmptyParts), [] (const auto &flag) { return flag.trimmed().toLower(); }); for (const auto &flag : flags) { if (flag == QLatin1String{"starttls"}) { config.d->connection = KeyserverConnection::UseSTARTTLS; } else if (flag == QLatin1String{"ldaptls"}) { config.d->connection = KeyserverConnection::TunnelThroughTLS; } else if (flag == QLatin1String{"plain"}) { config.d->connection = KeyserverConnection::Plain; } else if (flag == QLatin1String{"ntds"}) { config.d->authentication = KeyserverAuthentication::ActiveDirectory; } } } if (url.hasQuery()) { config.d->baseDn = url.query(); } return config; } QUrl KeyserverConfig::toUrl() const { QUrl url; url.setScheme(QStringLiteral("ldap")); // set host to empty string if it's a null string; this ensures that the URL has an authority and always gets a "//" after the scheme url.setHost(d->host.isNull() ? QStringLiteral("") : d->host); if (d->port != -1) { url.setPort(d->port); } if (!d->user.isEmpty()) { url.setUserName(d->user); } if (!d->password.isEmpty()) { url.setPassword(d->password); } if (!d->baseDn.isEmpty()) { url.setQuery(d->baseDn); } QStringList flags; switch (d->connection) { case KeyserverConnection::UseSTARTTLS: flags.push_back(QStringLiteral("starttls")); break; case KeyserverConnection::TunnelThroughTLS: flags.push_back(QStringLiteral("ldaptls")); break; case KeyserverConnection::Plain: - ; // omit connection flag "plain"; it's the default + flags.push_back(QStringLiteral("plain")); + break; + case KeyserverConnection::Default: + ; // omit connection flag to use default } if (d->authentication == KeyserverAuthentication::ActiveDirectory) { flags.push_back(QStringLiteral("ntds")); } if (!flags.isEmpty()) { url.setFragment(flags.join(QLatin1Char{','})); } return url; } QString KeyserverConfig::host() const { return d->host; } void KeyserverConfig::setHost(const QString &host) { d->host = host; } int KeyserverConfig::port() const { return d->port; } void KeyserverConfig::setPort(int port) { d->port = port; } KeyserverAuthentication KeyserverConfig::authentication() const { return d->authentication; } void KeyserverConfig::setAuthentication(KeyserverAuthentication authentication) { d->authentication = authentication; } QString KeyserverConfig::user() const { return d->user; } void KeyserverConfig::setUser(const QString &user) { d->user = user; } QString KeyserverConfig::password() const { return d->password; } void KeyserverConfig::setPassword(const QString &password) { d->password = password; } KeyserverConnection KeyserverConfig::connection() const { return d->connection; } void KeyserverConfig::setConnection(KeyserverConnection connection) { d->connection = connection; } QString KeyserverConfig::ldapBaseDn() const { return d->baseDn; } void KeyserverConfig::setLdapBaseDn(const QString &baseDn) { d->baseDn = baseDn; } diff --git a/src/kleo/keyserverconfig.h b/src/kleo/keyserverconfig.h index a7ffa9a0f..ea3a11eea 100644 --- a/src/kleo/keyserverconfig.h +++ b/src/kleo/keyserverconfig.h @@ -1,76 +1,77 @@ /* kleo/keyserverconfig.h This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include "kleo_export.h" #include class QString; class QUrl; namespace Kleo { enum class KeyserverAuthentication { Anonymous, ActiveDirectory, Password }; enum class KeyserverConnection { + Default, Plain, UseSTARTTLS, TunnelThroughTLS }; class KLEO_EXPORT KeyserverConfig { public: KeyserverConfig(); ~KeyserverConfig(); KeyserverConfig(const KeyserverConfig &other); KeyserverConfig &operator=(const KeyserverConfig &other); KeyserverConfig(KeyserverConfig &&other); KeyserverConfig &operator=(KeyserverConfig &&other); static KeyserverConfig fromUrl(const QUrl &url); QUrl toUrl() const; QString host() const; void setHost(const QString &host); int port() const; void setPort(int port); KeyserverAuthentication authentication() const; void setAuthentication(KeyserverAuthentication authentication); QString user() const; void setUser(const QString &user); QString password() const; void setPassword(const QString &password); KeyserverConnection connection() const; void setConnection(KeyserverConnection connection); QString ldapBaseDn() const; void setLdapBaseDn(const QString &baseDn); private: class Private; std::unique_ptr d; }; }