]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#1421] Implemented auth and response hook
authorFrancis Dupont <fdupont@isc.org>
Sun, 1 Nov 2020 16:44:50 +0000 (17:44 +0100)
committerTomek Mrugalski <tomek@isc.org>
Fri, 20 Nov 2020 10:44:41 +0000 (11:44 +0100)
24 files changed:
src/bin/agent/agent_hooks.dox
src/bin/agent/ca_response_creator.cc
src/bin/agent/ca_response_creator.h
src/bin/agent/tests/Makefile.am
src/bin/agent/tests/basic_auth_library.cc [new file with mode: 0644]
src/bin/agent/tests/ca_cfg_mgr_unittests.cc
src/bin/agent/tests/ca_response_creator_unittests.cc
src/bin/agent/tests/callout_library.cc [moved from src/bin/agent/tests/basic_library.cc with 100% similarity]
src/bin/agent/tests/get_config_unittest.cc
src/bin/agent/tests/test_libraries.h.in
src/bin/netconf/tests/control_socket_unittests.cc
src/hooks/dhcp/high_availability/tests/ha_service_unittest.cc
src/lib/http/Makefile.am
src/lib/http/auth_config.h
src/lib/http/basic_auth_config.cc
src/lib/http/basic_auth_config.h
src/lib/http/post_request_json.h
src/lib/http/request.h
src/lib/http/response_creator.cc
src/lib/http/response_creator.h
src/lib/http/tests/Makefile.am
src/lib/http/tests/connection_pool_unittests.cc
src/lib/http/tests/response_creator_unittests.cc
src/lib/http/tests/server_client_unittests.cc

index 20bf584eff2254321c1c07980e3ffe1eafe71262..641eb55c27025361f8edcef23106b2516b31d739 100644 (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
@@ -23,36 +23,34 @@ command.
 
 @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
index 702d031688608934f54ed4e3a463517a1f0f4dc4..9bfdec3ef5ac44be47db8f729277617f1ac3a93b 100644 (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 {
 
@@ -30,7 +56,7 @@ CtrlAgentResponseCreator::createNewHttpRequest() const {
 
 HttpResponsePtr
 CtrlAgentResponseCreator::
-createStockHttpResponse(const ConstHttpRequestPtr& request,
+createStockHttpResponse(const HttpRequestPtr& request,
                         const HttpStatusCode& status_code) const {
     HttpResponsePtr response = createStockHttpResponseInternal(request, status_code);
     response->finalize();
@@ -39,7 +65,7 @@ createStockHttpResponse(const ConstHttpRequestPtr& request,
 
 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
@@ -60,7 +86,7 @@ createStockHttpResponseInternal(const ConstHttpRequestPtr& request,
 
 HttpResponsePtr
 CtrlAgentResponseCreator::
-createDynamicHttpResponse(const ConstHttpRequestPtr& request) {
+createDynamicHttpResponse(HttpRequestPtr request) {
     // First check authentication.
     HttpResponseJsonPtr http_response;
 
@@ -89,19 +115,39 @@ createDynamicHttpResponse(const ConstHttpRequestPtr& request) {
             }
         }
     }
-    // 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));
@@ -125,6 +171,24 @@ createDynamicHttpResponse(const ConstHttpRequestPtr& request) {
     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);
 }
 
index d8f1dd8ae70a35a534cd6ce93f6172861ccfe67f..189a323b42bbb1128596808098aa0fbc9a1594e8 100644 (file)
@@ -1,4 +1,4 @@
-// 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
@@ -52,7 +52,7 @@ public:
     /// @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:
@@ -68,7 +68,7 @@ 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.
@@ -76,7 +76,7 @@ private:
     /// @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
index b694b24afa1c48f90d9b12c41efaca2eaed5371b..834b776db8710b2f3de6c45e0773d4501b2dfe88 100644 (file)
@@ -41,7 +41,7 @@ TESTS_ENVIRONMENT = \
 TESTS =
 if HAVE_GTEST
 
-noinst_LTLIBRARIES = libbasic.la
+noinst_LTLIBRARIES = libcallout.la libbasicauth.la
 
 TESTS += ca_unittests
 
@@ -82,13 +82,27 @@ ca_unittests_LDADD += $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS)
 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
 
diff --git a/src/bin/agent/tests/basic_auth_library.cc b/src/bin/agent/tests/basic_auth_library.cc
new file mode 100644 (file)
index 0000000..f3581b6
--- /dev/null
@@ -0,0 +1,275 @@
+// 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);
+}
+
+}
+}
index 480e8c2234c191be59727b7734c13d5f32457313..4ae7f91fda84ecb848d41781e3f3d7e740c2eedd 100644 (file)
@@ -470,7 +470,7 @@ TEST_F(AgentParserTest, configParse3Sockets) {
 // 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);
 
@@ -478,7 +478,7 @@ TEST_F(AgentParserTest, configParseHooks) {
     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());
 }
index 58bce6800e937de7b2f2d4d70cc526424791d661..c49ab858a98826183d202c3471a9d76ff1d9e702 100644 (file)
 #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>
@@ -23,6 +25,7 @@ using namespace isc;
 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;
@@ -68,6 +71,8 @@ public:
     /// 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.
@@ -315,4 +320,105 @@ TEST_F(CtrlAgentResponseCreatorTest, basicAuth) {
                 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);
+}
+
 }
index b028ea819959e971ffe5a87ddb46f270440d648b..b9227bea1ad1bfbd1ae48faf992e1cd87b9e8281 100644 (file)
@@ -105,7 +105,7 @@ pathReplacer(ConstElementPtr ca_cfg) {
         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));
 }
 
index 4602c4c81fd9309c09a7b8669bb503a65e73f281..77cee3522cd619285f316f3d750a4a4fd6bdec34 100644 (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
@@ -16,8 +16,11 @@ namespace {
 // .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
 
index 7525f74bcecf9bec1f05d78ad069af7664b07847..5f754cb2849e9825d6fbc89dc0f2c4ca32b52ac1 100644 (file)
@@ -1,4 +1,4 @@
-// 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
@@ -430,7 +430,7 @@ protected:
     /// @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_,
@@ -448,10 +448,10 @@ protected:
     /// @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");
         }
index 824c5c54464893f51b4a85e6bc4ce89aab107b77..54d76fe6c4f8b8880c2d1837dcbf340939c46998 100644 (file)
@@ -226,7 +226,7 @@ public:
     }
 
     /// @brief Returns a vector of received requests.
-    std::vector<ConstPostHttpRequestJsonPtr> getReceivedRequests() {
+    std::vector<PostHttpRequestJsonPtr> getReceivedRequests() {
         return (requests_);
     }
 
@@ -240,7 +240,7 @@ public:
     ///
     /// @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) {
@@ -255,7 +255,7 @@ public:
         }
 
         // Request not found.
-        return (ConstPostHttpRequestJsonPtr());
+        return (PostHttpRequestJsonPtr());
     }
 
     /// @brief Sets control result  to be included in the responses.
@@ -333,7 +333,7 @@ private:
     /// @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
@@ -357,7 +357,7 @@ private:
     /// @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()) {
@@ -384,8 +384,8 @@ private:
         }
 
         // 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);
@@ -474,7 +474,7 @@ private:
     }
 
     /// @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_;
index a2acdcb8ca049714c92446a1e0a5891b3e81a7b2..4f8b77a2d7bead1242f7e0f35d08d4b37c17110f 100644 (file)
@@ -48,6 +48,7 @@ libkea_http_la_LDFLAGS  = $(AM_LDFLAGS)
 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
index a2450b50d8b81c168a47e6620d4693fa4c2cc803..3f76033f0122292dbdf92dbc06179da64f689df5 100644 (file)
@@ -67,7 +67,7 @@ public:
     /// @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:
 
index 9dd8577ab81359909b42f831d7e2c4b27d2ae80f..87106663aa88414d7b357d73017ec2878d7c9313 100644 (file)
@@ -202,7 +202,7 @@ BasicHttpAuthConfig::parse(const ConstElementPtr& config) {
 
 HttpResponseJsonPtr
 BasicHttpAuthConfig::checkAuth(const HttpResponseCreator& creator,
-                               const ConstHttpRequestPtr& request) const {
+                               const HttpRequestPtr& request) const {
     const BasicHttpAuthMap& credentials = getCredentialMap();
     bool authentic = false;
     if (credentials.empty()) {
index ec88e12ff18143309a9dfa58a45553c8e6c77e0c..80417d7dd70b75b895af9900361e69fa2401b40a 100644 (file)
@@ -121,7 +121,7 @@ public:
     /// @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:
 
index 6d7f82a8af824b6ca4d673c333a644f6f03e43e2..7a2faba2b387c1e1d5f74d0d53484cf89b8983f6 100644 (file)
@@ -27,8 +27,6 @@ class PostHttpRequestJson;
 
 /// @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.
 ///
index 1b26b1c48f4e2f6bec92ef43525af64b296fd1c6..b5ce5ac55ca43bbaab1deafec050764cef3eff05 100644 (file)
@@ -7,6 +7,7 @@
 #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>
@@ -28,9 +29,6 @@ class HttpRequest;
 /// @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
@@ -46,7 +44,9 @@ typedef boost::shared_ptr<const HttpRequest> ConstHttpRequestPtr;
 /// 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.
index 5a310e22b54f95df1c6c24e8450934e91b3ce899..a060f6ad0bec5378250da987431ad7b1a6677589 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2016-2018 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2020 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -12,7 +12,7 @@ namespace isc {
 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) {
index dbae326ba98cdc74d45c346dd2aa55b9c30a89ab..ac212fe5de6cf119b691af373fdd2e51c628de90 100644 (file)
@@ -1,4 +1,4 @@
-// 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
@@ -74,7 +74,7 @@ public:
     /// @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.
     ///
@@ -93,7 +93,7 @@ public:
     /// @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:
@@ -103,7 +103,7 @@ 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;
 
 };
 
index 6dd63a1d5ee1d347e9038f5f0fe7c4e6f3b9d923..fb38443148706ec942fda4d38c0c8f7c324d0360 100644 (file)
@@ -44,6 +44,7 @@ libhttp_unittests_CXXFLAGS = $(AM_CXXFLAGS)
 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
index 42fda5ff227c039a1de007664bf303e3f81c997e..13c4bd52bb1c1316570374758522559c85fa6037 100644 (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
@@ -55,7 +55,7 @@ private:
     /// @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
@@ -73,7 +73,7 @@ private:
     /// @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(),
index 1b2f9fdfb87f4be85cabf3001022f61f0265b52d..3e0f1078c321d30797e7a90333c5a70685aedc56 100644 (file)
@@ -49,7 +49,7 @@ private:
     /// @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
@@ -68,7 +68,7 @@ private:
     /// @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(),
index d55b3c2aa0471771efb8e76d9cde10fd399b1bcd..ab123191d16cef7f086cbb3c40d4657ff8fc3a0a 100644 (file)
@@ -93,7 +93,7 @@ private:
     /// @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
@@ -122,10 +122,10 @@ private:
     /// @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();