2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-31 14:05:33 +00:00

[#1304] Checkpoint before regen

This commit is contained in:
Francis Dupont
2020-09-13 22:03:20 +02:00
parent 940599e958
commit f907a08345
23 changed files with 642 additions and 378 deletions

View File

@@ -10,17 +10,22 @@
"http-host": "127.0.0.1",
"http-port": 8000,
"basic-authentication-realm": "kea-control-agent",
// In basic HTTP authentication
"basic-authentications":
[
{
"comment": "admin is authorized",
"user": "admin",
"password": "1234"
}
],
// In authentication
"authentication":
{
"comment": "basic HTTP authentication",
// In basic HTTP authentication clients
"clients":
[
{
"comment": "admin is authorized",
"user": "admin",
"password": "1234"
}
]
},
// In control socket
"control-sockets":

View File

@@ -11,27 +11,35 @@
// 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",
// Optional authentication.
"authentication":
{
// Required authentication type. The only supported value is
// basic for the basic HTTP authentication.
"type": "basic",
// 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",
// An optional parameter is the basic HTTP authentication realm.
// Its default is "kea-control-agent".
"realm": "kea-control-agent",
// The user id must not be empty or contain the ':' character.
// It is a mandatory parameter.
"user": "admin",
// This list specifies the user ids and passwords to use for
// basic HTTP authentication. If empty or not present any client
// is authorized.
"clients":
[
// This specifies an authorized client.
{
"comment": "admin is authorized",
// If password is not specified an empty password is used.
"password": "1234"
}
],
// 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

View File

@@ -50,7 +50,15 @@ The following example demonstrates the basic CA configuration.
"Control-agent": {
"http-host": "10.20.30.40",
"http-port": 8000,
"basic-authentication-realm": "kea-control-agent",
"authentication": {
"type": "basic",
"realm": "kea-control-agent",
"clients": [
{
"user": "admin",
"password": "1234"
} ]
},
"control-sockets": {
"dhcp4": {
@@ -69,12 +77,6 @@ The following example demonstrates the basic CA configuration.
},
},
"basic-authentications": [
{
"user": "admin",
"password": "1234"
} ],
"hooks-libraries": [
{
"library": "/opt/local/control-agent-commands.so",
@@ -142,11 +144,15 @@ against not authorized uses of the control agent by local users. For the
protection against remote attackers HTTPS and reverse proxy of
:ref:`agent-secure-connection` provide a stronger security.
The ``basic-authentication-realm`` is used for error message when
the basic HTTP authentication is mandatory but the client is not
The authentication is described in the ``authentication`` block
with the mandatory ``type`` parameter which selects the authentication.
Currently only the basic HTTP authentication (type basic) is supported.
The ``realm`` authentication parameter is used for error message when
the basic HTTP authentication is required but the client is not
authorized.
When the ``basic-authentications`` list is configured and not empty
When the ``clients`` authentication list is configured and not empty
the basic HTTP authentication is required. Each element of the list
specifies a user id and a password. The user id is mandatory, must
be not empty and must not contain the colon (:) character. The

View File

@@ -203,9 +203,10 @@ ControlCharacterFill [^"\\]|\\{JSONEscapeSequence}
\"user-context\" {
switch(driver.ctx_) {
case ParserContext::AGENT:
case ParserContext::AUTHENTICATION;
case ParserContext::CLIENTS:
case ParserContext::SERVER:
case ParserContext::LOGGERS:
case ParserContext::CLIENTS:
return AgentParser::make_USER_CONTEXT(driver.loc_);
default:
return AgentParser::make_STRING("user-context", driver.loc_);
@@ -215,9 +216,10 @@ ControlCharacterFill [^"\\]|\\{JSONEscapeSequence}
\"comment\" {
switch(driver.ctx_) {
case ParserContext::AGENT:
case ParserContext::AUTHENTICATION;
case ParserContext::CLIENTS:
case ParserContext::SERVER:
case ParserContext::LOGGERS:
case ParserContext::CLIENTS:
return AgentParser::make_COMMENT(driver.loc_);
default:
return AgentParser::make_STRING("comment", driver.loc_);

View File

@@ -1269,7 +1269,7 @@ namespace isc { namespace agent {
#line 529 "agent_parser.yy"
{
// Add unique here
ctx.enter(ctx.NO_KEYWORD);
ctx.enter(ctx.NO_KEYWORDS);
}
#line 1275 "agent_parser.cc"
break;

View File

@@ -513,6 +513,9 @@ auth_params: auth_param
auth_param: auth_type
| realm
| clients
| comment
| user_context
| unknown_map_entry
;
auth_type: TYPE {
@@ -528,7 +531,7 @@ auth_type_value: BASIC { $$ = ElementPtr(new StringElement("basic", ctx.loc2pos(
realm: REALM {
// Add unique here
ctx.enter(ctx.NO_KEYWORD);
ctx.enter(ctx.NO_KEYWORDS);
} COLON STRING {
ElementPtr realm(new StringElement($4, ctx.loc2pos(@4)));
ctx.stack_.back()->set("realm", realm);

View File

@@ -10,6 +10,7 @@
#include <agent/simple_parser.h>
#include <cc/simple_parser.h>
#include <cc/command_interpreter.h>
#include <http/basic_auth_config.h>
#include <exceptions/exceptions.h>
using namespace isc::config;
@@ -21,15 +22,13 @@ namespace isc {
namespace agent {
CtrlAgentCfgContext::CtrlAgentCfgContext()
: http_host_(""), http_port_(0), basic_auth_realm_("") {
: http_host_(""), http_port_(0) {
}
CtrlAgentCfgContext::CtrlAgentCfgContext(const CtrlAgentCfgContext& orig)
: ConfigBase(), ctrl_sockets_(orig.ctrl_sockets_),
http_host_(orig.http_host_), http_port_(orig.http_port_),
basic_auth_realm_(orig.basic_auth_realm_),
hooks_config_(orig.hooks_config_),
basic_auth_config_(orig.basic_auth_config_) {
hooks_config_(orig.hooks_config_), auth_config_(orig.auth_config_) {
}
CtrlAgentCfgMgr::CtrlAgentCfgMgr()
@@ -53,8 +52,8 @@ CtrlAgentCfgMgr::getConfigSummary(const uint32_t /*selection*/) {
s << ctx->getControlSocketInfoSummary();
// Add something if authentication is required.
const isc::http::BasicHttpAuthConfig& auth = ctx->getBasicAuthConfig();
if (!auth.getClientList().empty()) {
const isc::http::HttpAuthConfigPtr& auth = ctx->getAuthConfig();
if (auth && !auth->empty()) {
s << ", requires basic HTTP authentication";
}
@@ -160,9 +159,10 @@ 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
// Set authentication
if (auth_config_) {
ca->set("authentication", auth_config_->toElement());
}
ca->set("hooks-libraries", hooks_config_.toElement());
// Set control-sockets
ElementPtr control_sockets = Element::createMap();
@@ -171,11 +171,6 @@ CtrlAgentCfgContext::toElement() const {
control_sockets->set(si->first, socket);
}
ca->set("control-sockets", control_sockets);
// Set basic HTTP authentication
const isc::http::BasicHttpAuthConfig& auth = basic_auth_config_;
if (!basic_auth_config_.getClientList().empty()) {
ca->set("basic-authentications", basic_auth_config_.toElement());
}
// Set Control-agent
ElementPtr result = Element::createMap();
result->set("Control-agent", ca);

View File

@@ -9,7 +9,7 @@
#include <cc/data.h>
#include <hooks/hooks_config.h>
#include <http/basic_auth_config.h>
#include <http/auth_config.h>
#include <process/d_cfg_mgr.h>
#include <boost/pointer_cast.hpp>
#include <map>
@@ -99,18 +99,22 @@ public:
return (http_port_);
}
/// @brief Sets basic-authentication-realm parameter
/// @brief Sets HTTP authentication configuration.
///
/// @param real Basic HTTP authentication realm
void setBasicAuthRealm(const std::string& realm) {
basic_auth_realm_ = realm;
/// @note Only the basic HTTP authentication is supported.
///
/// @param auth_config HTTP authentication configuration.
void setAuthConfig(const isc::http::HttpAuthConfigPtr& auth_config) {
auth_config_ = auth_config;
}
/// @brief Returns basic-authentication-realm parameter
/// @brief Returns HTTP authentication configuration
///
/// @return Basic HTTP authentication realm.
std::string getBasicAuthRealm() const {
return (basic_auth_realm_);
/// @note Only the basic HTTP authentication is supported.
///
/// @return HTTP authentication configuration.
const isc::http::HttpAuthConfigPtr& getAuthConfig() const {
return (auth_config_);
}
/// @brief Returns non-const reference to configured hooks libraries.
@@ -127,22 +131,6 @@ public:
return (hooks_config_);
}
/// @brief Returns non-const reference to configured basic HTTP
/// authentification clients.
///
/// @return non-const reference to configured basic auth clients.
isc::http::BasicHttpAuthConfig& getBasicAuthConfig() {
return (basic_auth_config_);
}
/// @brief Returns const reference to configured basic HTTP
/// authentification clients.
///
/// @return const reference to configured basic auth clients.
const isc::http::BasicHttpAuthConfig& getBasicAuthConfig() const {
return (basic_auth_config_);
}
/// @brief Unparse a configuration object
///
/// Returns an element which must parse into the same object, i.e.
@@ -178,14 +166,11 @@ 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_;
/// @brief Configured basic HTTP authentification clients.
isc::http::BasicHttpAuthConfig basic_auth_config_;
isc::http::HttpAuthConfigPtr auth_config_;
};
/// @brief Ctrl Agent Configuration Manager.

View File

@@ -13,7 +13,6 @@
#include <agent/ca_response_creator.h>
#include <cc/data.h>
#include <http/post_request_json.h>
#include <http/response_creator_auth.h>
#include <http/response_json.h>
#include <boost/pointer_cast.hpp>
#include <iostream>
@@ -81,11 +80,11 @@ createDynamicHttpResponse(const ConstHttpRequestPtr& request) {
if (cfgmgr) {
ctx = cfgmgr->getCtrlAgentCfgContext();
if (ctx) {
const BasicHttpAuthConfig& auth = ctx->getBasicAuthConfig();
const BasicHttpAuthMap& auth_map = auth.getCredentialMap();
// Check authentication.
http_response = checkAuth(*this, request, auth_map,
ctx->getBasicAuthRealm());
const HttpAuthConfigPtr& auth = ctx->getAuthConfig();
if (auth) {
// Check authentication.
http_response = auth->checkAuth(*this, request);
}
}
}
}

View File

@@ -96,6 +96,21 @@ ParserContext::loc2pos(isc::agent::location& loc)
return (isc::data::Element::Position(file, line, pos));
}
void
ParserContext::require(const std::string& name,
isc::data::Element::Position open_loc,
isc::data::Element::Position close_loc)
{
ConstElementPtr value = stack_.back()->get(name);
if (!value) {
isc_throw(ParseError,
"missing parameter '" << name << "' ("
<< stack_.back()->getPosition() << ") ["
<< contextName() << " map between "
<< open_loc << " and " << close_loc << "]");
}
}
void
ParserContext::enter(const LexerContext& ctx)
{

View File

@@ -147,6 +147,19 @@ public:
/// @return Position in format accepted by Element
isc::data::Element::Position loc2pos(isc::agent::location& loc);
/// @brief Check if a required parameter is present
///
/// Check if a required parameter is present in the map at the top
/// of the stack and raise an error when it is not.
///
/// @param name name of the parameter to check
/// @param open_loc location of the opening curly bracket
/// @param close_loc location of the closing curly bracket
/// @throw ParseError
void require(const std::string& name,
isc::data::Element::Position open_loc,
isc::data::Element::Position close_loc);
/// @brief Defines syntactic contexts for lexical tie-ins
typedef enum {
///< This one is used in pure JSON mode.

View File

@@ -11,6 +11,7 @@
#include <cc/dhcp_config_error.h>
#include <hooks/hooks_manager.h>
#include <hooks/hooks_parser.h>
#include <http/basic_auth_config.h>
#include <boost/foreach.hpp>
using namespace isc::data;
@@ -36,15 +37,20 @@ 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" },
{ "basic-authentication-realm", Element::string, "kea-control-agent" }
{ "http-host", Element::string, "127.0.0.1" },
{ "http-port", Element::integer, "8000" }
};
/// @brief This table defines default values for authentication.
const SimpleDefaults AgentSimpleParser::AUTH_DEFAULTS = {
{ "type", Element::string, "basic" },
{ "realm", Element::string, "kea-control-agent" }
};
/// @brief This table defines default values for control sockets.
///
const SimpleDefaults AgentSimpleParser::SOCKET_DEFAULTS = {
{ "socket-type", Element::string, "unix"}
{ "socket-type", Element::string, "unix" }
};
/// @}
@@ -59,6 +65,15 @@ size_t AgentSimpleParser::setAllDefaults(const isc::data::ElementPtr& global) {
// Set global defaults first.
cnt = setDefaults(global, AGENT_DEFAULTS);
// After set the defaults for authentication if it exists.
ConstElementPtr authentication = global->get("authentication");
if (authentication) {
ElementPtr auth = boost::const_pointer_cast<Element>(authentication);
if (auth) {
cnt += SimpleParser::setDefaults(auth, AUTH_DEFAULTS);
}
}
// Now set the defaults for control-sockets, if any.
ConstElementPtr sockets = global->get("control-sockets");
if (sockets) {
@@ -89,8 +104,6 @@ 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");
@@ -102,9 +115,13 @@ AgentSimpleParser::parse(const CtrlAgentCfgContextPtr& ctx,
}
// Basic HTTP authentications are third.
ConstElementPtr auth_config = config->get("basic-authentications");
ctx->getBasicAuthConfig().clear();
ctx->getBasicAuthConfig().parse(auth_config);
ConstElementPtr auth_config = config->get("authentications");
if (auth_config) {
using namespace isc::http;
BasicHttpAuthConfigPtr auth(new BasicHttpAuthConfig());
auth->parse(auth_config);
ctx->setAuthConfig(auth);
}
// User context can be done at anytime.
ConstElementPtr user_context = config->get("user-context");
@@ -113,7 +130,6 @@ AgentSimpleParser::parse(const CtrlAgentCfgContextPtr& ctx,
}
// Finally, let's get the hook libs!
using namespace isc::hooks;
HooksConfig& libraries = ctx->getHooksConfig();
ConstElementPtr hooks = config->get("hooks-libraries");

View File

@@ -1,4 +1,4 @@
// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2017-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
@@ -43,9 +43,10 @@ public:
// see simple_parser.cc for comments for those parameters
static const isc::data::SimpleDefaults AGENT_DEFAULTS;
static const isc::data::SimpleDefaults AUTH_DEFAULTS;
static const isc::data::SimpleDefaults SOCKET_DEFAULTS;
};
};
};
}
}
#endif

View File

@@ -10,7 +10,9 @@
#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_libraries.h>
#include <boost/pointer_cast.hpp>
#include <boost/scoped_ptr.hpp>
#include <gtest/gtest.h>
@@ -63,9 +65,6 @@ TEST(CtrlAgentCfgMgr, contextHttpParams) {
ctx.setHttpHost("alnitak");
EXPECT_EQ("alnitak", ctx.getHttpHost());
ctx.setBasicAuthRealm("foobar");
EXPECT_EQ("foobar", ctx.getBasicAuthRealm());
}
// Tests if context can store and retrieve control socket information.
@@ -126,15 +125,16 @@ TEST(CtrlAgentCfgMgr, contextSocketInfoCopy) {
EXPECT_NO_THROW(ctx.setHttpPort(12345));
EXPECT_NO_THROW(ctx.setHttpHost("bellatrix"));
EXPECT_NO_THROW(ctx.setBasicAuthRealm("foobar"));
HooksConfig& libs = ctx.getHooksConfig();
string exp_name("testlib1.so");
ConstElementPtr exp_param(new StringElement("myparam"));
libs.add(exp_name, exp_param);
BasicHttpAuthConfig& auth = ctx.getBasicAuthConfig();
auth.add("foo", "bar");
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());
@@ -144,7 +144,6 @@ TEST(CtrlAgentCfgMgr, contextSocketInfoCopy) {
// Now check the values returned
EXPECT_EQ(12345, copy->getHttpPort());
EXPECT_EQ("bellatrix", copy->getHttpHost());
EXPECT_EQ("foobar", copy->getBasicAuthRealm());
// Check socket info
ASSERT_TRUE(copy->getControlSocketInfo("d2"));
@@ -161,9 +160,10 @@ TEST(CtrlAgentCfgMgr, contextSocketInfoCopy) {
ASSERT_TRUE(libs2[0].second);
EXPECT_EQ(exp_param->str(), libs2[0].second->str());
// Check basic HTTP authentication
const BasicHttpAuthConfig& auth2 = copy->getBasicAuthConfig();
EXPECT_EQ(auth.toElement()->str(), auth2.toElement()->str());
// Check authentication
const HttpAuthConfigPtr& auth2 = copy->getAuthConfig();
ASSERT_TRUE(auth2);
EXPECT_EQ(auth->toElement()->str(), auth2->toElement()->str());
}
@@ -186,21 +186,24 @@ TEST(CtrlAgentCfgMgr, contextHookParams) {
EXPECT_EQ(libs.get(), stored_libs.get());
}
// Test if the context can store and retrieve basic HTTP authentication clients.
TEST(CtrlAgentCfgMgr, contextBasicAuth) {
// Test if the context can store and retrieve basic HTTP authentication
// configuration.
TEST(CtrlAgentCfgMgr, contextAuthConfig) {
CtrlAgentCfgContext ctx;
// By default there should be no authentication.
BasicHttpAuthConfig& auth = ctx.getBasicAuthConfig();
EXPECT_TRUE(auth.getClientList().empty());
EXPECT_FALSE(ctx.getAuthConfig());
BasicHttpAuthConfigPtr auth(new BasicHttpAuthConfig());
EXPECT_NO_THROW(ctx.setAuthConfig(auth));
auth.add("foo", "bar");
auth.add("test", "123\xa3");
auth->setRealm("foobar");
auth->add("foo", "bar");
auth->add("test", "123\xa3");
const BasicHttpAuthConfig& stored_auth = ctx.getBasicAuthConfig();
EXPECT_EQ(2, stored_auth.getClientList().size());
EXPECT_EQ(auth.toElement()->str(), stored_auth.toElement()->str());
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.
@@ -211,15 +214,13 @@ const char* AGENT_CONFIGS[] = {
// Configuration 1: http parameters only (no control sockets, not hooks)
"{ \"http-host\": \"betelgeuse\",\n"
" \"http-port\": 8001,\n"
" \"basic-authentication-realm\": \"foobar\"\n"
" \"http-port\": 8001\n"
"}",
// Configuration 2: http and 1 socket
"{\n"
" \"http-host\": \"betelgeuse\",\n"
" \"http-port\": 8001,\n"
" \"basic-authentication-realm\": \"foobar\",\n"
" \"control-sockets\": {\n"
" \"dhcp4\": {\n"
" \"socket-name\": \"/tmp/socket-v4\"\n"
@@ -231,7 +232,6 @@ const char* AGENT_CONFIGS[] = {
"{\n"
" \"http-host\": \"betelgeuse\",\n"
" \"http-port\": 8001,\n"
" \"basic-authentication-realm\": \"foobar\",\n"
" \"control-sockets\": {\n"
" \"dhcp4\": {\n"
" \"socket-name\": \"/tmp/socket-v4\"\n"
@@ -251,7 +251,6 @@ const char* AGENT_CONFIGS[] = {
"{\n"
" \"http-host\": \"betelgeuse\",\n"
" \"http-port\": 8001,\n"
" \"basic-authentication-realm\": \"foobar\",\n"
" \"control-sockets\": {\n"
" \"dhcp4\": {\n"
" \"socket-name\": \"/tmp/socket-v4\"\n"
@@ -271,7 +270,6 @@ const char* AGENT_CONFIGS[] = {
"{\n"
" \"http-host\": \"betelgeuse\",\n"
" \"http-port\": 8001,\n"
" \"basic-authentication-realm\": \"foobar\",\n"
" \"control-sockets\": {\n"
" \"d2\": {\n"
" \"socket-name\": \"/tmp/socket-d2\"\n"
@@ -283,7 +281,6 @@ const char* AGENT_CONFIGS[] = {
"{\n"
" \"http-host\": \"betelgeuse\",\n"
" \"http-port\": 8001,\n"
" \"basic-authentication-realm\": \"foobar\",\n"
" \"control-sockets\": {\n"
" \"dhcp6\": {\n"
" \"socket-name\": \"/tmp/socket-v6\"\n"
@@ -291,25 +288,28 @@ const char* AGENT_CONFIGS[] = {
" }\n"
"}",
// Configuration 7: http, 1 socket and basic authentication
// Configuration 7: http, 1 socket and authentication
"{\n"
" \"http-host\": \"betelgeuse\",\n"
" \"http-port\": 8001,\n"
" \"basic-authentication-realm\": \"foobar\",\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"
" \"basic-authentications\": ["
" {"
" \"user\": \"foo\",\n"
" \"password\": \"bar\"\n"
" },{\n"
" \"user\": \"test\",\n"
" \"password\": \"123\\u00a3\"\n"
" }\n"
" ]\n"
" }\n"
"}",
// Configuration 8: http and 2 sockets with user contexts and comments
@@ -317,7 +317,21 @@ const char* AGENT_CONFIGS[] = {
" \"user-context\": { \"comment\": \"Indirect comment\" },\n"
" \"http-host\": \"betelgeuse\",\n"
" \"http-port\": 8001,\n"
" \"basic-authentication-realm\": \"foobar\",\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"
@@ -327,17 +341,7 @@ const char* AGENT_CONFIGS[] = {
" \"socket-name\": \"/tmp/socket-v6\",\n"
" \"user-context\": { \"version\": 1 }\n"
" }\n"
" },\n"
" \"basic-authentications\": ["
" {"
" \"comment\": \"foo is authorized\",\n"
" \"user\": \"foo\",\n"
" \"password\": \"bar\"\n"
" },{\n"
" \"user\": \"test\",\n"
" \"user-context\": { \"no password\": true }\n"
" }\n"
" ]\n"
" }\n"
"}"
};
@@ -389,7 +393,6 @@ TEST_F(AgentParserTest, configParseHttpOnly) {
ASSERT_TRUE(ctx);
EXPECT_EQ("betelgeuse", ctx->getHttpHost());
EXPECT_EQ(8001, ctx->getHttpPort());
EXPECT_EQ("foobar", ctx->getBasicAuthRealm());
}
// Tests if a single socket can be configured. BTW this test also checks
@@ -482,13 +485,20 @@ TEST_F(AgentParserTest, configParseHooks) {
// This test checks that the config file with basic HTTP authentication can be
// loaded.
TEST_F(AgentParserTest, configParseBasicAuth) {
TEST_F(AgentParserTest, configParseAuth) {
configParse(AGENT_CONFIGS[7], 0);
CtrlAgentCfgContextPtr ctx = cfg_mgr_.getCtrlAgentCfgContext();
const BasicHttpAuthConfig& auth = ctx->getBasicAuthConfig();
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 credentails
auto credentials = auth.getCredentialMap();
auto credentials = basic_auth->getCredentialMap();
EXPECT_EQ(2, credentials.size());
std::string user;
EXPECT_NO_THROW(user = credentials.at("Zm9vOmJhcg=="));
@@ -498,9 +508,10 @@ TEST_F(AgentParserTest, configParseBasicAuth) {
// Check clients.
BasicHttpAuthConfig expected;
expected.setRealm("foobar");
expected.add("foo", "bar");
expected.add("test", "123\xa3");
EXPECT_EQ(expected.toElement()->str(), auth.toElement()->str());
EXPECT_EQ(expected.toElement()->str(), basic_auth->toElement()->str());
}
// This test checks comments.
@@ -538,22 +549,33 @@ TEST_F(AgentParserTest, comments) {
ASSERT_TRUE(ctx6->get("version"));
EXPECT_EQ("1", ctx6->get("version")->str());
// Check basic HTTP authentication comment.
const BasicHttpAuthConfig& auth = agent_ctx->getBasicAuthConfig();
auto clients = auth.getClientList();
ASSERT_EQ(2, clients.size());
ConstElementPtr ctx7 = clients.front().getContext();
// 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("\"foo is authorized\"", ctx7->get("comment")->str());
EXPECT_EQ("\"basic HTTP authentication\"", ctx7->get("comment")->str());
// Check basic HTTP authentication user context.
ConstElementPtr ctx8 = clients.back().getContext();
// 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("no password"));
EXPECT_EQ("true", ctx8->get("no password")->str());
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());
}
} // end of anonymous namespace

View File

@@ -10,6 +10,7 @@
#include <agent/ca_command_mgr.h>
#include <agent/ca_response_creator.h>
#include <cc/command_interpreter.h>
#include <http/basic_auth_config.h>
#include <http/post_request.h>
#include <http/post_request_json.h>
#include <http/response_json.h>
@@ -250,9 +251,10 @@ TEST_F(CtrlAgentResponseCreatorTest, noAuth) {
// Require authentication.
CtrlAgentCfgContextPtr ctx = getCtrlAgentCfgContext();
ASSERT_TRUE(ctx);
ctx->setBasicAuthRealm("ISC.ORG");
BasicHttpAuthConfig& auth = ctx->getBasicAuthConfig();
auth.add("foo", "bar");
BasicHttpAuthConfigPtr auth(new BasicHttpAuthConfig());
ASSERT_NO_THROW(ctx->setAuthConfig(auth));
auth->setRealm("ISC.ORG");
auth->add("foo", "bar");
HttpResponsePtr response;
ASSERT_NO_THROW(response = response_creator_.createHttpResponse(request_));
@@ -290,8 +292,11 @@ TEST_F(CtrlAgentResponseCreatorTest, basicAuth) {
// Require authentication.
CtrlAgentCfgContextPtr ctx = getCtrlAgentCfgContext();
ASSERT_TRUE(ctx);
BasicHttpAuthConfig& auth = ctx->getBasicAuthConfig();
auth.add("foo", "bar");
BasicHttpAuthConfigPtr auth(new BasicHttpAuthConfig());
ASSERT_NO_THROW(ctx->setAuthConfig(auth));
// In fact the realm is used only on errors... set it anyway.
auth->setRealm("ISC.ORG");
auth->add("foo", "bar");
HttpResponsePtr response;
ASSERT_NO_THROW(response = response_creator_.createHttpResponse(request_));

View File

@@ -35,11 +35,11 @@ libkea_http_la_SOURCES += response.cc response.h
libkea_http_la_SOURCES += response_parser.cc response_parser.h
libkea_http_la_SOURCES += response_context.h
libkea_http_la_SOURCES += response_creator.cc response_creator.h
libkea_http_la_SOURCES += response_creator_auth.cc response_creator_auth.h
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 += auth_config.h
libkea_http_la_SOURCES += basic_auth_config.cc basic_auth_config.h
libkea_http_la_CXXFLAGS = $(AM_CXXFLAGS)
@@ -91,6 +91,7 @@ endif
# Specify the headers for copying into the installation directory tree.
libkea_http_includedir = $(pkgincludedir)/http
libkea_http_include_HEADERS = \
auth_config.h \
basic_auth.h \
basic_auth_config.h \
client.h \
@@ -115,7 +116,6 @@ 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 \

View File

@@ -0,0 +1,84 @@
// 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_AUTH_CONFIG_H
#define HTTP_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/request.h>
#include <http/response_creator.h>
#include <http/response_json.h>
namespace isc {
namespace http {
/// @brief Base type of HTTP authentication configuration.
class HttpAuthConfig : public isc::data::UserContext,
public isc::data::CfgToElement {
public:
/// @brief Destructor.
virtual ~HttpAuthConfig() { }
/// @brief Set the realm.
///
/// @param realm New realm.
void setRealm(const std::string& realm) {
realm_ = realm;
}
/// @brief Returns the realm.
///
/// @return The basic HTTP authentication realm.
const std::string& getRealm() const {
return (realm_);
}
/// @brief Empty predicate.
/// @return true if the configuration is empty so authentication
/// is not required.
virtual bool empty() const = 0;
/// @brief Clear configuration.
virtual void clear() = 0;
/// @brief Parses HTTP authentication configuration.
///
/// @param config Element holding the basic HTTP authentication
/// configuration to be parsed.
/// @throw DhcpConfigError when the configuration is invalid.
virtual void parse(const isc::data::ConstElementPtr& config) = 0;
/// @brief Unparses HTTP authentication configuration.
///
/// @return A pointer to unparsed HTTP authentication configuration.
virtual isc::data::ElementPtr toElement() const = 0;
/// @brief Validate HTTP request.
///
/// @param creator The HTTP response creator.
/// @param request The HTTP request to validate.
/// @return Error HTTP response if validation failed, null otherwise.
virtual isc::http::HttpResponseJsonPtr
checkAuth(const isc::http::HttpResponseCreator& creator,
const isc::http::ConstHttpRequestPtr& request) const = 0;
private:
/// @brief The realm.
std::string realm_;
};
/// @brief Type of shared pointers to HTTP authentication configuration.
typedef boost::shared_ptr<HttpAuthConfig> HttpAuthConfigPtr;
} // end of namespace isc::http
} // end of namespace isc
#endif // endif HTTP_AUTH_CONFIG_H

View File

@@ -7,10 +7,13 @@
#include <config.h>
#include <http/basic_auth_config.h>
#include <http/http_log.h>
#include <util/strutil.h>
using namespace isc;
using namespace isc::data;
using namespace isc::dhcp;
using namespace isc::util;
using namespace std;
namespace isc {
@@ -56,13 +59,30 @@ BasicHttpAuthConfig::clear() {
map_.clear();
}
bool
BasicHttpAuthConfig::empty() const {
return (map_.empty());
}
ElementPtr
BasicHttpAuthConfig::toElement() const {
ElementPtr result = Element::createList();
ElementPtr result = Element::createMap();
// Set user-context
contextToElement(result);
// Set type
result->set("type", Element::create(string("basic")));
// Set realm
result->set("realm", Element::create(getRealm()));
// Set clients
ElementPtr clients = Element::createList();
for (auto client : list_) {
result->add(client.toElement());
clients->add(client.toElement());
}
result->set("clients", clients);
return (result);
}
@@ -72,21 +92,68 @@ BasicHttpAuthConfig::parse(const ConstElementPtr& config) {
if (!config) {
return;
}
if (config->getType() != Element::list) {
isc_throw(DhcpConfigError, "basic-authentications must be a list ("
if (config->getType() != Element::map) {
isc_throw(DhcpConfigError, "authentication must be a map ("
<< config->getPosition() << ")");
}
for (auto client : config->listValue()) {
// Get and verify the type.
ConstElementPtr type = config->get("type");
if (!type) {
isc_throw(DhcpConfigError, "type is required in authentication ("
<< config->getPosition() << ")");
}
if (type->getType() != Element::string) {
isc_throw(DhcpConfigError, "type is must be a string ("
<< type->getPosition() << ")");
}
if (type->stringValue() != "basic") {
isc_throw(DhcpConfigError, "only basic HTTP authentication is "
<< "supported: type is '" << type->stringValue()
<< "' not 'basic' (" << type->getPosition() << ")");
}
// Get the realm.
ConstElementPtr realm = config->get("realm");
if (realm) {
if (realm->getType() != Element::string) {
isc_throw(DhcpConfigError, "realm is must be a string ("
<< realm->getPosition() << ")");
}
setRealm(realm->stringValue());
}
// Get user context
ConstElementPtr user_context_cfg = config->get("user-context");
if (user_context_cfg) {
if (user_context_cfg->getType() != Element::map) {
isc_throw(DhcpConfigError, "user-context must be a map ("
<< user_context_cfg->getPosition() << ")");
}
setContext(user_context_cfg);
}
// Get clients.
ConstElementPtr clients = config->get("clients");
if (!clients) {
return;
}
if (clients->getType() != Element::list) {
isc_throw(DhcpConfigError, "clients must be a list ("
<< clients->getPosition() << ")");
}
// Iterate on clients.
for (auto client : clients->listValue()) {
if (client->getType() != Element::map) {
isc_throw(DhcpConfigError, "basic-authentications items must be "
<< "maps (" << client->getPosition() << ")");
isc_throw(DhcpConfigError, "clients 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 ("
isc_throw(DhcpConfigError, "user is required in clients items ("
<< client->getPosition() << ")");
}
if (user_cfg->getType() != Element::string) {
@@ -133,5 +200,60 @@ BasicHttpAuthConfig::parse(const ConstElementPtr& config) {
}
}
HttpResponseJsonPtr
BasicHttpAuthConfig::checkAuth(const HttpResponseCreator& creator,
const ConstHttpRequestPtr& request) const {
const BasicHttpAuthMap& credentials = getCredentialMap();
bool authentic = false;
if (credentials.empty()) {
authentic = true;
} else try {
string value = request->getHeaderValue("Authorization");
// Trim space characters.
value = str::trim(value);
if (value.size() < 8) {
isc_throw(BadValue, "header content is too short");
}
// Get the authentication scheme which must be "basic".
string scheme = value.substr(0, 5);
str::lowercase(scheme);
if (scheme != "basic") {
isc_throw(BadValue, "not basic authentication");
}
// Skip the authentication scheme name and space characters.
value = value.substr(5);
value = str::trim(value);
// Verify the credential is in the list.
const auto it = credentials.find(value);
if (it != credentials.end()) {
LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_BASIC,
HTTP_CLIENT_REQUEST_AUTHORIZED)
.arg(it->second);
authentic = true;
} else {
LOG_INFO(http_logger, HTTP_CLIENT_REQUEST_NOT_AUTHORIZED);
authentic = false;
}
} catch (const HttpMessageNonExistingHeader&) {
LOG_INFO(http_logger, HTTP_CLIENT_REQUEST_NO_AUTH_HEADER);
} catch (const BadValue& ex) {
LOG_INFO(http_logger, HTTP_CLIENT_REQUEST_BAD_AUTH_HEADER)
.arg(ex.what());
}
if (authentic) {
return (HttpResponseJsonPtr());
}
const string& realm = getRealm();
const string& scheme = "Basic";
HttpResponsePtr response =
creator.createStockHttpResponse(request, HttpStatusCode::UNAUTHORIZED);
response->reset();
response->context()->headers_.push_back(
HttpHeaderContext("WWW-Authenticate",
scheme + " realm=\"" + realm + "\""));
response->finalize();
return (boost::dynamic_pointer_cast<HttpResponseJson>(response));
}
} // end of namespace isc::http
} // end of namespace isc

View File

@@ -7,10 +7,7 @@
#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/auth_config.h>
#include <http/basic_auth.h>
#include <list>
#include <unordered_map>
@@ -67,9 +64,12 @@ private:
typedef std::list<BasicHttpAuthClient> BasicHttpAuthClientList;
/// @brief Basic HTTP authentication configuration.
class BasicHttpAuthConfig : public isc::data::CfgToElement {
class BasicHttpAuthConfig : public HttpAuthConfig {
public:
/// @brief Destructor.
virtual ~BasicHttpAuthConfig() { }
/// @brief Add a client configuration.
///
/// @param user User id
@@ -80,8 +80,13 @@ public:
const std::string& password,
const isc::data::ConstElementPtr& user_context = isc::data::ConstElementPtr());
/// @brief Empty predicate.
/// @return true if the configuration is empty so authentication
/// is not required.
virtual bool empty() const;
/// @brief Clear configuration.
void clear();
virtual void clear();
/// @brief Returns the list of client configuration.
///
@@ -109,6 +114,15 @@ public:
/// @return A pointer to unparsed basic HTTP authentication configuration.
virtual isc::data::ElementPtr toElement() const;
/// @brief Validate HTTP request.
///
/// @param creator The HTTP response creator.
/// @param request The HTTP request to validate.
/// @return Error HTTP response if validation failed, null otherwise.
virtual isc::http::HttpResponseJsonPtr
checkAuth(const isc::http::HttpResponseCreator& creator,
const isc::http::ConstHttpRequestPtr& request) const;
private:
/// @brief The list of basic HTTP authentication client configuration.
@@ -118,6 +132,9 @@ private:
BasicHttpAuthMap map_;
};
/// @brief Type of shared pointers to basic HTTP authentication configuration.
typedef boost::shared_ptr<BasicHttpAuthConfig> BasicHttpAuthConfigPtr;
} // end of namespace isc::http
} // end of namespace isc

View File

@@ -1,75 +0,0 @@
// 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/http_log.h>
#include <http/response_creator_auth.h>
#include <util/strutil.h>
using namespace isc;
using namespace isc::util;
using namespace std;
namespace isc {
namespace http {
HttpResponseJsonPtr checkAuth(const HttpResponseCreator& creator,
const ConstHttpRequestPtr& request,
const BasicHttpAuthMap& credentials,
const std::string& realm) {
bool authentic = false;
if (credentials.empty()) {
authentic = true;
} else try {
string value = request->getHeaderValue("Authorization");
// Trim space characters.
value = str::trim(value);
if (value.size() < 8) {
isc_throw(BadValue, "header content is too short");
}
// Get the authentication scheme which must be "basic".
string scheme = value.substr(0, 5);
str::lowercase(scheme);
if (scheme != "basic") {
isc_throw(BadValue, "not basic authentication");
}
// Skip the authentication scheme name and space characters.
value = value.substr(5);
value = str::trim(value);
// Verify the credential is in the list.
const auto it = credentials.find(value);
if (it != credentials.end()) {
LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_BASIC,
HTTP_CLIENT_REQUEST_AUTHORIZED)
.arg(it->second);
authentic = true;
} else {
LOG_INFO(http_logger, HTTP_CLIENT_REQUEST_NOT_AUTHORIZED);
authentic = false;
}
} catch (const HttpMessageNonExistingHeader&) {
LOG_INFO(http_logger, HTTP_CLIENT_REQUEST_NO_AUTH_HEADER);
} catch (const BadValue& ex) {
LOG_INFO(http_logger, HTTP_CLIENT_REQUEST_BAD_AUTH_HEADER)
.arg(ex.what());
}
if (authentic) {
return (HttpResponseJsonPtr());
}
string scheme = "Basic";
HttpResponsePtr response =
creator.createStockHttpResponse(request, HttpStatusCode::UNAUTHORIZED);
response->reset();
response->context()->headers_.push_back(
HttpHeaderContext("WWW-Authenticate",
scheme + " realm=\"" + realm + "\""));
response->finalize();
return (boost::dynamic_pointer_cast<HttpResponseJson>(response));
}
} // end of namespace isc::http
} // end of namespace isc

View File

@@ -1,38 +0,0 @@
// 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_RESPONSE_CREATOR_AUTH_H
#define HTTP_RESPONSE_CREATOR_AUTH_H
#include <http/basic_auth_config.h>
#include <http/response_creator.h>
#include <http/response_json.h>
#include <string.h>
#include <unordered_map>
namespace isc {
namespace http {
/// @brief Validate authentication.
///
/// Currently it only validates basic HTTP authentication.
/// Empty credentials map means that basic HTTP authentication is
/// not required i.e. all requests validate.
///
/// @param creator The HTTP response creator.
/// @param request The HTTP request to validate.
/// @param credentials A map of all allowed credentials.
/// @param realm Realm name.
/// @return Error HTTP response if validation failed, null otherwise.
HttpResponseJsonPtr checkAuth(const HttpResponseCreator& creator,
const ConstHttpRequestPtr& request,
const BasicHttpAuthMap& credentials,
const std::string& realm);
} // end of namespace isc::http
} // end of namespace isc
#endif // endif HTTP_RESPONSE_CREATOR_AUTH_H

View File

@@ -44,15 +44,26 @@ TEST(BasicHttpAuthConfigTest, basic) {
BasicHttpAuthConfig config;
// Initial configuration is empty.
EXPECT_TRUE(config.empty());
EXPECT_TRUE(config.getRealm().empty());
EXPECT_TRUE(config.getClientList().empty());
EXPECT_TRUE(config.getCredentialMap().empty());
// Set the realm and user context.
EXPECT_NO_THROW(config.setRealm("my-realm"));
EXPECT_EQ("my-realm", config.getRealm());
ConstElementPtr horse = Element::fromJSON("{ \"value\": \"a horse\" }");
EXPECT_NO_THROW(config.setContext(horse));
EXPECT_TRUE(horse->equals(*config.getContext()));
// Add rejects user id with embedded ':'.
EXPECT_THROW(config.add("foo:", "bar"), BadValue);
// Add a client.
EXPECT_TRUE(config.empty());
ConstElementPtr ctx = Element::fromJSON("{ \"foo\": \"bar\" }");
EXPECT_NO_THROW(config.add("foo", "bar", ctx));
EXPECT_FALSE(config.empty());
// Check the client.
ASSERT_EQ(1, config.getClientList().size());
@@ -68,12 +79,17 @@ TEST(BasicHttpAuthConfigTest, basic) {
EXPECT_EQ("foo", user);
// Check toElement.
ElementPtr expected = Element::createList();
ElementPtr expected = Element::createMap();
ElementPtr clients = 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);
clients->add(elem);
expected->set("type", Element::create(string("basic")));
expected->set("realm", Element::create(string("my-realm")));
expected->set("user-context", horse);
expected->set("clients", clients);
runToElementTest<BasicHttpAuthConfig>(expected, config);
// Add a second client and test it.
@@ -85,7 +101,8 @@ TEST(BasicHttpAuthConfigTest, basic) {
// Check clear.
config.clear();
expected = Element::createList();
EXPECT_TRUE(config.empty());
expected->set("clients", Element::createList());
runToElementTest<BasicHttpAuthConfig>(expected, config);
// Add clients again.
@@ -96,8 +113,10 @@ TEST(BasicHttpAuthConfigTest, basic) {
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);
clients = Element::createList();
clients->add(elem0);
clients->add(elem);
expected->set("clients", clients);
runToElementTest<BasicHttpAuthConfig>(expected, config);
}
@@ -108,35 +127,83 @@ TEST(BasicHttpAuthConfigTest, parse) {
// No config is accepted.
EXPECT_NO_THROW(config.parse(cfg));
EXPECT_TRUE(config.empty());
EXPECT_TRUE(config.getClientList().empty());
EXPECT_TRUE(config.getCredentialMap().empty());
runToElementTest<BasicHttpAuthConfig>(Element::createList(), config);
ElementPtr expected = Element::createMap();
expected->set("type", Element::create(string("basic")));
expected->set("realm", Element::create(string("")));
expected->set("clients", Element::createList());
runToElementTest<BasicHttpAuthConfig>(expected, config);
// The config must be a list.
// The config must be a map.
cfg = Element::createList();
EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
"authentication must be a map (:0:0)");
// The type must be present.
cfg = Element::createMap();
EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
"basic-authentications must be a list (:0:0)");
"type is required in authentication (:0:0)");
// The type must be a string.
cfg->set("type", Element::create(true));
EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
"type is must be a string (:0:0)");
// The type must be basic.
cfg->set("type", Element::create(string("foobar")));
string errmsg = "only basic HTTP authentication is supported: type is ";
errmsg += "'foobar' not 'basic' (:0:0)";
EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError, errmsg);
cfg->set("type", Element::create(string("basic")));
EXPECT_NO_THROW(config.parse(cfg));
// The realm must be a string.
cfg->set("realm", Element::createList());
EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
"realm is must be a string (:0:0)");
cfg->set("realm", Element::create(string("my-realm")));
EXPECT_NO_THROW(config.parse(cfg));
// The user context must be a map.
ElementPtr ctx = Element::createList();
cfg->set("user-context", ctx);
EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
"user-context must be a map (:0:0)");
ctx = Element::fromJSON("{ \"value\": \"a horse\" }");
cfg->set("user-context", ctx);
EXPECT_NO_THROW(config.parse(cfg));
// Clients must be a list.
ElementPtr clients_cfg = Element::createMap();
cfg->set("clients", clients_cfg);
EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
"clients must be a list (:0:0)");
// The client config must be a map.
cfg = Element::createList();
clients_cfg = Element::createList();
ElementPtr client_cfg = Element::createList();
cfg->add(client_cfg);
clients_cfg->add(client_cfg);
cfg->set("clients", clients_cfg);
EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
"basic-authentications items must be maps (:0:0)");
"clients 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);
clients_cfg = Element::createList();
clients_cfg->add(client_cfg);
cfg->set("clients", clients_cfg);
EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
"user is required in basic-authentications items (:0:0)");
"user is required in clients 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);
clients_cfg = Element::createList();
clients_cfg->add(client_cfg);
cfg->set("clients", clients_cfg);
EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
"user must be a string (:0:0)");
@@ -144,8 +211,9 @@ TEST(BasicHttpAuthConfigTest, parse) {
user_cfg = Element::create(string(""));
client_cfg = Element::createMap();
client_cfg->set("user", user_cfg);
cfg = Element::createList();
cfg->add(client_cfg);
clients_cfg = Element::createList();
clients_cfg->add(client_cfg);
cfg->set("clients", clients_cfg);
EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
"user must be not be empty (:0:0)");
@@ -153,8 +221,9 @@ TEST(BasicHttpAuthConfigTest, parse) {
user_cfg = Element::create(string("foo:bar"));
client_cfg = Element::createMap();
client_cfg->set("user", user_cfg);
cfg = Element::createList();
cfg->add(client_cfg);
clients_cfg = Element::createList();
clients_cfg->add(client_cfg);
cfg->set("clients", clients_cfg);
EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
"user must not contain a ':': 'foo:bar' (:0:0)");
@@ -162,8 +231,9 @@ TEST(BasicHttpAuthConfigTest, parse) {
user_cfg = Element::create(string("foo"));
client_cfg = Element::createMap();
client_cfg->set("user", user_cfg);
cfg = Element::createList();
cfg->add(client_cfg);
clients_cfg = Element::createList();
clients_cfg->add(client_cfg);
cfg->set("clients", clients_cfg);
EXPECT_NO_THROW(config.parse(cfg));
ASSERT_EQ(1, config.getClientList().size());
EXPECT_EQ("", config.getClientList().front().getPassword());
@@ -174,8 +244,9 @@ TEST(BasicHttpAuthConfigTest, parse) {
client_cfg = Element::createMap();
client_cfg->set("user", user_cfg);
client_cfg->set("password", password_cfg);
cfg = Element::createList();
cfg->add(client_cfg);
clients_cfg = Element::createList();
clients_cfg->add(client_cfg);
cfg->set("clients", clients_cfg);
EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
"password must be a string (:0:0)");
@@ -184,8 +255,9 @@ TEST(BasicHttpAuthConfigTest, parse) {
client_cfg = Element::createMap();
client_cfg->set("user", user_cfg);
client_cfg->set("password", password_cfg);
cfg = Element::createList();
cfg->add(client_cfg);
clients_cfg = Element::createList();
clients_cfg->add(client_cfg);
cfg->set("clients", clients_cfg);
EXPECT_NO_THROW(config.parse(cfg));
ASSERT_EQ(1, config.getClientList().size());
EXPECT_EQ("", config.getClientList().front().getPassword());
@@ -193,13 +265,14 @@ TEST(BasicHttpAuthConfigTest, parse) {
// User context must be a map.
password_cfg = Element::create(string("bar"));
ElementPtr ctx = Element::createList();
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);
clients_cfg = Element::createList();
clients_cfg->add(client_cfg);
cfg->set("clients", clients_cfg);
EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
"user-context must be a map (:0:0)");
@@ -209,8 +282,9 @@ TEST(BasicHttpAuthConfigTest, parse) {
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);
clients_cfg = Element::createList();
clients_cfg->add(client_cfg);
cfg->set("clients", clients_cfg);
EXPECT_NO_THROW(config.parse(cfg));
runToElementTest<BasicHttpAuthConfig>(cfg, config);
}

View File

@@ -6,11 +6,11 @@
#include <config.h>
#include <http/basic_auth.h>
#include <http/basic_auth_config.h>
#include <http/http_types.h>
#include <http/request.h>
#include <http/response.h>
#include <http/response_creator.h>
#include <http/response_creator_auth.h>
#include <http/response_json.h>
#include <http/tests/response_test.h>
#include <testutils/log_utils.h>
@@ -140,13 +140,14 @@ class HttpResponseCreatorAuthTest : public LogContentTest { };
// This test verifies that missing required authentication header gives
// unauthorized error.
TEST_F(HttpResponseCreatorAuthTest, noAuth) {
// Create credentials.
BasicHttpAuthPtr basic_auth;
EXPECT_NO_THROW(basic_auth.reset(new BasicHttpAuth("test", "123\xa3")));
EXPECT_EQ("dGVzdDoxMjPCow==", basic_auth->getCredential());
BasicHttpAuthMap credentials;
credentials[basic_auth->getCredential()] = "test";
string realm = "ISC.ORG";
// Create basic HTTP authentication configuration.
BasicHttpAuthConfigPtr auth_config(new BasicHttpAuthConfig());
EXPECT_NO_THROW(auth_config->add("test", "123\xa3"));
const BasicHttpAuthMap& credentials = auth_config->getCredentialMap();
auto cred = credentials.find("dGVzdDoxMjPCow==");
EXPECT_NE(cred, credentials.end());
EXPECT_EQ(cred->second, "test");
auth_config->setRealm("ISC.ORG");
// Create request and finalize it.
HttpRequestPtr request(new HttpRequest());
@@ -158,7 +159,7 @@ TEST_F(HttpResponseCreatorAuthTest, noAuth) {
HttpResponsePtr response;
TestHttpResponseCreatorPtr creator(new TestHttpResponseCreator());;
ASSERT_NO_THROW(response = checkAuth(*creator, request, credentials, realm));
ASSERT_NO_THROW(response = auth_config->checkAuth(*creator, request));
ASSERT_TRUE(response);
EXPECT_EQ("HTTP/1.0 401 Unauthorized\r\n"
@@ -176,13 +177,14 @@ TEST_F(HttpResponseCreatorAuthTest, noAuth) {
// This test verifies that too short authentication header is rejected.
TEST_F(HttpResponseCreatorAuthTest, authTooShort) {
// Create credentials.
BasicHttpAuthPtr basic_auth;
EXPECT_NO_THROW(basic_auth.reset(new BasicHttpAuth("test", "123\xa3")));
EXPECT_EQ("dGVzdDoxMjPCow==", basic_auth->getCredential());
BasicHttpAuthMap credentials;
credentials[basic_auth->getCredential()] = "test";
string realm = "ISC.ORG";
// Create basic HTTP authentication configuration.
BasicHttpAuthConfigPtr auth_config(new BasicHttpAuthConfig());
EXPECT_NO_THROW(auth_config->add("test", "123\xa3"));
const BasicHttpAuthMap& credentials = auth_config->getCredentialMap();
auto cred = credentials.find("dGVzdDoxMjPCow==");
EXPECT_NE(cred, credentials.end());
EXPECT_EQ(cred->second, "test");
auth_config->setRealm("ISC.ORG");
// Create request and finalize it.
HttpRequestPtr request(new HttpRequest());
@@ -196,7 +198,7 @@ TEST_F(HttpResponseCreatorAuthTest, authTooShort) {
HttpResponsePtr response;
TestHttpResponseCreatorPtr creator(new TestHttpResponseCreator());;
ASSERT_NO_THROW(response = checkAuth(*creator, request, credentials, realm));
ASSERT_NO_THROW(response = auth_config->checkAuth(*creator, request));
ASSERT_TRUE(response);
EXPECT_EQ("HTTP/1.0 401 Unauthorized\r\n"
@@ -215,13 +217,14 @@ TEST_F(HttpResponseCreatorAuthTest, authTooShort) {
// This test verifies that another authentication schema is rejected.
TEST_F(HttpResponseCreatorAuthTest, badScheme) {
// Create credentials.
BasicHttpAuthPtr basic_auth;
EXPECT_NO_THROW(basic_auth.reset(new BasicHttpAuth("test", "123\xa3")));
EXPECT_EQ("dGVzdDoxMjPCow==", basic_auth->getCredential());
BasicHttpAuthMap credentials;
credentials[basic_auth->getCredential()] = "test";
string realm = "ISC.ORG";
// Create basic HTTP authentication configuration.
BasicHttpAuthConfigPtr auth_config(new BasicHttpAuthConfig());
EXPECT_NO_THROW(auth_config->add("test", "123\xa3"));
const BasicHttpAuthMap& credentials = auth_config->getCredentialMap();
auto cred = credentials.find("dGVzdDoxMjPCow==");
EXPECT_NE(cred, credentials.end());
EXPECT_EQ(cred->second, "test");
auth_config->setRealm("ISC.ORG");
// Create request and finalize it.
HttpRequestPtr request(new HttpRequest());
@@ -235,7 +238,7 @@ TEST_F(HttpResponseCreatorAuthTest, badScheme) {
HttpResponsePtr response;
TestHttpResponseCreatorPtr creator(new TestHttpResponseCreator());;
ASSERT_NO_THROW(response = checkAuth(*creator, request, credentials, realm));
ASSERT_NO_THROW(response = auth_config->checkAuth(*creator, request));
ASSERT_TRUE(response);
EXPECT_EQ("HTTP/1.0 401 Unauthorized\r\n"
@@ -254,13 +257,14 @@ TEST_F(HttpResponseCreatorAuthTest, badScheme) {
// This test verifies that not matching credential is rejected.
TEST_F(HttpResponseCreatorAuthTest, notMatching) {
// Create credentials.
BasicHttpAuthPtr basic_auth;
EXPECT_NO_THROW(basic_auth.reset(new BasicHttpAuth("test", "123\xa3")));
EXPECT_EQ("dGVzdDoxMjPCow==", basic_auth->getCredential());
BasicHttpAuthMap credentials;
credentials[basic_auth->getCredential()] = "test";
string realm = "ISC.ORG";
// Create basic HTTP authentication configuration.
BasicHttpAuthConfigPtr auth_config(new BasicHttpAuthConfig());
EXPECT_NO_THROW(auth_config->add("test", "123\xa3"));
const BasicHttpAuthMap& credentials = auth_config->getCredentialMap();
auto cred = credentials.find("dGVzdDoxMjPCow==");
EXPECT_NE(cred, credentials.end());
EXPECT_EQ(cred->second, "test");
auth_config->setRealm("ISC.ORG");
// Create request and finalize it.
HttpRequestPtr request(new HttpRequest());
@@ -275,7 +279,7 @@ TEST_F(HttpResponseCreatorAuthTest, notMatching) {
HttpResponsePtr response;
TestHttpResponseCreatorPtr creator(new TestHttpResponseCreator());;
ASSERT_NO_THROW(response = checkAuth(*creator, request, credentials, realm));
ASSERT_NO_THROW(response = auth_config->checkAuth(*creator, request));
ASSERT_TRUE(response);
EXPECT_EQ("HTTP/1.0 401 Unauthorized\r\n"
@@ -293,13 +297,14 @@ TEST_F(HttpResponseCreatorAuthTest, notMatching) {
// This test verifies that matching credential is accepted.
TEST_F(HttpResponseCreatorAuthTest, matching) {
// Create credentials.
BasicHttpAuthPtr basic_auth;
EXPECT_NO_THROW(basic_auth.reset(new BasicHttpAuth("test", "123\xa3")));
EXPECT_EQ("dGVzdDoxMjPCow==", basic_auth->getCredential());
BasicHttpAuthMap credentials;
credentials[basic_auth->getCredential()] = "test";
string realm = "ISC.ORG";
// Create basic HTTP authentication configuration.
BasicHttpAuthConfigPtr auth_config(new BasicHttpAuthConfig());
EXPECT_NO_THROW(auth_config->add("test", "123\xa3"));
const BasicHttpAuthMap& credentials = auth_config->getCredentialMap();
auto cred = credentials.find("dGVzdDoxMjPCow==");
EXPECT_NE(cred, credentials.end());
EXPECT_EQ(cred->second, "test");
auth_config->setRealm("ISC.ORG");
// Create request and finalize it.
HttpRequestPtr request(new HttpRequest());
@@ -307,13 +312,13 @@ TEST_F(HttpResponseCreatorAuthTest, matching) {
request->context()->http_version_minor_ = 0;
request->context()->method_ = "GET";
request->context()->uri_ = "/foo";
BasicAuthHttpHeaderContext auth(*basic_auth);
HttpHeaderContext auth("Authorization", "Basic dGVzdDoxMjPCow==");
request->context()->headers_.push_back(auth);
ASSERT_NO_THROW(request->finalize());
HttpResponsePtr response;
TestHttpResponseCreatorPtr creator(new TestHttpResponseCreator());;
ASSERT_NO_THROW(response = checkAuth(*creator, request, credentials, realm));
ASSERT_NO_THROW(response = auth_config->checkAuth(*creator, request));
EXPECT_FALSE(response);
addString("HTTP_CLIENT_REQUEST_AUTHORIZED received HTTP request "