]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#1304] Added config/parse
authorFrancis Dupont <fdupont@isc.org>
Sun, 12 Jul 2020 12:58:32 +0000 (14:58 +0200)
committerFrancis Dupont <fdupont@isc.org>
Sat, 12 Sep 2020 08:50:34 +0000 (10:50 +0200)
doc/examples/agent/comments.json
doc/examples/agent/simple.json
src/bin/agent/ca_cfg_mgr.cc
src/bin/agent/ca_cfg_mgr.h
src/bin/agent/simple_parser.cc
src/lib/http/Makefile.am
src/lib/http/basic_auth_config.cc [new file with mode: 0644]
src/lib/http/basic_auth_config.h [new file with mode: 0644]
src/lib/http/response_creator_auth.h
src/lib/http/tests/Makefile.am
src/lib/http/tests/basic_auth_config_unittests.cc [new file with mode: 0644]

index 1f90e1ce559edb5bbb401eabd2d85c914665d816..d0c6a00285964adafc2bb303f2f920fbc055d6c3 100644 (file)
 
         "http-host": "127.0.0.1",
         "http-port": 8000,
+        "basic-authentication-realm": "kea-control-agent",
+
+        // In basoc HTTP authentication
+        "basic-authentications":
+        [
+            {
+                "comment": "admin is authorized",
+                "user": "admin",
+                "password": "1234"
+            }
+        ],
 
         // In control socket
         "control-sockets":
index 7bde939e8f436fb6872f9c88874b13257080c21a..db372485682d231aa40c9bd9499c220e10f23753 100644 (file)
         // Another mandatory parameter is the HTTP port.
         "http-port": 8000,
 
+        // An optional parameter is the basic HTTP authentication realm.
+        // Its default is "kea-control-agent".
+        "basic-authentication-realm": "kea-control-agent",
+
+        // This list specifies the user ids and passwords to use for
+        // basic HTTP authentication. If empty or not present any client
+        // is authorized.
+        "basic-authentications":
+        [
+            // This specifies an authorized client.
+            {
+                "comment": "admin is authorized",
+
+                // The user id must not be empty or contain the ':' character.
+                // It is a mandatory parameter.
+                "user": "admin",
+
+                // If password is not specified an empty password is used.
+                "password": "1234"
+            }
+        ],
+
         // This map specifies where control channel of each server is configured
         // to listen on. See 'control-socket' object in the respective
         // servers. At this time the only supported socket type is "unix".
index 5bf87e5e94e8aa08f42b9ea49610b6242ba51a35..fc013113ad3774ab1f14508b4fb2841c9d3d2e80 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2016-2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2020 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -21,7 +21,7 @@ namespace isc {
 namespace agent {
 
 CtrlAgentCfgContext::CtrlAgentCfgContext()
-    :http_host_(""), http_port_(0) {
+    : http_host_(""), http_port_(0), basic_auth_realm_("") {
 }
 
 CtrlAgentCfgContext::CtrlAgentCfgContext(const CtrlAgentCfgContext& orig)
@@ -50,6 +50,8 @@ CtrlAgentCfgMgr::getConfigSummary(const uint32_t /*selection*/) {
     // Then print the control-sockets
     s << ctx->getControlSocketInfoSummary();
 
+    // @todo: add something if authentication is required
+
     // Finally, print the hook libraries names
     const isc::hooks::HookLibsCollection libs = ctx->getHooksConfig().get();
     s << ", " << libs.size() << " lib(s):";
@@ -152,6 +154,8 @@ CtrlAgentCfgContext::toElement() const {
     ca->set("http-host", Element::create(http_host_));
     // Set http-port
     ca->set("http-port", Element::create(static_cast<int64_t>(http_port_)));
+    // Set basic-authentication-realm
+    ca->set("basic-authentication-realm", Element::create(basic_auth_realm_));
     // Set hooks-libraries
     ca->set("hooks-libraries", hooks_config_.toElement());
     // Set control-sockets
@@ -161,6 +165,7 @@ CtrlAgentCfgContext::toElement() const {
         control_sockets->set(si->first, socket);
     }
     ca->set("control-sockets", control_sockets);
+    // @todo: Set authentication.
     // Set Control-agent
     ElementPtr result = Element::createMap();
     result->set("Control-agent", ca);
index 27c8c02a6433357dad498c24a6abe91e8fbbe754..74de00b277913c7cd5a161e394b0cbbc42920cc5 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2016-2018 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2020 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -98,6 +98,20 @@ public:
         return (http_port_);
     }
 
+    /// @brief Sets basic-authentication-realm parameter
+    ///
+    /// @param real Basic HTTP authentication realm
+    void setBasicAuthRealm(const std::string& realm) {
+        basic_auth_realm_ = realm;
+    }
+
+    /// @brief Returns basic-authentication-realm parameter
+    ///
+    /// @return Basic HTTP authentication realm.
+    std::string getBasicAuthRealm() const {
+        return (basic_auth_realm_);
+    }
+
     /// @brief Returns non-const reference to configured hooks libraries.
     ///
     /// @return non-const reference to configured hooks libraries.
@@ -147,6 +161,9 @@ private:
     /// TCP port the CA should listen on.
     uint16_t http_port_;
 
+    /// Basic HTTP authentication realm.
+    std::string basic_auth_realm_;
+
     /// @brief Configured hooks libraries.
     isc::hooks::HooksConfig hooks_config_;
 };
index 7f79439e46a7e07f3577ef470a075c9da5fe7bac..14062dc58941d7f963559c6099d82d46a1f878b6 100644 (file)
@@ -36,8 +36,9 @@ namespace agent {
 ///
 /// These are global Control Agent parameters.
 const SimpleDefaults AgentSimpleParser::AGENT_DEFAULTS = {
-    { "http-host",    Element::string,  "127.0.0.1"},
-    { "http-port",    Element::integer,  "8000"}
+    { "http-host",                  Element::string,  "127.0.0.1" },
+    { "http-port",                  Element::integer, "8000" },
+    { "basic-authentication-realm", Element::string,  "kea-control-agent" }
 };
 
 /// @brief This table defines default values for control sockets.
@@ -88,6 +89,8 @@ AgentSimpleParser::parse(const CtrlAgentCfgContextPtr& ctx,
     // Let's get the HTTP parameters first.
     ctx->setHttpHost(SimpleParser::getString(config, "http-host"));
     ctx->setHttpPort(SimpleParser::getIntType<uint16_t>(config, "http-port"));
+    ctx->setBasicAuthRealm(SimpleParser::getString(config,
+       "basic-authentication-realm"));
 
     // Control sockets are second.
     ConstElementPtr ctrl_sockets = config->get("control-sockets");
@@ -98,6 +101,8 @@ AgentSimpleParser::parse(const CtrlAgentCfgContextPtr& ctx,
         }
     }
 
+    // Basic HTTP authentications are third.
+
     // User context can be done at anytime.
     ConstElementPtr user_context = config->get("user-context");
     if (user_context) {
index 64c07b77754d59ce08790944b0fd9433e402be6b..34e5f500ecaf86fef7b38aeeacec696326d4275b 100644 (file)
@@ -40,6 +40,7 @@ libkea_http_la_SOURCES += response_creator_factory.h
 libkea_http_la_SOURCES += response_json.cc response_json.h
 libkea_http_la_SOURCES += url.cc url.h
 libkea_http_la_SOURCES += basic_auth.cc basic_auth.h
+libkea_http_la_SOURCES += basic_auth_config.cc basic_auth_config.h
 
 libkea_http_la_CXXFLAGS = $(AM_CXXFLAGS)
 libkea_http_la_CPPFLAGS = $(AM_CPPFLAGS)
@@ -90,6 +91,8 @@ endif
 # Specify the headers for copying into the installation directory tree.
 libkea_http_includedir = $(pkgincludedir)/http
 libkea_http_include_HEADERS = \
+       basic_auth.h \
+       basic_auth_config.h \
        client.h \
        connection.h \
        connection_pool.h \
@@ -112,6 +115,7 @@ libkea_http_include_HEADERS = \
        response.h \
        response_context.h \
        response_creator.h \
+       response_creator_auth.h \
        response_creator_factory.h \
        response_json.h \
        response_parser.h \
diff --git a/src/lib/http/basic_auth_config.cc b/src/lib/http/basic_auth_config.cc
new file mode 100644 (file)
index 0000000..d6213f5
--- /dev/null
@@ -0,0 +1,137 @@
+// Copyright (C) 2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <http/basic_auth_config.h>
+
+using namespace isc;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace std;
+
+namespace isc {
+namespace http {
+
+BasicHttpAuthClient::BasicHttpAuthClient(const std::string& user,
+                                         const std::string& password,
+                                         const isc::data::ConstElementPtr& user_context)
+    : user_(user), password_(password) {
+    if (user_context) {
+        setContext(user_context);
+    }
+}
+
+ElementPtr
+BasicHttpAuthClient::toElement() const {
+    ElementPtr result = Element::createMap();
+
+    // Set user-context
+    contextToElement(result);
+
+    // Set user
+    result->set("user", Element::create(user_));
+
+    // Set password
+    result->set("password", Element::create(password_));
+
+    return (result);
+}
+
+void
+BasicHttpAuthConfig::add(const std::string& user,
+                         const std::string& password,
+                         const ConstElementPtr& user_context) {
+    BasicHttpAuth basic_auth(user, password);
+    list_.push_back(BasicHttpAuthClient(user, password, user_context));
+    map_[basic_auth.getCredential()] = user;
+}
+
+void
+BasicHttpAuthConfig::clear() {
+    list_.clear();
+    map_.clear();
+}
+
+ElementPtr
+BasicHttpAuthConfig::toElement() const {
+    ElementPtr result = Element::createList();
+
+    for (auto client : list_) {
+        result->add(client.toElement());
+    }
+
+    return (result);
+}
+
+void
+BasicHttpAuthConfig::parse(const ConstElementPtr& config) {
+    if (!config) {
+        return;
+    }
+    if (config->getType() != Element::list) {
+        isc_throw(DhcpConfigError, "basic-authentications must be a list ("
+                  << config->getPosition() << ")");
+    }
+    for (auto client : config->listValue()) {
+        if (client->getType() != Element::map) {
+            isc_throw(DhcpConfigError, "basic-authentications items must be "
+                      << "maps (" << client->getPosition() << ")");
+        }
+
+        // user
+        ConstElementPtr user_cfg = client->get("user");
+        if (!user_cfg) {
+            isc_throw(DhcpConfigError, "user is required in "
+                      << "basic-authentications items ("
+                      << client->getPosition() << ")");
+        }
+        if (user_cfg->getType() != Element::string) {
+            isc_throw(DhcpConfigError, "user must be a string ("
+                      << user_cfg->getPosition() << ")");
+        }
+        string user = user_cfg->stringValue();
+        if (user.empty()) {
+            isc_throw(DhcpConfigError, "user must be not be empty ("
+                      << user_cfg->getPosition() << ")");
+        }
+        if (user.find(':') != string::npos) {
+            isc_throw(DhcpConfigError, "user must not contain a ':': '"
+                      << user << "' (" << user_cfg->getPosition() << ")");
+        }
+
+        // password
+        string password;
+        ConstElementPtr password_cfg = client->get("password");
+        if (password_cfg) {
+            if (password_cfg->getType() != Element::string) {
+                isc_throw(DhcpConfigError, "password must be a string ("
+                          << password_cfg->getPosition() << ")");
+            }
+            password = password_cfg->stringValue();
+        }
+
+        // user context
+        ConstElementPtr user_context = client->get("user-context");
+        if (user_context) {
+            if (user_context->getType() != Element::map) {
+                isc_throw(DhcpConfigError, "user-context must be a map ("
+                          << user_context->getPosition() << ")");
+            }
+        }
+
+        // add it.
+        try {
+            add(user, password, user_context);
+        } catch (const std::exception& ex) {
+            isc_throw(DhcpConfigError, ex.what() << " ("
+                      << client->getPosition() << ")");
+        }
+    }
+}
+
+} // end of namespace isc::http
+} // end of namespace isc
diff --git a/src/lib/http/basic_auth_config.h b/src/lib/http/basic_auth_config.h
new file mode 100644 (file)
index 0000000..a5d3e74
--- /dev/null
@@ -0,0 +1,120 @@
+// Copyright (C) 2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_BASIC_AUTH_CONFIG_H
+#define HTTP_BASIC_AUTH_CONFIG_H
+
+#include <cc/cfg_to_element.h>
+#include <cc/data.h>
+#include <cc/simple_parser.h>
+#include <cc/user_context.h>
+#include <http/basic_auth.h>
+#include <list>
+#include <unordered_map>
+
+namespace isc {
+namespace http {
+
+/// @brief Type of basic HTTP authentication credential and user id map.
+typedef std::unordered_map<std::string, std::string> BasicHttpAuthMap;
+
+/// @brief Basic HTTP authentication client configuration.
+class BasicHttpAuthClient : public isc::data::UserContext,
+                            public isc::data::CfgToElement {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// @param user User id
+    /// @param password Password
+    /// @param user_context Optional user context
+    BasicHttpAuthClient(const std::string& user,
+                        const std::string& password,
+                        const isc::data::ConstElementPtr& user_context);
+
+    /// @brief Returns the user id.
+    const std::string& getUser() const {
+        return (user_);
+    }
+
+    /// @brief Returns the password.
+    const std::string& getPassword() const {
+        return (password_);
+    }
+
+    /// @brief Unparses basic HTTP authentication client configuration.
+    ///
+    /// @return A pointer to unparsed client configuration.
+    virtual isc::data::ElementPtr toElement() const;
+
+private:
+
+    /// @brief The user id.
+    std::string user_;
+
+    /// @brief The password.
+    std::string password_;
+};
+
+/// @brief Type of basic HTTP authentication client configuration list.
+typedef std::list<BasicHttpAuthClient> BasicHttpAuthClientList;
+
+/// @brief Basic HTTP authentication configuration.
+class BasicHttpAuthConfig : public isc::data::CfgToElement {
+public:
+
+    /// @brief Add a client configuration.
+    ///
+    /// @param user User id
+    /// @param password Password
+    /// @param user_context Optional user context
+    /// @throw BadValue if the user id contains the ':' character.
+    void add(const std::string& user,
+             const std::string& password,
+             const isc::data::ConstElementPtr& user_context = isc::data::ConstElementPtr());
+
+    /// @brief Clear configuration.
+    void clear();
+
+    /// @brief Returns the list of client configuration.
+    ///
+    /// @return List of basic HTTP authentication client configuration.
+    const BasicHttpAuthClientList& getClientList() const {
+        return (list_);
+    }
+
+    /// @brief Returns the credential and user id map.
+    ///
+    /// @return The basic HTTP authentication credential and user id map.
+    const BasicHttpAuthMap& getCredentialMap() const {
+        return (map_);
+    }
+
+    /// @brief Parses basic HTTP authentication configuration.
+    ///
+    /// @param config Element holding the basic HTTP authentication
+    /// configuration to be parsed.
+    /// @throw DhcpConfigError when the configuration is invalid.
+    void parse(const isc::data::ConstElementPtr& config);
+
+    /// @brief Unparses basic HTTP authentication configuration.
+    ///
+    /// @return A pointer to unparsed basic HTTP authentication configuration.
+    virtual isc::data::ElementPtr toElement() const;
+
+private:
+
+    /// @brief The list of basic HTTP authentication client configuration.
+    BasicHttpAuthClientList list_;
+
+    /// @brief The basic HTTP authentication credential and user id map.
+    BasicHttpAuthMap map_;
+};
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif // endif HTTP_BASIC_AUTH_CONFIG_H
index 5cc085d0711cc3d322f8a1af7d8c998f2bd85917..0bcfe2c9932b1b8a0c99ce3523aff8eb0e4c7abf 100644 (file)
@@ -7,6 +7,7 @@
 #ifndef HTTP_RESPONSE_CREATOR_AUTH_H
 #define HTTP_RESPONSE_CREATOR_AUTH_H
 
+#include <http/basic_auth_config.h>
 #include <http/response_creator.h>
 #include <string.h>
 #include <unordered_map>
@@ -14,9 +15,6 @@
 namespace isc {
 namespace http {
 
-/// @brief Type of basic HTTP authentication credential and user id map.
-typedef std::unordered_map<std::string, std::string> BasicHttpAuthMap;
-
 /// @brief Validate basic HTTP authentication.
 ///
 /// @param creator The HTTP response creator.
index e88d4dcccc9d2048a3448f5d6ad2dde1684fae63..6dd63a1d5ee1d347e9038f5f0fe7c4e6f3b9d923 100644 (file)
@@ -21,6 +21,7 @@ if HAVE_GTEST
 TESTS += libhttp_unittests
 
 libhttp_unittests_SOURCES  = basic_auth_unittests.cc
+libhttp_unittests_SOURCES += basic_auth_config_unittests.cc
 libhttp_unittests_SOURCES += connection_pool_unittests.cc
 libhttp_unittests_SOURCES += date_time_unittests.cc
 libhttp_unittests_SOURCES += http_header_unittests.cc
diff --git a/src/lib/http/tests/basic_auth_config_unittests.cc b/src/lib/http/tests/basic_auth_config_unittests.cc
new file mode 100644 (file)
index 0000000..5b7cd86
--- /dev/null
@@ -0,0 +1,218 @@
+// Copyright (C) 2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <http/basic_auth_config.h>
+#include <testutils/gtest_utils.h>
+#include <testutils/test_to_element.h>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::http;
+using namespace isc::test;
+using namespace std;
+
+namespace {
+
+// Test that basic auth client works as expected.
+TEST(BasicHttpAuthClientTest, basic) {
+    // Create a client.
+    ConstElementPtr ctx = Element::fromJSON("{ \"foo\": \"bar\" }");
+    BasicHttpAuthClient client("foo", "bar", ctx);
+
+    // Check it.
+    EXPECT_EQ("foo", client.getUser());
+    EXPECT_EQ("bar", client.getPassword());
+    EXPECT_TRUE(ctx->equals(*client.getContext()));
+
+    // Check toElement.
+    ElementPtr expected = Element::createMap();
+    expected->set("user", Element::create(string("foo")));
+    expected->set("password", Element::create(string("bar")));
+    expected->set("user-context", ctx);
+    runToElementTest<BasicHttpAuthClient>(expected, client);
+}
+
+// Test that basic auth configuration works as expected.
+TEST(BasicHttpAuthConfigTest, basic) {
+    // Create a configuration.
+    BasicHttpAuthConfig config;
+
+    // Initial configuration is empty.
+    EXPECT_TRUE(config.getClientList().empty());
+    EXPECT_TRUE(config.getCredentialMap().empty());
+
+    // Add rejects user id with embedded ':'.
+    EXPECT_THROW(config.add("foo:", "bar"), BadValue);
+
+    // Add a client.
+    ConstElementPtr ctx = Element::fromJSON("{ \"foo\": \"bar\" }");
+    EXPECT_NO_THROW(config.add("foo", "bar", ctx));
+
+    // Check the client.
+    ASSERT_EQ(1, config.getClientList().size());
+    const BasicHttpAuthClient& client = config.getClientList().front();
+    EXPECT_EQ("foo", client.getUser());
+    EXPECT_EQ("bar", client.getPassword());
+    EXPECT_TRUE(ctx->equals(*client.getContext()));
+
+    // Check the credential.
+    ASSERT_NE(0, config.getCredentialMap().count("Zm9vOmJhcg=="));
+    string user;
+    EXPECT_NO_THROW(user = config.getCredentialMap().at("Zm9vOmJhcg=="));
+    EXPECT_EQ("foo", user);
+
+    // Check toElement.
+    ElementPtr expected = Element::createList();
+    ElementPtr elem = Element::createMap();
+    elem->set("user", Element::create(string("foo")));
+    elem->set("password", Element::create(string("bar")));
+    elem->set("user-context", ctx);
+    expected->add(elem);
+    runToElementTest<BasicHttpAuthConfig>(expected, config);
+
+    // Add a second client and test it.
+    EXPECT_NO_THROW(config.add("test", "123\xa3"));
+    ASSERT_EQ(2, config.getClientList().size());
+    EXPECT_EQ("foo", config.getClientList().front().getUser());
+    EXPECT_EQ("test", config.getClientList().back().getUser());
+    ASSERT_NE(0, config.getCredentialMap().count("dGVzdDoxMjPCow=="));
+
+    // Check clear.
+    config.clear();
+    expected = Element::createList();
+    runToElementTest<BasicHttpAuthConfig>(expected, config);
+
+    // Add clients again.
+    EXPECT_NO_THROW(config.add("test", "123\xa3"));
+    EXPECT_NO_THROW(config.add("foo", "bar", ctx));
+
+    // Check that toElement keeps add order.
+    ElementPtr elem0 = Element::createMap();
+    elem0->set("user", Element::create(string("test")));
+    elem0->set("password", Element::create(string("123\xa3")));
+    expected->add(elem0);
+    expected->add(elem);
+    runToElementTest<BasicHttpAuthConfig>(expected, config);
+}
+
+// Test that basic auth configuration parses.
+TEST(BasicHttpAuthConfigTest, parse) {
+    BasicHttpAuthConfig config;
+    ElementPtr cfg;
+
+    // No config is accepted.
+    EXPECT_NO_THROW(config.parse(cfg));
+    EXPECT_TRUE(config.getClientList().empty());
+    EXPECT_TRUE(config.getCredentialMap().empty());
+    runToElementTest<BasicHttpAuthConfig>(Element::createList(), config);
+
+    // The config must be a list.
+    cfg = Element::createMap();
+    EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+                     "basic-authentications must be a list (:0:0)");
+
+    // The client config must be a map.
+    cfg = Element::createList();
+    ElementPtr client_cfg = Element::createList();
+    cfg->add(client_cfg);
+    EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+                     "basic-authentications items must be maps (:0:0)");
+
+    // The user parameter is mandatory in client config.
+    client_cfg = Element::createMap();
+    cfg = Element::createList();
+    cfg->add(client_cfg);
+    EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+                     "user is required in basic-authentications items (:0:0)");
+
+    // The user parameter must be a string.
+    ElementPtr user_cfg = Element::create(1);
+    client_cfg = Element::createMap();
+    client_cfg->set("user", user_cfg);
+    cfg = Element::createList();
+    cfg->add(client_cfg);
+    EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+                     "user must be a string (:0:0)");
+
+    // The user parameter must not be empty.
+    user_cfg = Element::create(string(""));
+    client_cfg = Element::createMap();
+    client_cfg->set("user", user_cfg);
+    cfg = Element::createList();
+    cfg->add(client_cfg);
+    EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+                     "user must be not be empty (:0:0)");
+
+    // The user parameter must not contain ':'.
+    user_cfg = Element::create(string("foo:bar"));
+    client_cfg = Element::createMap();
+    client_cfg->set("user", user_cfg);
+    cfg = Element::createList();
+    cfg->add(client_cfg);
+    EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+                     "user must not contain a ':': 'foo:bar' (:0:0)");
+
+    // Password is not required.
+    user_cfg = Element::create(string("foo"));
+    client_cfg = Element::createMap();
+    client_cfg->set("user", user_cfg);
+    cfg = Element::createList();
+    cfg->add(client_cfg);
+    EXPECT_NO_THROW(config.parse(cfg));
+    ASSERT_EQ(1, config.getClientList().size());
+    EXPECT_EQ("", config.getClientList().front().getPassword());
+    config.clear();
+
+    // The password parameter must be a string.
+    ElementPtr password_cfg = Element::create(1);
+    client_cfg = Element::createMap();
+    client_cfg->set("user", user_cfg);
+    client_cfg->set("password", password_cfg);
+    cfg = Element::createList();
+    cfg->add(client_cfg);
+    EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+                     "password must be a string (:0:0)");
+
+    // Empty password is accepted.
+    password_cfg = Element::create(string(""));
+    client_cfg = Element::createMap();
+    client_cfg->set("user", user_cfg);
+    client_cfg->set("password", password_cfg);
+    cfg = Element::createList();
+    cfg->add(client_cfg);
+    EXPECT_NO_THROW(config.parse(cfg));
+    ASSERT_EQ(1, config.getClientList().size());
+    EXPECT_EQ("", config.getClientList().front().getPassword());
+    config.clear();
+
+    // User context must be a map.
+    password_cfg = Element::create(string("bar"));
+    ElementPtr ctx = Element::createList();
+    client_cfg = Element::createMap();
+    client_cfg->set("user", user_cfg);
+    client_cfg->set("password", password_cfg);
+    client_cfg->set("user-context", ctx);
+    cfg = Element::createList();
+    cfg->add(client_cfg);
+    EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+                     "user-context must be a map (:0:0)");
+
+    // Check a working not empty config.
+    ctx = Element::fromJSON("{ \"foo\": \"bar\" }");
+    client_cfg = Element::createMap();
+    client_cfg->set("user", user_cfg);
+    client_cfg->set("password", password_cfg);
+    client_cfg->set("user-context", ctx);
+    cfg = Element::createList();
+    cfg->add(client_cfg);
+    EXPECT_NO_THROW(config.parse(cfg));
+    runToElementTest<BasicHttpAuthConfig>(cfg, config);
+}
+
+} // end of anonymous namespace