Commit bf62c16e authored by Ingo Klöcker's avatar Ingo Klöcker
Browse files

Add a value-class for a keyserver configuration

It has fromUrl() and toUrl() methods for converting between a
configuration and a QUrl as expected by QGpgMENewCryptoConfigEntry.

GnuPG-bug-id: 5465
parent 525fb65f
......@@ -31,6 +31,7 @@ ecm_add_test(
)
ecm_add_tests(
keyserverconfigtest.cpp
newkeyapprovaldialogtest.cpp
LINK_LIBRARIES KF5::Libkleo Qt::Widgets Qt::Test
)
/*
autotests/keyserverconfigtest.cpp
This file is part of libkleopatra's test suite.
SPDX-FileCopyrightText: 2021 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <Libkleo/KeyserverConfig>
#include <QString>
#include <QTest>
#include <QUrl>
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<int>(t)) + ")").c_str());
}
}
template <>
inline char *toString(const KeyserverConnection &t)
{
switch (t) {
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<int>(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);
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);
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);
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);
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);
QVERIFY(!createdUrl.hasQuery());
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")};
QCOMPARE(createdUrl, expectedUrl);
QVERIFY(!createdUrl.hasQuery());
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);
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.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"
......@@ -31,6 +31,7 @@ target_sources(KF5Libkleo PRIVATE
kleo/keygroup.cpp
kleo/keyresolver.cpp
kleo/keyresolvercore.cpp
kleo/keyserverconfig.cpp
kleo/kleoexception.cpp
kleo/oidmap.cpp
models/keycache.cpp
......@@ -141,6 +142,7 @@ ecm_generate_headers(libkleo_CamelCase_HEADERS
KeyGroup
KeyResolver
KeyResolverCore
KeyserverConfig
KleoException
OidMap
Predicates
......
/*
kleo/keyserverconfig.cpp
This file is part of libkleopatra, the KDE keymanagement library
SPDX-FileCopyrightText: 2021 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "keyserverconfig.h"
#include "utils/algorithm.h"
#include <QString>
#include <QUrl>
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;
QString baseDn;
};
KeyserverConfig::Private::Private()
{
}
KeyserverConfig::KeyserverConfig()
: d{std::make_unique<Private>()}
{
}
KeyserverConfig::~KeyserverConfig() = default;
KeyserverConfig::KeyserverConfig(const KeyserverConfig &other)
: d{std::make_unique<Private>(*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
}
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;
}
/*
kleo/keyserverconfig.h
This file is part of libkleopatra, the KDE keymanagement library
SPDX-FileCopyrightText: 2021 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "kleo_export.h"
#include <memory>
class QString;
class QUrl;
namespace Kleo
{
enum class KeyserverAuthentication {
Anonymous,
ActiveDirectory,
Password
};
enum class KeyserverConnection {
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<Private> d;
};
}
......@@ -29,5 +29,11 @@ ForwardIterator binary_find(ForwardIterator first, ForwardIterator last, const T
return (it == last || comp(value, *it)) ? last : it;
}
template <typename Container, typename UnaryOperation>
Container transformInPlace(Container &&c, UnaryOperation op)
{
std::transform(std::begin(c), std::end(c), std::begin(c), op);
return std::move(c);
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment