Kea 2.5.8
basic_auth_config.cc
Go to the documentation of this file.
1// Copyright (C) 2020-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
9#include <http/auth_log.h>
11#include <util/filesystem.h>
12#include <util/str.h>
13
14using namespace isc;
15using namespace isc::data;
16using namespace isc::dhcp;
17using namespace isc::util;
18using namespace std;
19
20namespace isc {
21namespace http {
22
24 const std::string& password,
25 const isc::data::ConstElementPtr& user_context)
26 : user_(user), user_file_(""), password_(password),
27 password_file_(""), password_file_only_(false) {
28 if (user_context) {
29 setContext(user_context);
30 }
31}
32
34 const std::string& user_file,
35 const std::string& password,
36 const std::string& password_file,
37 bool password_file_only,
38 const isc::data::ConstElementPtr& user_context)
39 : user_(user), user_file_(user_file), password_(password),
40 password_file_(password_file), password_file_only_(password_file_only) {
41 if (user_context) {
42 setContext(user_context);
43 }
44}
45
49
50 // Set user-context
51 contextToElement(result);
52
53 // Set password file or password.
54 if (!password_file_.empty()) {
55 result->set("password-file", Element::create(password_file_));
56 } else {
57 result->set("password", Element::create(password_));
58 }
59
60 // Set user-file or user.
61 if (!password_file_only_) {
62 if (!user_file_.empty()) {
63 result->set("user-file", Element::create(user_file_));
64 } else {
65 result->set("user", Element::create(user_));
66 }
67 }
68
69 return (result);
70}
71
72void
73BasicHttpAuthConfig::add(const std::string& user,
74 const std::string& user_file,
75 const std::string& password,
76 const std::string& password_file,
77 bool password_file_only,
78 const ConstElementPtr& user_context) {
79 BasicHttpAuth basic_auth(user, password);
80 list_.push_back(BasicHttpAuthClient(user, user_file, password,
81 password_file, password_file_only,
82 user_context));
83 map_[basic_auth.getCredential()] = user;
84}
85
86void
88 list_.clear();
89 map_.clear();
90}
91
92bool
94 return (map_.empty());
95}
96
97string
98BasicHttpAuthConfig::getFileContent(const std::string& file_name) const {
99 // Build path.
100 string path = getDirectory();
101 // Add a trailing '/' if the last character is not already a '/'.
102 if (path.empty() || (path[path.size() - 1] != '/')) {
103 path += "/";
104 }
105 // Don't add a second '/'.
106 if (file_name.empty() || (file_name[0] != '/')) {
107 path += file_name;
108 } else {
109 path += file_name.substr(1);
110 }
111
112 try {
113 return (file::getContent(path));
114 } catch (const isc::BadValue& ex) {
116 }
117}
118
122
123 // Set user-context
124 contextToElement(result);
125
126 // Set type
127 result->set("type", Element::create(string("basic")));
128
129 // Set realm
130 result->set("realm", Element::create(getRealm()));
131
132 // Set directory.
133 result->set("directory", Element::create(getDirectory()));
134
135 // Set clients
137 for (auto const& client : list_) {
138 clients->add(client.toElement());
139 }
140 result->set("clients", clients);
141
142 return (result);
143}
144
145void
147 if (!config) {
148 return;
149 }
150 if (config->getType() != Element::map) {
151 isc_throw(DhcpConfigError, "authentication must be a map ("
152 << config->getPosition() << ")");
153 }
154
155 // Get and verify the type.
156 ConstElementPtr type = config->get("type");
157 if (!type) {
158 isc_throw(DhcpConfigError, "type is required in authentication ("
159 << config->getPosition() << ")");
160 }
161 if (type->getType() != Element::string) {
162 isc_throw(DhcpConfigError, "type must be a string ("
163 << type->getPosition() << ")");
164 }
165 if (type->stringValue() != "basic") {
166 isc_throw(DhcpConfigError, "only basic HTTP authentication is "
167 << "supported: type is '" << type->stringValue()
168 << "' not 'basic' (" << type->getPosition() << ")");
169 }
170
171 // Get the realm.
172 ConstElementPtr realm = config->get("realm");
173 if (realm) {
174 if (realm->getType() != Element::string) {
175 isc_throw(DhcpConfigError, "realm must be a string ("
176 << realm->getPosition() << ")");
177 }
178 setRealm(realm->stringValue());
179 }
180
181 // Get the directory.
182 ConstElementPtr directory = config->get("directory");
183 if (directory) {
184 if (directory->getType() != Element::string) {
185 isc_throw(DhcpConfigError, "directory must be a string ("
186 << directory->getPosition() << ")");
187 }
188 setDirectory(directory->stringValue());
189 }
190
191 // Get user context.
192 ConstElementPtr user_context_cfg = config->get("user-context");
193 if (user_context_cfg) {
194 if (user_context_cfg->getType() != Element::map) {
195 isc_throw(DhcpConfigError, "user-context must be a map ("
196 << user_context_cfg->getPosition() << ")");
197 }
198 setContext(user_context_cfg);
199 }
200
201 // Get clients.
202 ConstElementPtr clients = config->get("clients");
203 if (!clients) {
204 return;
205 }
206 if (clients->getType() != Element::list) {
207 isc_throw(DhcpConfigError, "clients must be a list ("
208 << clients->getPosition() << ")");
209 }
210
211 // Iterate on clients.
212 for (auto const& client : clients->listValue()) {
213 if (client->getType() != Element::map) {
214 isc_throw(DhcpConfigError, "clients items must be maps ("
215 << client->getPosition() << ")");
216 }
217
218 // password.
219 string password;
220 ConstElementPtr password_cfg = client->get("password");
221 if (password_cfg) {
222 if (password_cfg->getType() != Element::string) {
223 isc_throw(DhcpConfigError, "password must be a string ("
224 << password_cfg->getPosition() << ")");
225 }
226 password = password_cfg->stringValue();
227 }
228
229 // password file.
230 string password_file;
231 ConstElementPtr password_file_cfg = client->get("password-file");
232 if (password_file_cfg) {
233 if (password_cfg) {
234 isc_throw(DhcpConfigError, "password ("
235 << password_cfg->getPosition()
236 << ") and password-file ("
237 << password_file_cfg->getPosition()
238 << ") are mutually exclusive");
239 }
240 if (password_file_cfg->getType() != Element::string) {
241 isc_throw(DhcpConfigError, "password-file must be a string ("
242 << password_file_cfg->getPosition() << ")");
243 }
244 password_file = password_file_cfg->stringValue();
245 }
246
247 ConstElementPtr user_cfg = client->get("user");
248 ConstElementPtr user_file_cfg = client->get("user-file");
249 bool password_file_only = false;
250 if (!user_cfg && !user_file_cfg) {
251 if (password_file_cfg) {
252 password_file_only = true;
253 } else {
254 isc_throw(DhcpConfigError, "user is required in clients "
255 << "items (" << client->getPosition() << ")");
256 }
257 }
258
259 // user.
260 string user;
261 if (user_cfg) {
262 if (user_file_cfg) {
263 isc_throw(DhcpConfigError, "user (" << user_cfg->getPosition()
264 << ") and user-file ("
265 << user_file_cfg->getPosition()
266 << ") are mutually exclusive");
267 }
268 if (user_cfg->getType() != Element::string) {
269 isc_throw(DhcpConfigError, "user must be a string ("
270 << user_cfg->getPosition() << ")");
271 }
272 user = user_cfg->stringValue();
273 if (user.empty()) {
274 isc_throw(DhcpConfigError, "user must not be empty ("
275 << user_cfg->getPosition() << ")");
276 }
277 if (user.find(':') != string::npos) {
278 isc_throw(DhcpConfigError, "user must not contain a ':': '"
279 << user << "' (" << user_cfg->getPosition() << ")");
280 }
281 }
282
283 // user file.
284 string user_file;
285 if (user_file_cfg) {
286 if (user_file_cfg->getType() != Element::string) {
287 isc_throw(DhcpConfigError, "user-file must be a string ("
288 << user_file_cfg->getPosition() << ")");
289 }
290 user_file = user_file_cfg->stringValue();
291 user = getFileContent(user_file);
292 if (user.empty()) {
293 isc_throw(DhcpConfigError, "user must not be empty "
294 << "from user-file '" << user_file << "' ("
295 << user_file_cfg->getPosition() << ")");
296 }
297 if (user.find(':') != string::npos) {
298 isc_throw(DhcpConfigError, "user must not contain a ':' "
299 << "from user-file '" << user_file << "' ("
300 << user_file_cfg->getPosition() << ")");
301 }
302 }
303
304 // Solve password file.
305 if (password_file_cfg) {
306 if (password_file_only) {
307 string content = getFileContent(password_file);
308 auto pos = content.find(':');
309 if (pos == string::npos) {
310 isc_throw(DhcpConfigError, "can't find the user id part "
311 << "in password-file '" << password_file << "' ("
312 << password_file_cfg->getPosition() << ")");
313 }
314 user = content.substr(0, pos);
315 password = content.substr(pos + 1);
316 } else {
317 password = getFileContent(password_file);
318 }
319 }
320
321 // user context.
322 ConstElementPtr user_context = client->get("user-context");
323 if (user_context) {
324 if (user_context->getType() != Element::map) {
325 isc_throw(DhcpConfigError, "user-context must be a map ("
326 << user_context->getPosition() << ")");
327 }
328 }
329
330 // add it.
331 try {
332 add(user, user_file, password, password_file, password_file_only,
333 user_context);
334 } catch (const std::exception& ex) {
335 isc_throw(DhcpConfigError, ex.what() << " ("
336 << client->getPosition() << ")");
337 }
338 }
339}
340
343 const HttpRequestPtr& request) const {
344 const BasicHttpAuthMap& credentials = getCredentialMap();
345 bool authentic = false;
346 if (credentials.empty()) {
347 authentic = true;
348 } else try {
349 string value = request->getHeaderValue("Authorization");
350 // Trim space characters.
351 value = str::trim(value);
352 if (value.size() < 8) {
353 isc_throw(BadValue, "header content is too short");
354 }
355 // Get the authentication scheme which must be "basic".
356 string scheme = value.substr(0, 5);
357 str::lowercase(scheme);
358 if (scheme != "basic") {
359 isc_throw(BadValue, "not basic authentication");
360 }
361 // Skip the authentication scheme name and space characters.
362 value = value.substr(5);
363 value = str::trim(value);
364 // Verify the credential is in the list.
365 auto const it = credentials.find(value);
366 if (it != credentials.end()) {
368 .arg(it->second);
370 request->setBasicAuth(it->second);
371 }
372 authentic = true;
373 } else {
375 authentic = false;
376 }
377 } catch (const HttpMessageNonExistingHeader&) {
379 } catch (const BadValue& ex) {
381 .arg(ex.what());
382 }
383 if (authentic) {
384 return (HttpResponseJsonPtr());
385 }
386 const string& realm = getRealm();
387 const string& scheme = "Basic";
388 HttpResponsePtr response =
390 response->reset();
391 response->context()->headers_.push_back(
392 HttpHeaderContext("WWW-Authenticate",
393 scheme + " realm=\"" + realm + "\""));
394 response->finalize();
395 return (boost::dynamic_pointer_cast<HttpResponseJson>(response));
396}
397
398} // end of namespace isc::http
399} // end of namespace isc
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
static ElementPtr create(const Position &pos=ZERO_POSITION())
Definition: data.cc:249
static ElementPtr createMap(const Position &pos=ZERO_POSITION())
Creates an empty MapElement type ElementPtr.
Definition: data.cc:304
static ElementPtr createList(const Position &pos=ZERO_POSITION())
Creates an empty ListElement type ElementPtr.
Definition: data.cc:299
To be removed. Please use ConfigError instead.
Basic HTTP authentication client configuration.
BasicHttpAuthClient(const std::string &user, const std::string &password, const isc::data::ConstElementPtr &user_context)
Constructor (legacy).
virtual isc::data::ElementPtr toElement() const
Unparses basic HTTP authentication client configuration.
std::string getFileContent(const std::string &file_name) const
Get the content of {directory}/{file-name} regular file.
virtual isc::data::ElementPtr toElement() const
Unparses basic HTTP authentication configuration.
void add(const std::string &user, const std::string &user_file, const std::string &password, const std::string &password_file, bool password_file_only=false, const isc::data::ConstElementPtr &user_context=isc::data::ConstElementPtr())
Add a client configuration.
void parse(const isc::data::ConstElementPtr &config)
Parses basic HTTP authentication configuration.
virtual isc::http::HttpResponseJsonPtr checkAuth(const isc::http::HttpResponseCreator &creator, const isc::http::HttpRequestPtr &request) const
Validate HTTP request.
virtual void clear()
Clear configuration.
virtual bool empty() const
Empty predicate.
const BasicHttpAuthMap & getCredentialMap() const
Returns the credential and user id map.
Represents a basic HTTP authentication.
Definition: basic_auth.h:21
const std::string & getCredential() const
Returns the credential (base64 of the UTF-8 secret).
Definition: basic_auth.h:43
const std::string & getRealm() const
Returns the realm.
Definition: auth_config.h:39
const std::string & getDirectory() const
Returns the common part for file paths (usually a directory).
Definition: auth_config.h:53
void setDirectory(const std::string &directory)
Set the common part for file paths (usually a directory).
Definition: auth_config.h:46
void setRealm(const std::string &realm)
Set the realm.
Definition: auth_config.h:32
Exception thrown when attempt is made to retrieve a non-existing header.
Definition: http_message.h:30
static bool recordBasicAuth_
Record basic auth.
Definition: request.h:258
Specifies an interface for classes creating HTTP responses from HTTP requests.
virtual HttpResponsePtr createStockHttpResponse(const HttpRequestPtr &request, const HttpStatusCode &status_code) const =0
Creates implementation specific HTTP response.
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
#define LOG_INFO(LOGGER, MESSAGE)
Macro to conveniently test info output and log it.
Definition: macros.h:20
boost::shared_ptr< const Element > ConstElementPtr
Definition: data.h:29
boost::shared_ptr< Element > ElementPtr
Definition: data.h:28
std::unordered_map< std::string, std::string > BasicHttpAuthMap
Type of basic HTTP authentication credential and user id map, e.g.
const isc::log::MessageID HTTP_CLIENT_REQUEST_NOT_AUTHORIZED
Definition: auth_messages.h:13
boost::shared_ptr< HttpResponseJson > HttpResponseJsonPtr
Pointer to the HttpResponseJson object.
Definition: response_json.h:27
const isc::log::MessageID HTTP_CLIENT_REQUEST_BAD_AUTH_HEADER
Definition: auth_messages.h:12
const isc::log::MessageID HTTP_CLIENT_REQUEST_AUTHORIZED
Definition: auth_messages.h:11
boost::shared_ptr< HttpResponse > HttpResponsePtr
Pointer to the HttpResponse object.
Definition: response.h:81
const isc::log::MessageID HTTP_CLIENT_REQUEST_NO_AUTH_HEADER
Definition: auth_messages.h:14
boost::shared_ptr< HttpRequest > HttpRequestPtr
Pointer to the HttpRequest object.
Definition: request.h:30
isc::log::Logger auth_logger("auth")
Defines the logger used by the HTTP authentication.
Definition: auth_log.h:18
string getContent(string const &file_name)
Get the content of a regular file.
Definition: filesystem.cc:32
void lowercase(string &text)
Convert string to lowercase.
Definition: str.cc:134
string trim(const string &input)
Trim leading and trailing spaces.
Definition: str.cc:32
Defines the logger used by the top-level component of kea-lfc.
void contextToElement(data::ElementPtr map) const
Merge unparse a user_context object.
Definition: user_context.cc:15
void setContext(const data::ConstElementPtr &ctx)
Sets user context.
Definition: user_context.h:30
HTTP header context.