diff options
Diffstat (limited to 'src/bin/agent/tests/ca_cfg_mgr_unittests.cc')
-rw-r--r-- | src/bin/agent/tests/ca_cfg_mgr_unittests.cc | 703 |
1 files changed, 703 insertions, 0 deletions
diff --git a/src/bin/agent/tests/ca_cfg_mgr_unittests.cc b/src/bin/agent/tests/ca_cfg_mgr_unittests.cc new file mode 100644 index 0000000..a7b40c5 --- /dev/null +++ b/src/bin/agent/tests/ca_cfg_mgr_unittests.cc @@ -0,0 +1,703 @@ +// Copyright (C) 2016-2022 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 <agent/ca_cfg_mgr.h> +#include <agent/parser_context.h> +#include <exceptions/exceptions.h> +#include <process/testutils/d_test_stubs.h> +#include <process/d_cfg_mgr.h> +#include <http/basic_auth_config.h> +#include <agent/tests/test_callout_libraries.h> +#include <agent/tests/test_data_files_config.h> +#include <boost/pointer_cast.hpp> +#include <boost/scoped_ptr.hpp> +#include <gtest/gtest.h> + +using namespace isc::agent; +using namespace isc::data; +using namespace isc::hooks; +using namespace isc::http; +using namespace isc::process; + +namespace { + +/// @brief Almost regular agent CfgMgr with internal parse method exposed. +class NakedAgentCfgMgr : public CtrlAgentCfgMgr { +public: + using CtrlAgentCfgMgr::parse; +}; + +// Tests construction of CtrlAgentCfgMgr class. +TEST(CtrlAgentCfgMgr, construction) { + boost::scoped_ptr<CtrlAgentCfgMgr> cfg_mgr; + + // Verify that configuration manager constructions without error. + ASSERT_NO_THROW(cfg_mgr.reset(new CtrlAgentCfgMgr())); + + // Verify that the context can be retrieved and is not null. + CtrlAgentCfgContextPtr context; + ASSERT_NO_THROW(context = cfg_mgr->getCtrlAgentCfgContext()); + EXPECT_TRUE(context); + + // Verify that the manager can be destructed without error. + EXPECT_NO_THROW(cfg_mgr.reset()); +} + +// Tests if getContext can be retrieved. +TEST(CtrlAgentCfgMgr, getContext) { + CtrlAgentCfgMgr cfg_mgr; + + CtrlAgentCfgContextPtr ctx; + ASSERT_NO_THROW(ctx = cfg_mgr.getCtrlAgentCfgContext()); + ASSERT_TRUE(ctx); +} + +// Tests if context can store and retrieve HTTP parameters +TEST(CtrlAgentCfgMgr, contextHttpParams) { + CtrlAgentCfgContext ctx; + + // Check http parameters + ctx.setHttpPort(12345); + EXPECT_EQ(12345, ctx.getHttpPort()); + + ctx.setHttpHost("alnitak"); + EXPECT_EQ("alnitak", ctx.getHttpHost()); +} + +// Tests if context can store and retrieve TLS parameters. +TEST(CtrlAgentCfgMgr, contextTlsParams) { + CtrlAgentCfgContext ctx; + + // Check TLS parameters + ctx.setTrustAnchor("my-ca"); + EXPECT_EQ("my-ca", ctx.getTrustAnchor()); + + ctx.setCertFile("my-cert"); + EXPECT_EQ("my-cert", ctx.getCertFile()); + + ctx.setKeyFile("my-key"); + EXPECT_EQ("my-key", ctx.getKeyFile()); + + EXPECT_TRUE(ctx.getCertRequired()); + ctx.setCertRequired(false); + EXPECT_FALSE(ctx.getCertRequired()); +} + +// Tests if context can store and retrieve control socket information. +TEST(CtrlAgentCfgMgr, contextSocketInfo) { + + CtrlAgentCfgContext ctx; + + // Check control socket parameters + // By default, there are no control sockets stored. + EXPECT_FALSE(ctx.getControlSocketInfo("d2")); + EXPECT_FALSE(ctx.getControlSocketInfo("dhcp4")); + EXPECT_FALSE(ctx.getControlSocketInfo("dhcp6")); + + ConstElementPtr socket1 = Element::fromJSON("{ \"socket-type\": \"unix\",\n" + " \"socket-name\": \"socket1\" }"); + ConstElementPtr socket2 = Element::fromJSON("{ \"socket-type\": \"unix\",\n" + " \"socket-name\": \"socket2\" }"); + ConstElementPtr socket3 = Element::fromJSON("{ \"socket-type\": \"unix\",\n" + " \"socket-name\": \"socket3\" }"); + // Ok, now set the control socket for D2 + EXPECT_NO_THROW(ctx.setControlSocketInfo(socket1, "d2")); + + // Now check the values returned + EXPECT_EQ(socket1, ctx.getControlSocketInfo("d2")); + EXPECT_FALSE(ctx.getControlSocketInfo("dhcp4")); + EXPECT_FALSE(ctx.getControlSocketInfo("dhcp6")); + + // Now set the v6 socket and sanity check again + EXPECT_NO_THROW(ctx.setControlSocketInfo(socket2, "dhcp6")); + + // Should be possible to retrieve two sockets. + EXPECT_EQ(socket1, ctx.getControlSocketInfo("d2")); + EXPECT_EQ(socket2, ctx.getControlSocketInfo("dhcp6")); + EXPECT_FALSE(ctx.getControlSocketInfo("dhcp4")); + + // Finally, set the third control socket. + EXPECT_NO_THROW(ctx.setControlSocketInfo(socket3, "dhcp4")); + EXPECT_EQ(socket1, ctx.getControlSocketInfo("d2")); + EXPECT_EQ(socket2, ctx.getControlSocketInfo("dhcp6")); + EXPECT_EQ(socket3, ctx.getControlSocketInfo("dhcp4")); +} + +// Tests if copied context retains all parameters. +TEST(CtrlAgentCfgMgr, contextSocketInfoCopy) { + + CtrlAgentCfgContext ctx; + + ConstElementPtr socket1 = Element::fromJSON("{ \"socket-type\": \"unix\",\n" + " \"socket-name\": \"socket1\" }"); + ConstElementPtr socket2 = Element::fromJSON("{ \"socket-type\": \"unix\",\n" + " \"socket-name\": \"socket2\" }"); + ConstElementPtr socket3 = Element::fromJSON("{ \"socket-type\": \"unix\",\n" + " \"socket-name\": \"socket3\" }"); + // Ok, now set the control sockets + EXPECT_NO_THROW(ctx.setControlSocketInfo(socket1, "d2")); + EXPECT_NO_THROW(ctx.setControlSocketInfo(socket2, "dhcp4")); + EXPECT_NO_THROW(ctx.setControlSocketInfo(socket3, "dhcp6")); + + EXPECT_NO_THROW(ctx.setHttpPort(12345)); + EXPECT_NO_THROW(ctx.setHttpHost("bellatrix")); + + HooksConfig& libs = ctx.getHooksConfig(); + string exp_name("testlib1.so"); + ConstElementPtr exp_param(new StringElement("myparam")); + libs.add(exp_name, exp_param); + + BasicHttpAuthConfigPtr auth(new BasicHttpAuthConfig()); + auth->setRealm("foobar"); + auth->add("foo", "", "bar", ""); + EXPECT_NO_THROW(ctx.setAuthConfig(auth)); + + // Make a copy. + ConfigPtr copy_base(ctx.clone()); + CtrlAgentCfgContextPtr copy = boost::dynamic_pointer_cast<CtrlAgentCfgContext>(copy_base); + ASSERT_TRUE(copy); + + // Now check the values returned + EXPECT_EQ(12345, copy->getHttpPort()); + EXPECT_EQ("bellatrix", copy->getHttpHost()); + + // Check socket info + ASSERT_TRUE(copy->getControlSocketInfo("d2")); + ASSERT_TRUE(copy->getControlSocketInfo("dhcp4")); + ASSERT_TRUE(copy->getControlSocketInfo("dhcp6")); + EXPECT_EQ(socket1->str(), copy->getControlSocketInfo("d2")->str()); + EXPECT_EQ(socket2->str(), copy->getControlSocketInfo("dhcp4")->str()); + EXPECT_EQ(socket3->str(), copy->getControlSocketInfo("dhcp6")->str()); + + // Check hook libs + const HookLibsCollection& libs2 = copy->getHooksConfig().get(); + ASSERT_EQ(1, libs2.size()); + EXPECT_EQ(exp_name, libs2[0].first); + ASSERT_TRUE(libs2[0].second); + EXPECT_EQ(exp_param->str(), libs2[0].second->str()); + + // Check authentication + const HttpAuthConfigPtr& auth2 = copy->getAuthConfig(); + ASSERT_TRUE(auth2); + EXPECT_EQ(auth->toElement()->str(), auth2->toElement()->str()); +} + + +// Tests if the context can store and retrieve hook libs information. +TEST(CtrlAgentCfgMgr, contextHookParams) { + CtrlAgentCfgContext ctx; + + // By default there should be no hooks. + HooksConfig& libs = ctx.getHooksConfig(); + EXPECT_TRUE(libs.get().empty()); + + libs.add("libone.so", ConstElementPtr()); + libs.add("libtwo.so", Element::fromJSON("{\"foo\": true}")); + libs.add("libthree.so", Element::fromJSON("{\"bar\": 42}")); + + const HooksConfig& stored_libs = ctx.getHooksConfig(); + EXPECT_EQ(3, stored_libs.get().size()); + + // @todo add a == operator to HooksConfig + EXPECT_EQ(libs.get(), stored_libs.get()); +} + +// Test if the context can store and retrieve basic HTTP authentication +// configuration. +TEST(CtrlAgentCfgMgr, contextAuthConfig) { + CtrlAgentCfgContext ctx; + + // By default there should be no authentication. + EXPECT_FALSE(ctx.getAuthConfig()); + BasicHttpAuthConfigPtr auth(new BasicHttpAuthConfig()); + EXPECT_NO_THROW(ctx.setAuthConfig(auth)); + + auth->setRealm("foobar"); + auth->add("foo", "", "bar", ""); + auth->add("test", "", "123\xa3", ""); + + const HttpAuthConfigPtr& stored_auth = ctx.getAuthConfig(); + ASSERT_TRUE(stored_auth); + EXPECT_FALSE(stored_auth->empty()); + EXPECT_EQ(auth->toElement()->str(), stored_auth->toElement()->str()); +} + +// Test if the context can store and retrieve basic HTTP authentication +// configuration using files. +TEST(CtrlAgentCfgMgr, contextAuthConfigFile) { + CtrlAgentCfgContext ctx; + + // By default there should be no authentication. + EXPECT_FALSE(ctx.getAuthConfig()); + BasicHttpAuthConfigPtr auth(new BasicHttpAuthConfig()); + EXPECT_NO_THROW(ctx.setAuthConfig(auth)); + + auth->setRealm("foobar"); + auth->setDirectory("/tmp"); + auth->add("", "/tmp/foo", "", "/tmp/bar"); + auth->add("", "/tmp/test", "", "/tmp/pwd"); + + const HttpAuthConfigPtr& stored_auth = ctx.getAuthConfig(); + ASSERT_TRUE(stored_auth); + EXPECT_FALSE(stored_auth->empty()); + EXPECT_EQ(auth->toElement()->str(), stored_auth->toElement()->str()); +} + +/// Control Agent configurations used in tests. +const char* AGENT_CONFIGS[] = { + + // configuration 0: empty (nothing specified) + "{ }", + + // Configuration 1: http parameters only (no control sockets, not hooks) + "{ \"http-host\": \"betelgeuse\",\n" + " \"http-port\": 8001\n" + "}", + + // Configuration 2: http and 1 socket + "{\n" + " \"http-host\": \"betelgeuse\",\n" + " \"http-port\": 8001,\n" + " \"control-sockets\": {\n" + " \"dhcp4\": {\n" + " \"socket-name\": \"/tmp/socket-v4\"\n" + " }\n" + " }\n" + "}", + + // Configuration 3: http and all 3 sockets + "{\n" + " \"http-host\": \"betelgeuse\",\n" + " \"http-port\": 8001,\n" + " \"control-sockets\": {\n" + " \"dhcp4\": {\n" + " \"socket-name\": \"/tmp/socket-v4\"\n" + " },\n" + " \"dhcp6\": {\n" + " \"socket-name\": \"/tmp/socket-v6\"\n" + " },\n" + " \"d2\": {\n" + " \"socket-name\": \"/tmp/socket-d2\"\n" + " }\n" + " }\n" + "}", + + // Configuration 4: http, 1 socket and hooks + // CA is able to load hook libraries that augment its operation. + // The primary functionality is the ability to add new commands. + "{\n" + " \"http-host\": \"betelgeuse\",\n" + " \"http-port\": 8001,\n" + " \"control-sockets\": {\n" + " \"dhcp4\": {\n" + " \"socket-name\": \"/tmp/socket-v4\"\n" + " }\n" + " },\n" + " \"hooks-libraries\": [" + " {" + " \"library\": \"%LIBRARY%\"," + " \"parameters\": {\n" + " \"param1\": \"foo\"\n" + " }\n" + " }\n" + " ]\n" + "}", + + // Configuration 5: http and 1 socket (d2 only) + "{\n" + " \"http-host\": \"betelgeuse\",\n" + " \"http-port\": 8001,\n" + " \"control-sockets\": {\n" + " \"d2\": {\n" + " \"socket-name\": \"/tmp/socket-d2\"\n" + " }\n" + " }\n" + "}", + + // Configuration 6: http and 1 socket (dhcp6 only) + "{\n" + " \"http-host\": \"betelgeuse\",\n" + " \"http-port\": 8001,\n" + " \"control-sockets\": {\n" + " \"dhcp6\": {\n" + " \"socket-name\": \"/tmp/socket-v6\"\n" + " }\n" + " }\n" + "}", + + // Configuration 7: http, 1 socket and authentication + "{\n" + " \"http-host\": \"betelgeuse\",\n" + " \"http-port\": 8001,\n" + " \"authentication\": {\n" + " \"type\": \"basic\",\n" + " \"realm\": \"foobar\",\n" + " \"clients\": [" + " {" + " \"user\": \"foo\",\n" + " \"password\": \"bar\"\n" + " },{\n" + " \"user\": \"test\",\n" + " \"password\": \"123\\u00a3\"\n" + " }\n" + " ]\n" + " },\n" + " \"control-sockets\": {\n" + " \"dhcp4\": {\n" + " \"socket-name\": \"/tmp/socket-v4\"\n" + " }\n" + " }\n" + "}", + + // Configuration 8: http and 2 sockets with user contexts and comments + "{\n" + " \"user-context\": { \"comment\": \"Indirect comment\" },\n" + " \"http-host\": \"betelgeuse\",\n" + " \"http-port\": 8001,\n" + " \"authentication\": {\n" + " \"comment\": \"basic HTTP authentication\",\n" + " \"type\": \"basic\",\n" + " \"realm\": \"foobar\",\n" + " \"clients\": [" + " {" + " \"comment\": \"foo is authorized\",\n" + " \"user\": \"foo\",\n" + " \"password\": \"bar\"\n" + " },{\n" + " \"user\": \"test\",\n" + " \"user-context\": { \"no password\": true }\n" + " }\n" + " ]\n" + " },\n" + " \"control-sockets\": {\n" + " \"dhcp4\": {\n" + " \"comment\": \"dhcp4 socket\",\n" + " \"socket-name\": \"/tmp/socket-v4\"\n" + " },\n" + " \"dhcp6\": {\n" + " \"socket-name\": \"/tmp/socket-v6\",\n" + " \"user-context\": { \"version\": 1 }\n" + " }\n" + " }\n" + "}", + + // Configuration 9: https aka http over TLS + "{\n" + " \"http-host\": \"betelgeuse\",\n" + " \"http-port\": 8001,\n" + " \"trust-anchor\": \"my-ca\",\n" + " \"cert-file\": \"my-cert\",\n" + " \"key-file\": \"my-key\",\n" + " \"cert-required\": false\n" + "}", + + // Configuration 10: http, 1 socket and authentication using files + "{\n" + " \"http-host\": \"betelgeuse\",\n" + " \"http-port\": 8001,\n" + " \"authentication\": {\n" + " \"type\": \"basic\",\n" + " \"realm\": \"foobar\",\n" + " \"directory\": \"" CA_TEST_DATA_DIR "\",\n" + " \"clients\": [" + " {" + " \"user-file\": \"hiddenu\",\n" + " \"password-file\": \"hiddenp\"\n" + " },{\n" + " \"password-file\": \"hiddens\"\n" + " }\n" + " ]\n" + " },\n" + " \"control-sockets\": {\n" + " \"dhcp4\": {\n" + " \"socket-name\": \"/tmp/socket-v4\"\n" + " }\n" + " }\n" + "}" +}; + +/// @brief Class used for testing CfgMgr +class AgentParserTest : public isc::process::ConfigParseTest { +public: + + /// @brief Tries to load input text as a configuration + /// + /// @param config text containing input configuration + /// @param expected_answer expected result of configuration (0 = success) + void configParse(const char* config, int expected_answer) { + isc::agent::ParserContext parser; + ConstElementPtr json = parser.parseString(config, ParserContext::PARSER_SUB_AGENT); + + EXPECT_NO_THROW(answer_ = cfg_mgr_.parse(json, false)); + EXPECT_TRUE(checkAnswer(expected_answer)); + } + + /// @brief Replaces %LIBRARY% with specified library name + /// + /// @param config input config text (should contain "%LIBRARY%" string) + /// @param lib_name %LIBRARY% will be replaced with that name + /// @return configuration text with library name replaced + std::string pathReplacer(const char* config, const char* lib_name) { + string txt(config); + txt.replace(txt.find("%LIBRARY%"), strlen("%LIBRARY%"), string(lib_name)); + return (txt); + } + + /// Configuration Manager (used in tests) + NakedAgentCfgMgr cfg_mgr_; +}; + +// This test verifies if an empty config is handled properly. In practice such +// a config makes little sense, but perhaps it's ok for a default deployment. +// Sadly, our bison parser requires at last one parameter to be present. +// Until we determine whether we want the empty config to be allowed or not, +// this test remains disabled. +TEST_F(AgentParserTest, DISABLED_configParseEmpty) { + configParse(AGENT_CONFIGS[0], 0); +} + +// This test checks if a config with only HTTP parameters is parsed properly. +TEST_F(AgentParserTest, configParseHttpOnly) { + configParse(AGENT_CONFIGS[1], 0); + + CtrlAgentCfgContextPtr ctx = cfg_mgr_.getCtrlAgentCfgContext(); + ASSERT_TRUE(ctx); + EXPECT_EQ("betelgeuse", ctx->getHttpHost()); + EXPECT_EQ(8001, ctx->getHttpPort()); +} + +// Tests if a single socket can be configured. BTW this test also checks +// if default value for socket-type is specified (the config doesn't have it, +// so the default value should be filed in). +TEST_F(AgentParserTest, configParseSocketDhcp4) { + configParse(AGENT_CONFIGS[2], 0); + + CtrlAgentCfgContextPtr ctx = cfg_mgr_.getCtrlAgentCfgContext(); + ASSERT_TRUE(ctx); + ConstElementPtr socket = ctx->getControlSocketInfo("dhcp4"); + ASSERT_TRUE(socket); + EXPECT_EQ("{ \"socket-name\": \"/tmp/socket-v4\", \"socket-type\": \"unix\" }", + socket->str()); + EXPECT_FALSE(ctx->getControlSocketInfo("dhcp6")); + EXPECT_FALSE(ctx->getControlSocketInfo("d2")); +} + +// Tests if a single socket can be configured. BTW this test also checks +// if default value for socket-type is specified (the config doesn't have it, +// so the default value should be filed in). +TEST_F(AgentParserTest, configParseSocketD2) { + configParse(AGENT_CONFIGS[5], 0); + + CtrlAgentCfgContextPtr ctx = cfg_mgr_.getCtrlAgentCfgContext(); + ASSERT_TRUE(ctx); + ConstElementPtr socket = ctx->getControlSocketInfo("d2"); + ASSERT_TRUE(socket); + EXPECT_EQ("{ \"socket-name\": \"/tmp/socket-d2\", \"socket-type\": \"unix\" }", + socket->str()); + + EXPECT_FALSE(ctx->getControlSocketInfo("dhcp4")); + EXPECT_FALSE(ctx->getControlSocketInfo("dhcp6")); +} + +// Tests if a single socket can be configured. BTW this test also checks +// if default value for socket-type is specified (the config doesn't have it, +// so the default value should be filed in). +TEST_F(AgentParserTest, configParseSocketDhcp6) { + configParse(AGENT_CONFIGS[6], 0); + + CtrlAgentCfgContextPtr ctx = cfg_mgr_.getCtrlAgentCfgContext(); + ASSERT_TRUE(ctx); + ConstElementPtr socket = ctx->getControlSocketInfo("dhcp6"); + ASSERT_TRUE(socket); + EXPECT_EQ("{ \"socket-name\": \"/tmp/socket-v6\", \"socket-type\": \"unix\" }", + socket->str()); + EXPECT_FALSE(ctx->getControlSocketInfo("dhcp4")); + EXPECT_FALSE(ctx->getControlSocketInfo("d2")); +} + +// This tests if all 3 sockets can be configured and makes sure the parser +// doesn't confuse them. +TEST_F(AgentParserTest, configParse3Sockets) { + configParse(AGENT_CONFIGS[3], 0); + CtrlAgentCfgContextPtr ctx = cfg_mgr_.getCtrlAgentCfgContext(); + ASSERT_TRUE(ctx); + ConstElementPtr socket2 = ctx->getControlSocketInfo("d2"); + ConstElementPtr socket4 = ctx->getControlSocketInfo("dhcp4"); + ConstElementPtr socket6 = ctx->getControlSocketInfo("dhcp6"); + ASSERT_TRUE(socket2); + EXPECT_EQ("{ \"socket-name\": \"/tmp/socket-d2\", \"socket-type\": \"unix\" }", + socket2->str()); + ASSERT_TRUE(socket4); + EXPECT_EQ("{ \"socket-name\": \"/tmp/socket-v4\", \"socket-type\": \"unix\" }", + socket4->str()); + ASSERT_TRUE(socket6); + EXPECT_EQ("{ \"socket-name\": \"/tmp/socket-v6\", \"socket-type\": \"unix\" }", + socket6->str()); +} + +// This test checks that the config file with hook library specified can be +// loaded. This one is a bit tricky, because the parser sanity checks the lib +// name. In particular, it checks if such a library exists. Therefore we +// can't use AGENT_CONFIGS[4] as is, but need to run it through path replacer. +TEST_F(AgentParserTest, configParseHooks) { + // Create the configuration with proper lib path. + std::string cfg = pathReplacer(AGENT_CONFIGS[4], CALLOUT_LIBRARY); + // The configuration should be successful. + configParse(cfg.c_str(), 0); + + // The context now should have the library specified. + CtrlAgentCfgContextPtr ctx = cfg_mgr_.getCtrlAgentCfgContext(); + const HookLibsCollection libs = ctx->getHooksConfig().get(); + ASSERT_EQ(1, libs.size()); + EXPECT_EQ(string(CALLOUT_LIBRARY), libs[0].first); + ASSERT_TRUE(libs[0].second); + EXPECT_EQ("{ \"param1\": \"foo\" }", libs[0].second->str()); +} + +// This test checks that the config file with basic HTTP authentication can be +// loaded. +TEST_F(AgentParserTest, configParseAuth) { + configParse(AGENT_CONFIGS[7], 0); + CtrlAgentCfgContextPtr ctx = cfg_mgr_.getCtrlAgentCfgContext(); + const HttpAuthConfigPtr& auth = ctx->getAuthConfig(); + ASSERT_TRUE(auth); + const BasicHttpAuthConfigPtr& basic_auth = + boost::dynamic_pointer_cast<BasicHttpAuthConfig>(auth); + ASSERT_TRUE(basic_auth); + + // Check realm + EXPECT_EQ("foobar", basic_auth->getRealm()); + + // Check credentials + auto credentials = basic_auth->getCredentialMap(); + EXPECT_EQ(2, credentials.size()); + std::string user; + EXPECT_NO_THROW(user = credentials.at("Zm9vOmJhcg==")); + EXPECT_EQ("foo", user); + EXPECT_NO_THROW(user = credentials.at("dGVzdDoxMjPCow==")); + EXPECT_EQ("test", user); + + // Check clients. + BasicHttpAuthConfig expected; + expected.setRealm("foobar"); + expected.add("foo", "", "bar", ""); + expected.add("test", "", "123\xa3", ""); + EXPECT_EQ(expected.toElement()->str(), basic_auth->toElement()->str()); +} + +// This test checks comments. +TEST_F(AgentParserTest, comments) { + configParse(AGENT_CONFIGS[8], 0); + CtrlAgentCfgContextPtr agent_ctx = cfg_mgr_.getCtrlAgentCfgContext(); + ASSERT_TRUE(agent_ctx); + + // Check global user context. + ConstElementPtr ctx = agent_ctx->getContext(); + ASSERT_TRUE(ctx); + ASSERT_EQ(1, ctx->size()); + ASSERT_TRUE(ctx->get("comment")); + EXPECT_EQ("\"Indirect comment\"", ctx->get("comment")->str()); + + // There is a DHCP4 control socket. + ConstElementPtr socket4 = agent_ctx->getControlSocketInfo("dhcp4"); + ASSERT_TRUE(socket4); + + // Check DHCP4 control socket user context. + ConstElementPtr ctx4 = socket4->get("user-context"); + ASSERT_TRUE(ctx4); + ASSERT_EQ(1, ctx4->size()); + ASSERT_TRUE(ctx4->get("comment")); + EXPECT_EQ("\"dhcp4 socket\"", ctx4->get("comment")->str()); + + // There is a DHCP6 control socket. + ConstElementPtr socket6 = agent_ctx->getControlSocketInfo("dhcp6"); + ASSERT_TRUE(socket6); + + // Check DHCP6 control socket user context. + ConstElementPtr ctx6 = socket6->get("user-context"); + ASSERT_TRUE(ctx6); + ASSERT_EQ(1, ctx6->size()); + ASSERT_TRUE(ctx6->get("version")); + EXPECT_EQ("1", ctx6->get("version")->str()); + + // Check authentication comment. + const HttpAuthConfigPtr& auth = agent_ctx->getAuthConfig(); + ASSERT_TRUE(auth); + ConstElementPtr ctx7 = auth->getContext(); + ASSERT_TRUE(ctx7); + ASSERT_EQ(1, ctx7->size()); + ASSERT_TRUE(ctx7->get("comment")); + EXPECT_EQ("\"basic HTTP authentication\"", ctx7->get("comment")->str()); + + // Check basic HTTP authentication client comment. + const BasicHttpAuthConfigPtr& basic_auth = + boost::dynamic_pointer_cast<BasicHttpAuthConfig>(auth); + ASSERT_TRUE(basic_auth); + auto clients = basic_auth->getClientList(); + ASSERT_EQ(2, clients.size()); + ConstElementPtr ctx8 = clients.front().getContext(); + ASSERT_TRUE(ctx8); + ASSERT_EQ(1, ctx8->size()); + ASSERT_TRUE(ctx8->get("comment")); + EXPECT_EQ("\"foo is authorized\"", ctx8->get("comment")->str()); + + // Check basic HTTP authentication client user context. + ConstElementPtr ctx9 = clients.back().getContext(); + ASSERT_TRUE(ctx9); + ASSERT_EQ(1, ctx9->size()); + ASSERT_TRUE(ctx9->get("no password")); + EXPECT_EQ("true", ctx9->get("no password")->str()); +} + +// This test checks if a config with TLS parameters is parsed properly. +TEST_F(AgentParserTest, configParseTls) { + configParse(AGENT_CONFIGS[9], 0); + + CtrlAgentCfgContextPtr ctx = cfg_mgr_.getCtrlAgentCfgContext(); + ASSERT_TRUE(ctx); + EXPECT_EQ("my-ca", ctx->getTrustAnchor()); + EXPECT_EQ("my-cert", ctx->getCertFile()); + EXPECT_EQ("my-key", ctx->getKeyFile()); + EXPECT_FALSE(ctx->getCertRequired()); +} + +// This test checks that the config file with basic HTTP authentication +// using files can be loaded. +TEST_F(AgentParserTest, configParseAuthFile) { + configParse(AGENT_CONFIGS[10], 0); + CtrlAgentCfgContextPtr ctx = cfg_mgr_.getCtrlAgentCfgContext(); + const HttpAuthConfigPtr& auth = ctx->getAuthConfig(); + ASSERT_TRUE(auth); + const BasicHttpAuthConfigPtr& basic_auth = + boost::dynamic_pointer_cast<BasicHttpAuthConfig>(auth); + ASSERT_TRUE(basic_auth); + + // Check realm + EXPECT_EQ("foobar", basic_auth->getRealm()); + + // Check directory + EXPECT_EQ(std::string(CA_TEST_DATA_DIR), basic_auth->getDirectory()); + + // Check credentials + auto credentials = basic_auth->getCredentialMap(); + EXPECT_EQ(2, credentials.size()); + std::string user; + EXPECT_NO_THROW(user = credentials.at("a2VhdGVzdDpLZWFUZXN0")); + EXPECT_EQ("keatest", user); + EXPECT_NO_THROW(user = credentials.at("a2VhOnRlc3Q=")); + EXPECT_EQ("kea", user); + + // Check clients. + BasicHttpAuthConfig expected; + expected.setRealm("foobar"); + expected.setDirectory(std::string(CA_TEST_DATA_DIR)); + expected.add("", "hiddenu", "", "hiddenp"); + expected.add("", "", "", "hiddens", true); + EXPECT_EQ(expected.toElement()->str(), basic_auth->toElement()->str()); +} + +} // end of anonymous namespace |