-// 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
@section agentHooksHookPoints Hooks in the Control Agent
- @subsection agentHooksControlCommandReceive control_command_receive
+ @subsection agentHooksAuth auth
- @b Arguments:
- - name: @b command, type: isc::data::ConstElementPtr, direction: <b>in/out</b>
- - name: @b response, type: isc::data::ConstElementPtr, direction: <b>in/out</b>
+ - name: @b request, type: isc::http::HttpRequestPtr, direction: <b>in/out</b>
+ - name: @b response, type: isc::http::HttpResponseJsonPtr, direction: <b>in/out</b>
- @b Description: this callout is executed when Control Agent receives a
control command over the RESTful interface (HTTP).
- The "command" argument is a pointer to the parsed JSON structure
- including command name and command arguments. If the callout implements
- the specified command, it handles the command and creates appropriate
- response. The response should be returned in the "response" argument.
- In most cases, the callout which handles the command will set the next
- step action to SKIP, to prevent the server from trying to handle the
- command on its own and overriding the response created by the callouts.
- A notable exception is the 'list-commands' command for which the callouts
- should not set the next step action to SKIP. The server has a special
- code path for this command which combines the list of commands returned
- by the callouts with the list of commands supported by the server. If
- the callout sets the next step action to SKIP in this case, the server
- will only return the list of commands supported by the hook library.
- The callout can modify the command arguments to influence the command
- processing by the Command Manager. For example, it may freely modify
- the configuration received in 'config-set' before it is processed by
- the server. The SKIP action is not set in this case.
-
- - <b>Next step status</b>: if any callout sets the next step action to SKIP,
- the server will assume that the command has been handled by the callouts
- and will expect that the response is provided in the "response" argument.
- The Control Agent will not handle the command in this case but simply
- return the response returned by the callout to the caller.
+ The "request" argument is a pointer to the request, in fact a
+ PostHttpRequestJsonPtr. The "response" argument is the response in
+ case of errors. The purpose of this callout is to implement authentication
+ and authorization. It is called after basic HTTP authentication.
+ The next step status is ignored: if the response is set the processing
+ will stop and the response is returned. In particular the command is not
+ forwarded.
+
+ @subsection agentHooksResponse response
+
+ - @b Arguments:
+ - name: @b request, type: isc::http::HttpRequestPtr, direction: <b>in</b>
+ - name: @b response, type: isc::http::HttpResponseJsonPtr, direction: <b>in/out</b>
+
+ - @b Description: this callout is executed when Control Agent executed
+ a control command over the RESTful interface (HTTP).
+ The "request" argument is a pointer to the request. It is used as a
+ reference and for callout contexts. The "response" argument is the
+ response which will be sent back to the resquesting client. It is
+ called after command processing. The next step status is ignored:
+ the response eventually modified will be sent back.
*/
\ No newline at end of file
#include <agent/ca_process.h>
#include <agent/ca_response_creator.h>
#include <cc/data.h>
+#include <hooks/callout_handle.h>
+#include <hooks/hooks_log.h>
+#include <hooks/hooks_manager.h>
#include <http/post_request_json.h>
#include <http/response_json.h>
#include <boost/pointer_cast.hpp>
#include <iostream>
using namespace isc::data;
+using namespace isc::hooks;
using namespace isc::http;
+namespace {
+
+/// Structure that holds registered hook indexes.
+struct CtrlAgentHooks {
+ int hook_index_auth_; ///< index of "auth" hook point.
+ int hook_index_response_; ///< index of "response" hook point.
+
+ /// Constructor that registers hook points.
+ CtrlAgentHooks() {
+ hook_index_auth_ = HooksManager::registerHook("auth");
+ hook_index_response_ = HooksManager::registerHook("response");
+ }
+};
+
+} // end of anonymous namespace.
+
+// Declare a Hooks object. As this is outside any function or method, it
+// will be instantiated (and the constructor run) when the module is loaded.
+// As a result, the hook indexes will be defined before any method in this
+// module is called.
+CtrlAgentHooks Hooks;
+
namespace isc {
namespace agent {
HttpResponsePtr
CtrlAgentResponseCreator::
-createStockHttpResponse(const ConstHttpRequestPtr& request,
+createStockHttpResponse(const HttpRequestPtr& request,
const HttpStatusCode& status_code) const {
HttpResponsePtr response = createStockHttpResponseInternal(request, status_code);
response->finalize();
HttpResponsePtr
CtrlAgentResponseCreator::
-createStockHttpResponseInternal(const ConstHttpRequestPtr& request,
+createStockHttpResponseInternal(const HttpRequestPtr& request,
const HttpStatusCode& status_code) const {
// The request hasn't been finalized so the request object
// doesn't contain any information about the HTTP version number
HttpResponsePtr
CtrlAgentResponseCreator::
-createDynamicHttpResponse(const ConstHttpRequestPtr& request) {
+createDynamicHttpResponse(HttpRequestPtr request) {
// First check authentication.
HttpResponseJsonPtr http_response;
}
}
}
- // The basic HTTP authentication check failed and left a response.
+
+ // Callout point for "auth".
+ if (HooksManager::calloutsPresent(Hooks.hook_index_auth_)) {
+ // Get callout handle.
+ CalloutHandlePtr callout_handle = request->getCalloutHandle();
+ ScopedCalloutHandleState callout_handle_state(callout_handle);
+
+ // Pass arguments.
+ callout_handle->setArgument("request", request);
+ callout_handle->setArgument("response", http_response);
+
+ // Call callouts.
+ HooksManager::callCallouts(Hooks.hook_index_auth_, *callout_handle);
+ callout_handle->getArgument("request", request);
+ callout_handle->getArgument("response", http_response);
+
+ // Ignore status as the HTTP response is used instead.
+ }
+
+ // The basic HTTP authentication check or a callout failed and
+ // left a response.
if (http_response) {
return (http_response);
}
// The request is always non-null, because this is verified by the
// createHttpResponse method. Let's try to convert it to the
- // ConstPostHttpRequestJson type as this is the type generated by the
+ // PostHttpRequestJson type as this is the type generated by the
// createNewHttpRequest. If the conversion result is null it means that
// the caller did not use createNewHttpRequest method to create this
// instance. This is considered an error in the server logic.
- ConstPostHttpRequestJsonPtr request_json = boost::dynamic_pointer_cast<
- const PostHttpRequestJson>(request);
+ PostHttpRequestJsonPtr request_json =
+ boost::dynamic_pointer_cast<PostHttpRequestJson>(request);
if (!request_json) {
// Notify the client that we have a problem with our server.
return (createStockHttpResponse(request, HttpStatusCode::INTERNAL_SERVER_ERROR));
http_response->setBodyAsJson(response);
http_response->finalize();
+ // Callout point for "response".
+ if (HooksManager::calloutsPresent(Hooks.hook_index_response_)) {
+ // Get callout handle.
+ CalloutHandlePtr callout_handle = request->getCalloutHandle();
+ ScopedCalloutHandleState callout_handle_state(callout_handle);
+
+ // Pass arguments.
+ callout_handle->setArgument("request", request);
+ callout_handle->setArgument("response", http_response);
+
+ // Call callouts.
+ HooksManager::callCallouts(Hooks.hook_index_response_,
+ *callout_handle);
+ callout_handle->getArgument("response", http_response);
+
+ // Ignore status as the HTTP response is used instead.
+ }
+
return (http_response);
}
-// Copyright (C) 2017-2018 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
/// @return Pointer to an @ref isc::http::HttpResponseJson object
/// representing stock HTTP response.
virtual http::HttpResponsePtr
- createStockHttpResponse(const http::ConstHttpRequestPtr& request,
+ createStockHttpResponse(const http::HttpRequestPtr& request,
const http::HttpStatusCode& status_code) const;
private:
/// @return Pointer to an @ref isc::http::HttpResponseJson object
/// representing stock HTTP response.
http::HttpResponsePtr
- createStockHttpResponseInternal(const http::ConstHttpRequestPtr& request,
+ createStockHttpResponseInternal(const http::HttpRequestPtr& request,
const http::HttpStatusCode& status_code) const;
/// @brief Creates implementation specific HTTP response.
/// @param request Pointer to an object representing HTTP request.
/// @return Pointer to an object representing HTTP response.
virtual http::HttpResponsePtr
- createDynamicHttpResponse(const http::ConstHttpRequestPtr& request);
+ createDynamicHttpResponse(http::HttpRequestPtr request);
};
} // end of namespace isc::agent
TESTS =
if HAVE_GTEST
-noinst_LTLIBRARIES = libbasic.la
+noinst_LTLIBRARIES = libcallout.la libbasicauth.la
TESTS += ca_unittests
ca_unittests_LDADD += $(BOOST_LIBS) $(GTEST_LDADD)
# The basic callout library - contains standard callouts
-libbasic_la_SOURCES = basic_library.cc
-libbasic_la_CXXFLAGS = $(AM_CXXFLAGS)
-libbasic_la_CPPFLAGS = $(AM_CPPFLAGS)
-libbasic_la_LIBADD = $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
-libbasic_la_LIBADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
-libbasic_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
-libbasic_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
+libcallout_la_SOURCES = callout_library.cc
+libcallout_la_CXXFLAGS = $(AM_CXXFLAGS)
+libcallout_la_CPPFLAGS = $(AM_CPPFLAGS)
+libcallout_la_LIBADD = $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libcallout_la_LIBADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+libcallout_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
+libcallout_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
+
+# The basic HTTP auth as a callout library
+libbasicauth_la_SOURCES = basic_auth_library.cc
+libbasicauth_la_CXXFLAGS = $(AM_CXXFLAGS)
+libbasicauth_la_CPPFLAGS = $(AM_CPPFLAGS)
+libbasicauth_la_LIBADD = $(top_builddir)/src/lib/http/libkea-http.la
+libbasicauth_la_LIBADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+libbasicauth_la_LIBADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libbasicauth_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libbasicauth_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
+libbasicauth_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
+libbasicauth_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libbasicauth_la_LIBADD += $(LOG4CPLUS_LIBS) $(BOOST_LIBS)
+libbasicauth_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
nodist_ca_unittests_SOURCES = test_data_files_config.h test_libraries.h
--- /dev/null
+// 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/.
+
+/// @file
+/// @brief Basic HTTP Authentication callout library
+
+#include <config.h>
+
+#include <cc/command_interpreter.h>
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+#include <hooks/hooks.h>
+#include <http/basic_auth_config.h>
+#include <http/post_request_json.h>
+#include <http/response_creator.h>
+#include <boost/shared_ptr.hpp>
+#include <boost/pointer_cast.hpp>
+
+using namespace isc::data;
+using namespace isc::hooks;
+using namespace isc::http;
+using namespace isc;
+using namespace std;
+
+namespace {
+
+/// @brief Response creator.
+class ResponseCreator : public HttpResponseCreator {
+public:
+ /// @brief Create a new request.
+ /// @return Pointer to the new instance of the @ref
+ /// isc::http::PostHttpRequestJson.
+ virtual HttpRequestPtr
+ createNewHttpRequest() const;
+
+ /// @brief Create stock HTTP response.
+ ///
+ /// @param request Pointer to an object representing HTTP request.
+ /// @param status_code Status code of the response.
+ /// @return Pointer to an @ref isc::http::HttpResponseJson object
+ /// representing stock HTTP response.
+ virtual HttpResponsePtr
+ createStockHttpResponse(const HttpRequestPtr& request,
+ const HttpStatusCode& status_code) const;
+
+ /// @brief Creates implementation specific HTTP response.
+ ///
+ /// @param request Pointer to an object representing HTTP request.
+ /// @return Pointer to an object representing HTTP response.
+ virtual HttpResponsePtr
+ createDynamicHttpResponse(HttpRequestPtr request);
+};
+
+HttpRequestPtr
+ResponseCreator::createNewHttpRequest() const {
+ return (HttpRequestPtr(new PostHttpRequestJson()));
+}
+
+HttpResponsePtr
+ResponseCreator::createStockHttpResponse(const HttpRequestPtr& request,
+ const HttpStatusCode& status_code) const {
+ HttpVersion http_version(1, 1);
+ HttpResponsePtr response(new HttpResponseJson(http_version, status_code));
+ response->finalize();
+ return (response);
+}
+
+HttpResponsePtr
+ResponseCreator::createDynamicHttpResponse(HttpRequestPtr request) {
+ isc_throw(NotImplemented, "createDynamicHttpResponse should not be called");
+}
+
+/// @brief The type of shared pointers to response creators.
+typedef boost::shared_ptr<ResponseCreator> ResponseCreatorPtr;
+
+/// @brief Implementation.
+class Impl {
+public:
+
+ /// @brief Constructor.
+ Impl();
+
+ /// @brief Destructor.
+ ~Impl();
+
+ /// @brief Configure.
+ ///
+ /// @param config element pointer to client list.
+ void configure(ConstElementPtr config);
+
+ /// @brief Basic HTTP authentication configuration.
+ BasicHttpAuthConfigPtr config_;
+
+ /// @brief Response creator.
+ ResponseCreatorPtr creator_;
+};
+
+Impl::Impl()
+ : config_(new BasicHttpAuthConfig()), creator_(new ResponseCreator()) {
+}
+
+Impl::~Impl() {
+}
+
+void
+Impl::configure(ConstElementPtr config) {
+ config_->parse(config);
+}
+
+/// @brief The type of shared pointers to implementations.
+typedef boost::shared_ptr<Impl> ImplPtr;
+
+/// @brief The implementation.
+ImplPtr impl;
+
+extern "C" {
+
+// Framework functions.
+
+/// @brief returns Kea hooks version.
+int
+version() {
+ return (KEA_HOOKS_VERSION);
+}
+
+/// @brief This function is called when the library is loaded.
+///
+/// @param handle library handle.
+/// @return 0 when initialization is successful, 1 otherwise.
+int
+load(LibraryHandle& handle) {
+#ifdef USE_STATIC_LINK
+ hooksStaticLinkInit();
+#endif
+ try {
+ impl.reset(new Impl());
+ ConstElementPtr config = handle.getParameter("config");
+ impl->configure(config);
+ } catch (const std::exception& ex) {
+ std::cerr << "load error: " << ex.what() << std::endl;
+ return (1);
+ }
+
+ return (0);
+}
+
+/// @brief This function is called when the library is unloaded.
+///
+/// @return always 0.
+int
+unload() {
+ impl.reset();
+ return (0);
+}
+
+// Callout functions.
+
+/// @brief This callout is called at the "auth" hook.
+///
+/// @param handle CalloutHandle.
+/// @return 0 upon success, non-zero otherwise.
+int
+auth(CalloutHandle& handle) {
+ // Sanity.
+ if (!impl) {
+ std::cerr << "no implementation" << std::endl;
+ return (0);
+ }
+
+ // Get the parameters.
+ HttpRequestPtr request;
+ HttpResponseJsonPtr response;
+ handle.getArgument("request", request);
+ handle.getArgument("response", response);
+
+ if (response) {
+ std::cerr << "response already set" << std::endl;
+ return (0);
+ }
+ if (!request) {
+ std::cerr << "no request" << std::endl;
+ return (0);
+ }
+ PostHttpRequestJsonPtr request_json =
+ boost::dynamic_pointer_cast<PostHttpRequestJson>(request);
+ if (!request_json) {
+ std::cerr << "no json post request" << std::endl;
+ return (0);
+ }
+ ConstElementPtr command = request_json->getBodyAsJson();
+ if (!command) {
+ std::cerr << "no command" << std::endl;
+ return (0);
+ }
+ if (command->getType() != Element::map) {
+ std::cerr << "command is not a map" << std::endl;
+ return (0);
+ }
+
+ // Modify request.
+ ElementPtr mutable_command = boost::const_pointer_cast<Element>(command);
+ if (command->contains("service")) {
+ mutable_command->remove("service");
+ request_json->setBodyAsJson(command);
+ }
+
+ // Perform authentication.
+ response = impl->config_->checkAuth(*impl->creator_, request);
+
+ // Set parameters.
+ handle.setArgument("request", request);
+ handle.setArgument("response", response);
+ return (0);
+}
+
+/// @brief This callout is called at the "response" hook.
+///
+/// @param handle CalloutHandle.
+/// @return 0 upon success, non-zero otherwise.
+int
+response(CalloutHandle& handle) {
+ // Sanity.
+ if (!impl) {
+ std::cerr << "no implementation" << std::endl;
+ return (0);
+ }
+
+ // Get the parameters.
+ HttpRequestPtr request;
+ HttpResponseJsonPtr response;
+ handle.getArgument("request", request);
+ handle.getArgument("response", response);
+
+ if (!request) {
+ std::cerr << "no request" << std::endl;
+ return (0);
+ }
+ if (!response) {
+ std::cerr << "no response" << std::endl;
+ return (0);
+ }
+
+ // Modify response.
+ ConstElementPtr body = response->getBodyAsJson();
+ if (!body) {
+ std::cerr << "no body" << std::endl;
+ return (0);
+ }
+ if (body->getType() != Element::list) {
+ std::cerr << "body is not a list" << std::endl;
+ return (0);
+ }
+ if (body->size() < 1) {
+ std::cerr << "body is empty" << std::endl;
+ return (0);
+ }
+ ConstElementPtr answer = body->get(0);
+ if (!answer || (answer->getType() != Element::map)) {
+ std::cerr << "answer is not map" << std::endl;
+ return (0);
+ }
+ ElementPtr mutable_answer = boost::const_pointer_cast<Element>(answer);
+ mutable_answer->set("comment", Element::create(string("got")));
+ response->setBodyAsJson(body);
+
+ // Set parameters.
+ handle.setArgument("response", response);
+ return (0);
+}
+
+}
+}
// 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], BASIC_CALLOUT_LIBRARY);
+ std::string cfg = pathReplacer(AGENT_CONFIGS[4], CALLOUT_LIBRARY);
// The configuration should be successful.
configParse(cfg.c_str(), 0);
CtrlAgentCfgContextPtr ctx = cfg_mgr_.getCtrlAgentCfgContext();
const HookLibsCollection libs = ctx->getHooksConfig().get();
ASSERT_EQ(1, libs.size());
- EXPECT_EQ(string(BASIC_CALLOUT_LIBRARY), libs[0].first);
+ EXPECT_EQ(string(CALLOUT_LIBRARY), libs[0].first);
ASSERT_TRUE(libs[0].second);
EXPECT_EQ("{ \"param1\": \"foo\" }", libs[0].second->str());
}
#include <agent/ca_command_mgr.h>
#include <agent/ca_response_creator.h>
#include <cc/command_interpreter.h>
+#include <hooks/hooks_manager.h>
#include <http/basic_auth_config.h>
#include <http/post_request.h>
#include <http/post_request_json.h>
#include <http/response_json.h>
#include <process/testutils/d_test_stubs.h>
+#include <agent/tests/test_libraries.h>
#include <gtest/gtest.h>
#include <boost/pointer_cast.hpp>
#include <functional>
using namespace isc::agent;
using namespace isc::config;
using namespace isc::data;
+using namespace isc::hooks;
using namespace isc::http;
using namespace isc::process;
namespace ph = std::placeholders;
/// Removes registered commands from the command manager.
virtual ~CtrlAgentResponseCreatorTest() {
CtrlAgentCommandMgr::instance().deregisterAll();
+ HooksManager::prepareUnloadLibraries();
+ static_cast<void>(HooksManager::unloadLibraries());
}
/// @brief Fills request context with required data to create new request.
std::string::npos);
}
+// This test verifies that Unauthorized is returned when authentication is
+// required but not provided by request using the hook.
+TEST_F(CtrlAgentResponseCreatorTest, hookNoAuth) {
+ setBasicContext(request_);
+
+ // Body: "list-commands" is natively supported by the command manager.
+ request_->context()->body_ = "{ \"command\": \"list-commands\","
+ " \"service\": [ ] }";
+
+ // All requests must be finalized before they can be processed.
+ ASSERT_NO_THROW(request_->finalize());
+
+ // Setup hook.
+ CtrlAgentCfgContextPtr ctx = getCtrlAgentCfgContext();
+ ASSERT_TRUE(ctx);
+ HooksConfig& hooks_cfg = ctx->getHooksConfig();
+ std::string auth_cfg = "{ \"config\": {\n"
+ "\"type\": \"basic\",\n"
+ "\"realm\": \"ISC.ORG\",\n"
+ "\"clients\": [{\n"
+ " \"user\": \"foo\",\n"
+ " \"password\": \"bar\"\n"
+ " }]}}";
+ ConstElementPtr auth_json;
+ ASSERT_NO_THROW(auth_json = Element::fromJSON(auth_cfg));
+ hooks_cfg.add(std::string(BASIC_AUTH_LIBRARY), auth_json);
+ ASSERT_NO_THROW(hooks_cfg.loadLibraries());
+
+ HttpResponsePtr response;
+ ASSERT_NO_THROW(response = response_creator_.createHttpResponse(request_));
+ ASSERT_TRUE(response);
+
+ // Request should have no service.
+ EXPECT_EQ("{ \"command\": \"list-commands\" }",
+ request_->context()->body_);
+
+ // Response must be convertible to HttpResponseJsonPtr.
+ HttpResponseJsonPtr response_json = boost::dynamic_pointer_cast<
+ HttpResponseJson>(response);
+ ASSERT_TRUE(response_json);
+
+ // Response must contain Unauthorized status code.
+ std::string expected = "HTTP/1.1 401 Unauthorized";
+ EXPECT_TRUE(response_json->toString().find(expected) != std::string::npos);
+ // Reponse should contain WWW-Authenticate header with configured realm.
+ expected = "WWW-Authenticate: Basic realm=\"ISC.ORG\"";
+ EXPECT_TRUE(response_json->toString().find(expected) != std::string::npos);
+}
+
+// Test successful server response when the client is authenticated.
+TEST_F(CtrlAgentResponseCreatorTest, hookBasicAuth) {
+ setBasicContext(request_);
+
+ // Body: "list-commands" is natively supported by the command manager.
+ request_->context()->body_ = "{ \"command\": \"list-commands\" }";
+
+ // Add basic HTTP authentication header.
+ const BasicHttpAuth& basic_auth = BasicHttpAuth("foo", "bar");
+ const BasicAuthHttpHeaderContext& basic_auth_header =
+ BasicAuthHttpHeaderContext(basic_auth);
+ request_->context()->headers_.push_back(basic_auth_header);
+
+ // All requests must be finalized before they can be processed.
+ ASSERT_NO_THROW(request_->finalize());
+
+ // Setup hook.
+ CtrlAgentCfgContextPtr ctx = getCtrlAgentCfgContext();
+ ASSERT_TRUE(ctx);
+ HooksConfig& hooks_cfg = ctx->getHooksConfig();
+ std::string auth_cfg = "{ \"config\": {\n"
+ "\"type\": \"basic\",\n"
+ "\"realm\": \"ISC.ORG\",\n"
+ "\"clients\": [{\n"
+ " \"user\": \"foo\",\n"
+ " \"password\": \"bar\"\n"
+ " }]}}";
+ ConstElementPtr auth_json;
+ ASSERT_NO_THROW(auth_json = Element::fromJSON(auth_cfg));
+ hooks_cfg.add(std::string(BASIC_AUTH_LIBRARY), auth_json);
+ ASSERT_NO_THROW(hooks_cfg.loadLibraries());
+
+ HttpResponsePtr response;
+ ASSERT_NO_THROW(response = response_creator_.createHttpResponse(request_));
+ ASSERT_TRUE(response);
+
+ // Response must be convertible to HttpResponseJsonPtr.
+ HttpResponseJsonPtr response_json = boost::dynamic_pointer_cast<
+ HttpResponseJson>(response);
+ ASSERT_TRUE(response_json);
+
+ // Response must be successful.
+ EXPECT_TRUE(response_json->toString().find("HTTP/1.1 200 OK") !=
+ std::string::npos);
+ // Response must contain JSON body with "result" of 0.
+ EXPECT_TRUE(response_json->toString().find("\"result\": 0") !=
+ std::string::npos);
+ // Response must contain JSON body with "comment": "got".
+ EXPECT_TRUE(response_json->toString().find("\"comment\": \"got\"") !=
+ std::string::npos);
+}
+
}
return;
}
ElementPtr first_lib = hooks_libs->getNonConst(0);
- std::string lib_path(BASIC_CALLOUT_LIBRARY);
+ std::string lib_path(CALLOUT_LIBRARY);
first_lib->set("library", Element::create(lib_path));
}
-// 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
// .so file. Note that we access the .so file - libtool creates this as a
// like to the real shared library.
-// Basic library with context_create and three "standard" callouts.
-static const char* BASIC_CALLOUT_LIBRARY = "@abs_builddir@/.libs/libbasic.so";
+// Basic callout library with context_create and three "standard" callouts.
+static const char* CALLOUT_LIBRARY = "@abs_builddir@/.libs/libcallout.so";
+
+// Basic HTTP authentication as a callout library.
+static const char* BASIC_AUTH_LIBRARY = "@abs_builddir@/.libs/libbasicauth.so";
} // anonymous namespace
-// Copyright (C) 2018-2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2018-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
/// @param request Pointer to the HTTP request.
/// @return Pointer to the generated HTTP response.
virtual HttpResponsePtr
- createStockHttpResponse(const ConstHttpRequestPtr& request,
+ createStockHttpResponse(const HttpRequestPtr& request,
const HttpStatusCode& status_code) const {
// Data is in the request context.
HttpVersion http_version(request->context()->http_version_major_,
/// @param request Pointer to the HTTP request.
/// @return Pointer to an object representing HTTP response.
virtual HttpResponsePtr
- createDynamicHttpResponse(const ConstHttpRequestPtr& request) {
+ createDynamicHttpResponse(HttpRequestPtr request) {
// Request must always be JSON.
- ConstPostHttpRequestJsonPtr request_json =
- boost::dynamic_pointer_cast<const PostHttpRequestJson>(request);
+ PostHttpRequestJsonPtr request_json =
+ boost::dynamic_pointer_cast<PostHttpRequestJson>(request);
if (!request_json) {
isc_throw(Unexpected, "request is not JSON");
}
}
/// @brief Returns a vector of received requests.
- std::vector<ConstPostHttpRequestJsonPtr> getReceivedRequests() {
+ std::vector<PostHttpRequestJsonPtr> getReceivedRequests() {
return (requests_);
}
///
/// @return Pointer to the request found, or null pointer if there is
/// no such request.
- ConstPostHttpRequestJsonPtr
+ PostHttpRequestJsonPtr
findRequest(const std::string& str1, const std::string& str2,
const std::string& str3 = "") {
for (auto r = requests_.begin(); r < requests_.end(); ++r) {
}
// Request not found.
- return (ConstPostHttpRequestJsonPtr());
+ return (PostHttpRequestJsonPtr());
}
/// @brief Sets control result to be included in the responses.
/// @param request Pointer to the HTTP request.
/// @return Pointer to the generated HTTP response.
virtual HttpResponsePtr
- createStockHttpResponse(const ConstHttpRequestPtr& request,
+ createStockHttpResponse(const HttpRequestPtr& request,
const HttpStatusCode& status_code) const {
// The request hasn't been finalized so the request object
// doesn't contain any information about the HTTP version number
/// @param request Pointer to the HTTP request.
/// @return Pointer to the generated HTTP OK response.
virtual HttpResponsePtr
- createDynamicHttpResponse(const ConstHttpRequestPtr& request) {
+ createDynamicHttpResponse(HttpRequestPtr request) {
// Check authentication.
const BasicHttpAuthMap& credentials = getCredentials();
if (!credentials.empty()) {
}
// Request must always be JSON.
- ConstPostHttpRequestJsonPtr request_json =
- boost::dynamic_pointer_cast<const PostHttpRequestJson>(request);
+ PostHttpRequestJsonPtr request_json =
+ boost::dynamic_pointer_cast<PostHttpRequestJson>(request);
// Remember the request received.
requests_.push_back(request_json);
}
/// @brief Holds received HTTP requests.
- std::vector<ConstPostHttpRequestJsonPtr> requests_;
+ std::vector<PostHttpRequestJsonPtr> requests_;
/// @brief Control result to be returned in the server responses.
int control_result_;
libkea_http_la_LDFLAGS += -no-undefined -version-info 20:0:0
libkea_http_la_LIBADD =
+libkea_http_la_LIBADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
libkea_http_la_LIBADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
libkea_http_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la
libkea_http_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
/// @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;
+ const isc::http::HttpRequestPtr& request) const = 0;
private:
HttpResponseJsonPtr
BasicHttpAuthConfig::checkAuth(const HttpResponseCreator& creator,
- const ConstHttpRequestPtr& request) const {
+ const HttpRequestPtr& request) const {
const BasicHttpAuthMap& credentials = getCredentialMap();
bool authentic = false;
if (credentials.empty()) {
/// @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;
+ const isc::http::HttpRequestPtr& request) const;
private:
/// @brief Pointer to @ref PostHttpRequestJson.
typedef boost::shared_ptr<PostHttpRequestJson> PostHttpRequestJsonPtr;
-/// @brief Pointer to const @ref PostHttpRequestJson.
-typedef boost::shared_ptr<const PostHttpRequestJson> ConstPostHttpRequestJsonPtr;
/// @brief Represents HTTP POST request with JSON body.
///
#ifndef HTTP_REQUEST_H
#define HTTP_REQUEST_H
+#include <hooks/callout_handle_associate.h>
#include <http/basic_auth.h>
#include <http/http_message.h>
#include <http/request_context.h>
/// @brief Pointer to the @ref HttpRequest object.
typedef boost::shared_ptr<HttpRequest> HttpRequestPtr;
-/// @brief Pointer to the const @ref HttpRequest object.
-typedef boost::shared_ptr<const HttpRequest> ConstHttpRequestPtr;
-
/// @brief Represents HTTP request message.
///
/// This derivation of the @c HttpMessage class is specialized to represent
/// which derives from @c PostHttpRequest requires that the POST message
/// includes body holding a JSON structure and provides methods to parse the
/// JSON body.
-class HttpRequest : public HttpMessage {
+///
+/// Callouts are associated to the request.
+class HttpRequest : public HttpMessage, public hooks::CalloutHandleAssociate {
public:
/// @brief HTTP methods.
-// 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
namespace http {
HttpResponsePtr
-HttpResponseCreator::createHttpResponse(const ConstHttpRequestPtr& request) {
+HttpResponseCreator::createHttpResponse(HttpRequestPtr request) {
// This should never happen. This method must only be called with a
// non null request, so we consider it unlikely internal server error.
if (!request) {
-// Copyright (C) 2016-2017 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
/// @return Pointer to the object encapsulating generated HTTP response.
/// @throw HttpResponseError if request is a NULL pointer.
virtual HttpResponsePtr
- createHttpResponse(const ConstHttpRequestPtr& request) final;
+ createHttpResponse(HttpRequestPtr request) final;
/// @brief Create a new request.
///
/// @param status_code Status code of the response.
/// @return Pointer to an object representing HTTP response.
virtual HttpResponsePtr
- createStockHttpResponse(const ConstHttpRequestPtr& request,
+ createStockHttpResponse(const HttpRequestPtr& request,
const HttpStatusCode& status_code) const = 0;
protected:
/// @param request Pointer to an object representing HTTP request.
/// @return Pointer to an object representing HTTP response.
virtual HttpResponsePtr
- createDynamicHttpResponse(const ConstHttpRequestPtr& request) = 0;
+ createDynamicHttpResponse(HttpRequestPtr request) = 0;
};
libhttp_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
libhttp_unittests_LDADD = $(top_builddir)/src/lib/http/libkea-http.la
+libhttp_unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
libhttp_unittests_LDADD += $(top_builddir)/src/lib/testutils/libkea-testutils.la
libhttp_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
libhttp_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
-// 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
/// @param request Pointer to the HTTP request.
/// @return Pointer to the generated HTTP response.
virtual HttpResponsePtr
- createStockHttpResponse(const ConstHttpRequestPtr& request,
+ createStockHttpResponse(const HttpRequestPtr& request,
const HttpStatusCode& status_code) const {
// The request hasn't been finalized so the request object
// doesn't contain any information about the HTTP version number
/// @param request Pointer to the HTTP request.
/// @return Pointer to the generated HTTP OK response with no content.
virtual HttpResponsePtr
- createDynamicHttpResponse(const ConstHttpRequestPtr& request) {
+ createDynamicHttpResponse(HttpRequestPtr request) {
// The simplest thing is to create a response with no content.
// We don't need content to test our class.
ResponsePtr response(new Response(request->getHttpVersion(),
/// @param request Pointer to the HTTP request.
/// @return Pointer to the generated HTTP response.
virtual HttpResponsePtr
- createStockHttpResponse(const ConstHttpRequestPtr& request,
+ createStockHttpResponse(const HttpRequestPtr& request,
const HttpStatusCode& status_code) const {
// The request hasn't been finalized so the request object
// doesn't contain any information about the HTTP version number
/// @param request Pointer to the HTTP request.
/// @return Pointer to the generated HTTP OK response with no content.
virtual HttpResponsePtr
- createDynamicHttpResponse(const ConstHttpRequestPtr& request) {
+ createDynamicHttpResponse(HttpRequestPtr request) {
// The simplest thing is to create a response with no content.
// We don't need content to test our class.
ResponsePtr response(new Response(request->getHttpVersion(),
/// @param request Pointer to the HTTP request.
/// @return Pointer to the generated HTTP response.
virtual HttpResponsePtr
- createStockHttpResponse(const ConstHttpRequestPtr& request,
+ createStockHttpResponse(const HttpRequestPtr& request,
const HttpStatusCode& status_code) const {
// The request hasn't been finalized so the request object
// doesn't contain any information about the HTTP version number
/// @param request Pointer to the HTTP request.
/// @return Pointer to the generated HTTP OK response with no content.
virtual HttpResponsePtr
- createDynamicHttpResponse(const ConstHttpRequestPtr& request) {
+ createDynamicHttpResponse(HttpRequestPtr request) {
// Request must always be JSON.
- ConstPostHttpRequestJsonPtr request_json =
- boost::dynamic_pointer_cast<const PostHttpRequestJson>(request);
+ PostHttpRequestJsonPtr request_json =
+ boost::dynamic_pointer_cast<PostHttpRequestJson>(request);
ConstElementPtr body;
if (request_json) {
body = request_json->getBodyAsJson();