Kea 2.7.5
http_command_response_creator.cc
Go to the documentation of this file.
1// Copyright (C) 2021-2024 Internet Systems Consortium, Inc. ("ISC")
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7#include <config.h>
8
10#include <config/command_mgr.h>
11#include <config/config_log.h>
13#include <hooks/hooks_log.h>
14#include <hooks/hooks_manager.h>
16#include <http/response_json.h>
17#include <boost/pointer_cast.hpp>
18#include <iostream>
19
20using namespace isc::config;
21using namespace isc::data;
22using namespace isc::hooks;
23using namespace isc::http;
24using namespace std;
25
26namespace {
27
29struct HttpCommandHooks {
30 int hook_index_http_auth_;
31 int hook_index_http_response_;
32
34 HttpCommandHooks() {
35 hook_index_http_auth_ = HooksManager::registerHook("http_auth");
36 hook_index_http_response_ = HooksManager::registerHook("http_response");
37 }
38};
39
40} // end of anonymous namespace.
41
42// Declare a Hooks object. As this is outside any function or method, it
43// will be instantiated (and the constructor run) when the module is loaded.
44// As a result, the hook indexes will be defined before any method in this
45// module is called.
46HttpCommandHooks Hooks;
47
48namespace isc {
49namespace config {
50
55
59 const HttpStatusCode& status_code) const {
60 HttpResponsePtr response = createStockHttpResponseInternal(request, status_code);
61 response->finalize();
62 return (response);
63}
64
66HttpCommandResponseCreator::
67createStockHttpResponseInternal(const HttpRequestPtr& request,
68 const HttpStatusCode& status_code) const {
69 // The request hasn't been finalized so the request object
70 // doesn't contain any information about the HTTP version number
71 // used. But, the context should have this data (assuming the
72 // HTTP version is parsed OK).
73 HttpVersion http_version(request->context()->http_version_major_,
74 request->context()->http_version_minor_);
75 // We only accept HTTP version 1.0 or 1.1. If other version number is found
76 // we fall back to HTTP/1.0.
77 if ((http_version < HttpVersion(1, 0)) || (HttpVersion(1, 1) < http_version)) {
78 http_version.major_ = 1;
79 http_version.minor_ = 0;
80 }
81 // This will generate the response holding JSON content.
82 HttpResponsePtr response(new HttpResponseJson(http_version, status_code));
83 // Add extra headers.
84 if (config_) {
85 copyHttpHeaders(config_->getHttpHeaders(), *response);
86 }
87 return (response);
88}
89
91HttpCommandResponseCreator::createDynamicHttpResponse(HttpRequestPtr request) {
92 CfgHttpHeaders headers;
93 HttpResponseJsonPtr http_response;
94
95 // Check the basic HTTP authentication.
96 if (config_) {
97 headers = config_->getHttpHeaders();
98 const HttpAuthConfigPtr& auth = config_->getAuthConfig();
99 if (auth) {
100 http_response = auth->checkAuth(*this, request);
101 }
102 }
103
104 // Pass extra headers to the hook.
105 bool auth_failed = false;
106 if (http_response) {
107 auth_failed = true;
108 copyHttpHeaders(headers, *http_response);
109 }
110
111 // Callout point for "http_auth".
112 bool reset_handle = false;
113 if (HooksManager::calloutsPresent(Hooks.hook_index_http_auth_)) {
114 // Get callout handle.
115 CalloutHandlePtr callout_handle = request->getCalloutHandle();
116 ScopedCalloutHandleState callout_handle_state(callout_handle);
117
118 // Pass arguments.
119 callout_handle->setArgument("request", request);
120 callout_handle->setArgument("response", http_response);
121
122 // Call callouts.
123 HooksManager::callCallouts(Hooks.hook_index_http_auth_,
124 *callout_handle);
125 callout_handle->getArgument("request", request);
126 callout_handle->getArgument("response", http_response);
127
128 // Status other than continue means 'please reset the handle'.
129 if (callout_handle->getStatus() != CalloutHandle::NEXT_STEP_CONTINUE) {
130 reset_handle = true;
131 }
132 }
133
134 // The basic HTTP authentication check or a callout failed and
135 // left a response.
136 if (http_response) {
137 // Avoid to copy extra headers twice even this should not be required.
138 if (!auth_failed && !headers.empty()) {
139 copyHttpHeaders(headers, *http_response);
140 if (http_response->isFinalized()) {
141 // Argh! The response was already finalized.
142 http_response->reset();
143 http_response->finalize();
144 }
145 }
146 return (http_response);
147 }
148
149 // Reset the handle when a hook asks for.
150 if (reset_handle) {
151 request->resetCalloutHandle();
152 }
153
154 // The request is always non-null, because this is verified by the
155 // createHttpResponse method. Let's try to convert it to the
156 // PostHttpRequestJson type as this is the type generated by the
157 // createNewHttpRequest. If the conversion result is null it means that
158 // the caller did not use createNewHttpRequest method to create this
159 // instance. This is considered an error in the server logic.
160 PostHttpRequestJsonPtr request_json =
161 boost::dynamic_pointer_cast<PostHttpRequestJson>(request);
162 if (!request_json) {
163 // Notify the client that we have a problem with our server.
164 return (createStockHttpResponse(request, HttpStatusCode::INTERNAL_SERVER_ERROR));
165 }
166
167 // We have already checked that the request is finalized so the call
168 // to getBodyAsJson must not trigger an exception.
169 ConstElementPtr command = request_json->getBodyAsJson();
170
171 // Process command doesn't generate exceptions but can possibly return
172 // null response, if the handler is not implemented properly. This is
173 // again an internal server issue.
174 ConstElementPtr response = config::CommandMgr::instance().processCommand(command);
175
176 if (!response) {
177 // Notify the client that we have a problem with our server.
178 return (createStockHttpResponse(request, HttpStatusCode::INTERNAL_SERVER_ERROR));
179 }
180
181 // Normal Responses coming from the Kea server must always be wrapped in
182 // a list as they may contain responses from multiple daemons.
183 // If we're emulating that for backward compatibility, then we need to wrap
184 // the answer in a list if it isn't in one already.
185 if ((!config_ || config_->getEmulateAgentResponse()) &&
186 (response->getType() != Element::list)) {
187 ElementPtr response_list = Element::createList();
188 response_list->add(boost::const_pointer_cast<Element>(response));
189 response = response_list;
190 }
191
192 // The response is OK, so let's create new HTTP response with the status OK.
193 http_response = boost::dynamic_pointer_cast<
194 HttpResponseJson>(createStockHttpResponseInternal(request, HttpStatusCode::OK));
195 http_response->setBodyAsJson(response);
196 http_response->finalize();
197
198 // Callout point for "http_response".
199 if (HooksManager::calloutsPresent(Hooks.hook_index_http_response_)) {
200 // Get callout handle.
201 CalloutHandlePtr callout_handle = request->getCalloutHandle();
202 ScopedCalloutHandleState callout_handle_state(callout_handle);
203
204 // Pass arguments.
205 callout_handle->setArgument("request", request);
206 callout_handle->setArgument("response", http_response);
207
208 // Call callouts.
209 HooksManager::callCallouts(Hooks.hook_index_http_response_,
210 *callout_handle);
211 callout_handle->getArgument("response", http_response);
212
213 // Ignore status as the HTTP response is used instead.
214 }
215
216 return (http_response);
217}
218
219} // end of namespace isc::config
220} // end of namespace isc
CtrlAgentHooks Hooks
static CommandMgr & instance()
CommandMgr is a singleton class.
virtual http::HttpRequestPtr createNewHttpRequest() const
Create a new request.
virtual http::HttpResponsePtr createStockHttpResponse(const http::HttpRequestPtr &request, const http::HttpStatusCode &status_code) const
Creates stock HTTP response.
static ElementPtr createList(const Position &pos=ZERO_POSITION())
Creates an empty ListElement type ElementPtr.
Definition data.cc:299
@ NEXT_STEP_CONTINUE
continue normally
static int registerHook(const std::string &name)
Register Hook.
static bool calloutsPresent(int index)
Are callouts present?
static void callCallouts(int index, CalloutHandle &handle)
Calls the callouts for a given hook.
Wrapper class around callout handle which automatically resets handle's state.
Represents HTTP response with JSON content.
void setBodyAsJson(const data::ConstElementPtr &json_body)
Generates JSON content from the data structures represented as data::ConstElementPtr.
Represents HTTP POST request with JSON body.
HttpCommandHooks Hooks
boost::shared_ptr< const Element > ConstElementPtr
Definition data.h:29
boost::shared_ptr< Element > ElementPtr
Definition data.h:28
boost::shared_ptr< CalloutHandle > CalloutHandlePtr
A shared pointer to a CalloutHandle object.
HttpStatusCode
HTTP status codes (cf RFC 2068)
Definition response.h:30
void copyHttpHeaders(const CfgHttpHeaders &headers, const HTTP_MSG &message)
Copy config HTTP headers to message.
boost::shared_ptr< PostHttpRequestJson > PostHttpRequestJsonPtr
Pointer to PostHttpRequestJson.
boost::shared_ptr< HttpAuthConfig > HttpAuthConfigPtr
Type of shared pointers to HTTP authentication configuration.
Definition auth_config.h:97
boost::shared_ptr< HttpResponseJson > HttpResponseJsonPtr
Pointer to the HttpResponseJson object.
boost::shared_ptr< HttpResponse > HttpResponsePtr
Pointer to the HttpResponse object.
Definition response.h:81
boost::shared_ptr< HttpRequest > HttpRequestPtr
Pointer to the HttpRequest object.
Definition request.h:30
std::vector< CfgHttpHeader > CfgHttpHeaders
Collection of config HTTP headers.
Defines the logger used by the top-level component of kea-lfc.
HTTP protocol version.
Definition http_types.h:14