Kea 3.1.9
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
11#include <config/command_mgr.h>
12#include <config/config_log.h>
14#include <hooks/hooks_log.h>
15#include <hooks/hooks_manager.h>
17#include <http/response_json.h>
18#include <boost/pointer_cast.hpp>
19#include <iostream>
20
21using namespace isc::config;
22using namespace isc::data;
23using namespace isc::hooks;
24using namespace isc::http;
25using namespace std;
26
27namespace {
28
30struct HttpCommandHooks {
31 int hook_index_http_auth_;
32 int hook_index_http_response_;
33
35 HttpCommandHooks() {
36 hook_index_http_auth_ = HooksManager::registerHook("http_auth");
37 hook_index_http_response_ = HooksManager::registerHook("http_response");
38 }
39};
40
41} // end of anonymous namespace.
42
43// Declare a Hooks object. As this is outside any function or method, it
44// will be instantiated (and the constructor run) when the module is loaded.
45// As a result, the hook indexes will be defined before any method in this
46// module is called.
47HttpCommandHooks Hooks;
48
49namespace isc {
50namespace config {
51
56
60 const HttpStatusCode& status_code) const {
61 HttpResponsePtr response = createStockHttpResponseInternal(request, status_code);
62 response->finalize();
63 return (response);
64}
65
67HttpCommandResponseCreator::
68createStockHttpResponseInternal(const HttpRequestPtr& request,
69 const HttpStatusCode& status_code) const {
70 // The request hasn't been finalized so the request object
71 // doesn't contain any information about the HTTP version number
72 // used. But, the context should have this data (assuming the
73 // HTTP version is parsed OK).
74 HttpVersion http_version(request->context()->http_version_major_,
75 request->context()->http_version_minor_);
76 // We only accept HTTP version 1.0 or 1.1. If other version number is found
77 // we fall back to HTTP/1.0.
78 if ((http_version < HttpVersion(1, 0)) || (HttpVersion(1, 1) < http_version)) {
79 http_version.major_ = 1;
80 http_version.minor_ = 0;
81 }
82 // This will generate the response holding JSON content.
83 HttpResponsePtr response(new HttpResponseJson(http_version, status_code));
84 // Add extra headers.
85 if (config_) {
86 copyHttpHeaders(config_->getHttpHeaders(), *response);
87 }
88 return (response);
89}
90
92HttpCommandResponseCreator::createDynamicHttpResponse(HttpRequestPtr request) {
93 CfgHttpHeaders headers;
94 HttpResponseJsonPtr http_response;
95
96 // Check the basic HTTP authentication.
97 if (config_) {
98 headers = config_->getHttpHeaders();
99 const HttpAuthConfigPtr& auth = config_->getAuthConfig();
100 if (auth) {
101 http_response = auth->checkAuth(*this, request);
102 }
103 }
104
105 // Pass extra headers to the hook.
106 bool auth_failed = false;
107 if (http_response) {
108 auth_failed = true;
109 copyHttpHeaders(headers, *http_response);
110 }
111
112 // Callout point for "http_auth".
113 bool reset_handle = false;
114 if (HooksManager::calloutsPresent(Hooks.hook_index_http_auth_)) {
115 // Get callout handle.
116 CalloutHandlePtr callout_handle = request->getCalloutHandle();
117 ScopedCalloutHandleState callout_handle_state(callout_handle);
118
119 // Pass arguments.
120 callout_handle->setArgument("request", request);
121 callout_handle->setArgument("response", http_response);
122
123 // Call callouts.
124 HooksManager::callCallouts(Hooks.hook_index_http_auth_,
125 *callout_handle);
126 callout_handle->getArgument("request", request);
127 callout_handle->getArgument("response", http_response);
128
129 // Status other than continue means 'please reset the handle'.
130 if (callout_handle->getStatus() != CalloutHandle::NEXT_STEP_CONTINUE) {
131 reset_handle = true;
132 }
133 }
134
135 // The basic HTTP authentication check or a callout failed and
136 // left a response.
137 if (http_response) {
138 // Avoid to copy extra headers twice even this should not be required.
139 if (!auth_failed && !headers.empty()) {
140 copyHttpHeaders(headers, *http_response);
141 if (http_response->isFinalized()) {
142 // Argh! The response was already finalized.
143 http_response->reset();
144 http_response->finalize();
145 }
146 }
147 return (http_response);
148 }
149
150 // Reset the handle when a hook asks for.
151 if (reset_handle) {
152 request->resetCalloutHandle();
153 }
154
155 // The request is always non-null, because this is verified by the
156 // createHttpResponse method. Let's try to convert it to the
157 // PostHttpRequestJson type as this is the type generated by the
158 // createNewHttpRequest. If the conversion result is null it means that
159 // the caller did not use createNewHttpRequest method to create this
160 // instance. This is considered an error in the server logic.
161 PostHttpRequestJsonPtr request_json =
162 boost::dynamic_pointer_cast<PostHttpRequestJson>(request);
163 if (!request_json) {
164 // Notify the client that we have a problem with our server.
165 return (createStockHttpResponse(request, HttpStatusCode::INTERNAL_SERVER_ERROR));
166 }
167
168 // We have already checked that the request is finalized so the call
169 // to getBodyAsJson must not trigger an exception.
170 ConstElementPtr command = request_json->getBodyAsJson();
171
172 // Process command doesn't generate exceptions but can possibly return
173 // null response, if the handler is not implemented properly. This is
174 // again an internal server issue.
175 // Check the services argument first.
176 ConstElementPtr response = checkService(command);
177 if (!response) {
178 response = config::CommandMgr::instance().processCommand(command);
179 if (!response) {
180 // Notify the client that we have a problem with our server.
181 return (createStockHttpResponse(request,
182 HttpStatusCode::INTERNAL_SERVER_ERROR));
183 }
184 }
185
186 // Normal Responses coming from the Kea server must always be wrapped in
187 // a list as they may contain responses from multiple daemons.
188 // If we're emulating that for backward compatibility, then we need to wrap
189 // the answer in a list if it isn't in one already.
191 (response->getType() != Element::list)) {
192 ElementPtr response_list = Element::createList();
193 response_list->add(boost::const_pointer_cast<Element>(response));
194 response = response_list;
195 }
196
197 // The response is OK, so let's create new HTTP response with the status OK.
198 http_response = boost::dynamic_pointer_cast<
199 HttpResponseJson>(createStockHttpResponseInternal(request, HttpStatusCode::OK));
200 http_response->setBodyAsJson(response);
201 http_response->finalize();
202
203 // Callout point for "http_response".
204 if (HooksManager::calloutsPresent(Hooks.hook_index_http_response_)) {
205 // Get callout handle.
206 CalloutHandlePtr callout_handle = request->getCalloutHandle();
207 ScopedCalloutHandleState callout_handle_state(callout_handle);
208
209 // Pass arguments.
210 callout_handle->setArgument("request", request);
211 callout_handle->setArgument("response", http_response);
212
213 // Call callouts.
214 HooksManager::callCallouts(Hooks.hook_index_http_response_,
215 *callout_handle);
216 callout_handle->getArgument("response", http_response);
217
218 // Ignore status as the HTTP response is used instead.
219 }
220
221 return (http_response);
222}
223
225HttpCommandResponseCreator::checkService(ConstElementPtr command) const {
227 return (ConstElementPtr());
228 }
229
230 // Sanity checks (errors will be handled by processCommand).
231 if (!command || (command->getType() != Element::map)) {
232 return (ConstElementPtr());
233 }
234 ConstElementPtr services = command->get("service");
235 if (!services) {
236 return (ConstElementPtr());
237 }
238 if (services->getType() != Element::list) {
239 return (createAnswer(CONTROL_RESULT_ERROR, "service value must be a list"));
240 }
241 // Ignore empty service list as the control agent does.
242 if (services->empty()) {
243 return (ConstElementPtr());
244 }
245 if (services->size() != 1) {
246 return (createAnswer(CONTROL_RESULT_ERROR, "service value has more than one item"));
247 }
248 ConstElementPtr service = services->get(0);
249 if (!service || (service->getType() != Element::string)) {
250 return (createAnswer(CONTROL_RESULT_ERROR, "service name must be a string"));
251 }
252 string name = service->stringValue();
253 if (name.empty() || (name == HttpCommandConfig::SUPPORTED_SERVICE)) {
254 return (ConstElementPtr());
255 }
256 // Service name mismatch.
257 string msg = "unsupported service '" + name + "', expected '";
260}
261
262} // end of namespace isc::config
263} // end of namespace isc
@ NEXT_STEP_CONTINUE
continue normally
@ map
Definition data.h:160
@ list
Definition data.h:159
@ string
Definition data.h:157
static ElementPtr createList(const Position &pos=ZERO_POSITION())
Creates an empty ListElement type ElementPtr.
Definition data.cc:349
virtual isc::data::ConstElementPtr processCommand(const isc::data::ConstElementPtr &cmd)
Triggers command processing.
static CommandMgr & instance()
CommandMgr is a singleton class.
static std::string SUPPORTED_SERVICE
Supported service.
static bool EMULATE_AGENT_RESPONSE
Emulation flag.
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 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.
Represents HTTP response with JSON content.
Represents HTTP POST request with JSON body.
This file contains several functions and constants that are used for handling commands and responses ...
Dhcp4Hooks Hooks
Definition dhcp4_srv.cc:213
const int CONTROL_RESULT_ERROR
Status code indicating a general failure.
ConstElementPtr createAnswer()
Creates a standard config/command level success answer message (i.e.
boost::shared_ptr< const Element > ConstElementPtr
Definition data.h:30
boost::shared_ptr< Element > ElementPtr
Definition data.h:29
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