Kea 2.7.8
basic_auth_config.cc
Go to the documentation of this file.
1// Copyright (C) 2020-2025 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 <http/auth_log.h>
12#include <util/filesystem.h>
13#include <util/str.h>
14
15using namespace isc;
16using namespace isc::data;
17using namespace isc::dhcp;
18using namespace isc::util;
19using namespace std;
20
21namespace isc {
22namespace http {
23
25 const std::string& password,
26 const isc::data::ConstElementPtr& user_context)
27 : user_(user), user_file_(""), password_(password),
28 password_file_(""), password_file_only_(false) {
29 if (user_context) {
30 setContext(user_context);
31 }
32}
33
35 const std::string& user_file,
36 const std::string& password,
37 const std::string& password_file,
38 bool password_file_only,
39 const isc::data::ConstElementPtr& user_context)
40 : user_(user), user_file_(user_file), password_(password),
41 password_file_(password_file), password_file_only_(password_file_only) {
42 if (user_context) {
43 setContext(user_context);
44 }
45}
46
50
51 // Set user-context
52 contextToElement(result);
53
54 // Set password file or password.
55 if (!password_file_.empty()) {
56 result->set("password-file", Element::create(password_file_));
57 } else {
58 result->set("password", Element::create(password_));
59 }
60
61 // Set user-file or user.
62 if (!password_file_only_) {
63 if (!user_file_.empty()) {
64 result->set("user-file", Element::create(user_file_));
65 } else {
66 result->set("user", Element::create(user_));
67 }
68 }
69
70 return (result);
71}
72
73void
74BasicHttpAuthConfig::add(const std::string& user,
75 const std::string& user_file,
76 const std::string& password,
77 const std::string& password_file,
78 bool password_file_only,
79 const ConstElementPtr& user_context) {
80 BasicHttpAuth basic_auth(user, password);
81 list_.push_back(BasicHttpAuthClient(user, user_file, password,
82 password_file, password_file_only,
83 user_context));
84 map_[basic_auth.getCredential()] = user;
85}
86
87void
89 list_.clear();
90 map_.clear();
91}
92
93bool
95 return (map_.empty());
96}
97
98string
99BasicHttpAuthConfig::getFileContent(const std::string& file_name) const {
100 // Build path.
101 string path = getDirectory();
102 // Add a trailing '/' if the last character is not already a '/'.
103 if (path.empty() || (path[path.size() - 1] != '/')) {
104 path += "/";
105 }
106 // Don't add a second '/'.
107 if (file_name.empty() || (file_name[0] != '/')) {
108 path += file_name;
109 } else {
110 path += file_name.substr(1);
111 }
112
113 try {
114 return (file::getContent(path));
115 } catch (const isc::BadValue& ex) {
117 }
118}
119
123
124 // Set user-context
125 contextToElement(result);
126
127 // Set type
128 result->set("type", Element::create(string("basic")));
129
130 // Set realm
131 result->set("realm", Element::create(getRealm()));
132
133 // Set directory.
134 result->set("directory", Element::create(getDirectory()));
135
136 // Set clients
138 for (auto const& client : list_) {
139 clients->add(client.toElement());
140 }
141 result->set("clients", clients);
142
143 return (result);
144}
145
146void
148 if (!config) {
149 return;
150 }
151 if (config->getType() != Element::map) {
152 isc_throw(DhcpConfigError, "authentication must be a map ("
153 << config->getPosition() << ")");
154 }
155
156 // Get and verify the type.
157 ConstElementPtr type = config->get("type");
158 if (!type) {
159 isc_throw(DhcpConfigError, "type is required in authentication ("
160 << config->getPosition() << ")");
161 }
162 if (type->getType() != Element::string) {
163 isc_throw(DhcpConfigError, "type must be a string ("
164 << type->getPosition() << ")");
165 }
166 if (type->stringValue() != "basic") {
167 isc_throw(DhcpConfigError, "only basic HTTP authentication is "
168 << "supported: type is '" << type->stringValue()
169 << "' not 'basic' (" << type->getPosition() << ")");
170 }
171
172 // Get the realm.
173 ConstElementPtr realm = config->get("realm");
174 if (realm) {
175 if (realm->getType() != Element::string) {
176 isc_throw(DhcpConfigError, "realm must be a string ("
177 << realm->getPosition() << ")");
178 }
179 setRealm(realm->stringValue());
180 }
181
182 // Get the directory.
183 ConstElementPtr directory = config->get("directory");
184 if (directory) {
185 if (directory->getType() != Element::string) {
186 isc_throw(DhcpConfigError, "directory must be a string ("
187 << directory->getPosition() << ")");
188 }
189 setDirectory(directory->stringValue());
190 }
191
192 // Get user context.
193 ConstElementPtr user_context_cfg = config->get("user-context");
194 if (user_context_cfg) {
195 if (user_context_cfg->getType() != Element::map) {
196 isc_throw(DhcpConfigError, "user-context must be a map ("
197 << user_context_cfg->getPosition() << ")");
198 }
199 setContext(user_context_cfg);
200 }
201
202 // Get clients.
203 ConstElementPtr clients = config->get("clients");
204 if (!clients) {
205 return;
206 }
207 if (clients->getType() != Element::list) {
208 isc_throw(DhcpConfigError, "clients must be a list ("
209 << clients->getPosition() << ")");
210 }
211
212 // Iterate on clients.
213 for (auto const& client : clients->listValue()) {
214 if (client->getType() != Element::map) {
215 isc_throw(DhcpConfigError, "clients items must be maps ("
216 << client->getPosition() << ")");
217 }
218
219 // password.
220 string password;
221 ConstElementPtr password_cfg = client->get("password");
222 if (password_cfg) {
223 if (password_cfg->getType() != Element::string) {
224 isc_throw(DhcpConfigError, "password must be a string ("
225 << password_cfg->getPosition() << ")");
226 }
227 password = password_cfg->stringValue();
228 try {
230 } catch (const DefaultCredential&) {
232 "password must not be a default one ("
233 << password_cfg->getPosition() << ")");
234 }
235 }
236
237 // password file.
238 string password_file;
239 ConstElementPtr password_file_cfg = client->get("password-file");
240 if (password_file_cfg) {
241 if (password_cfg) {
242 isc_throw(DhcpConfigError, "password ("
243 << password_cfg->getPosition()
244 << ") and password-file ("
245 << password_file_cfg->getPosition()
246 << ") are mutually exclusive");
247 }
248 if (password_file_cfg->getType() != Element::string) {
249 isc_throw(DhcpConfigError, "password-file must be a string ("
250 << password_file_cfg->getPosition() << ")");
251 }
252 password_file = password_file_cfg->stringValue();
253 }
254
255 ConstElementPtr user_cfg = client->get("user");
256 ConstElementPtr user_file_cfg = client->get("user-file");
257 bool password_file_only = false;
258 if (!user_cfg && !user_file_cfg) {
259 if (password_file_cfg) {
260 password_file_only = true;
261 } else {
262 isc_throw(DhcpConfigError, "user is required in clients "
263 << "items (" << client->getPosition() << ")");
264 }
265 }
266
267 // user.
268 string user;
269 if (user_cfg) {
270 if (user_file_cfg) {
271 isc_throw(DhcpConfigError, "user (" << user_cfg->getPosition()
272 << ") and user-file ("
273 << user_file_cfg->getPosition()
274 << ") are mutually exclusive");
275 }
276 if (user_cfg->getType() != Element::string) {
277 isc_throw(DhcpConfigError, "user must be a string ("
278 << user_cfg->getPosition() << ")");
279 }
280 user = user_cfg->stringValue();
281 if (user.empty()) {
282 isc_throw(DhcpConfigError, "user must not be empty ("
283 << user_cfg->getPosition() << ")");
284 }
285 if (user.find(':') != string::npos) {
286 isc_throw(DhcpConfigError, "user must not contain a ':': '"
287 << user << "' (" << user_cfg->getPosition() << ")");
288 }
289 }
290
291 // user file.
292 string user_file;
293 if (user_file_cfg) {
294 if (user_file_cfg->getType() != Element::string) {
295 isc_throw(DhcpConfigError, "user-file must be a string ("
296 << user_file_cfg->getPosition() << ")");
297 }
298 user_file = user_file_cfg->stringValue();
299 user = getFileContent(user_file);
300 if (user.empty()) {
301 isc_throw(DhcpConfigError, "user must not be empty "
302 << "from user-file '" << user_file << "' ("
303 << user_file_cfg->getPosition() << ")");
304 }
305 if (user.find(':') != string::npos) {
306 isc_throw(DhcpConfigError, "user must not contain a ':' "
307 << "from user-file '" << user_file << "' ("
308 << user_file_cfg->getPosition() << ")");
309 }
310 }
311
312 // Solve password file.
313 if (password_file_cfg) {
314 if (password_file_only) {
315 string content = getFileContent(password_file);
316 auto pos = content.find(':');
317 if (pos == string::npos) {
318 isc_throw(DhcpConfigError, "can't find the user id part "
319 << "in password-file '" << password_file << "' ("
320 << password_file_cfg->getPosition() << ")");
321 }
322 user = content.substr(0, pos);
323 password = content.substr(pos + 1);
324 } else {
325 password = getFileContent(password_file);
326 }
327 }
328
329 // user context.
330 ConstElementPtr user_context = client->get("user-context");
331 if (user_context) {
332 if (user_context->getType() != Element::map) {
333 isc_throw(DhcpConfigError, "user-context must be a map ("
334 << user_context->getPosition() << ")");
335 }
336 }
337
338 // add it.
339 try {
340 add(user, user_file, password, password_file, password_file_only,
341 user_context);
342 } catch (const std::exception& ex) {
343 isc_throw(DhcpConfigError, ex.what() << " ("
344 << client->getPosition() << ")");
345 }
346 }
347}
348
351 const HttpRequestPtr& request) const {
352 const BasicHttpAuthMap& credentials = getCredentialMap();
353 bool authentic = false;
354 if (credentials.empty()) {
355 authentic = true;
356 } else try {
357 string value = request->getHeaderValue("Authorization");
358 // Trim space characters.
359 value = str::trim(value);
360 if (value.size() < 8) {
361 isc_throw(BadValue, "header content is too short");
362 }
363 // Get the authentication scheme which must be "basic".
364 string scheme = value.substr(0, 5);
365 str::lowercase(scheme);
366 if (scheme != "basic") {
367 isc_throw(BadValue, "not basic authentication");
368 }
369 // Skip the authentication scheme name and space characters.
370 value = value.substr(5);
371 value = str::trim(value);
372 // Verify the credential is in the list.
373 auto const it = credentials.find(value);
374 if (it != credentials.end()) {
376 .arg(it->second);
378 request->setBasicAuth(it->second);
379 }
380 authentic = true;
381 } else {
383 authentic = false;
384 }
385 } catch (const HttpMessageNonExistingHeader&) {
387 } catch (const BadValue& ex) {
389 .arg(ex.what());
390 }
391 if (authentic) {
392 return (HttpResponseJsonPtr());
393 }
394 const string& realm = getRealm();
395 const string& scheme = "Basic";
396 HttpResponsePtr response =
398 response->reset();
399 response->context()->headers_.push_back(
400 HttpHeaderContext("WWW-Authenticate",
401 scheme + " realm=\"" + realm + "\""));
402 response->finalize();
403 return (boost::dynamic_pointer_cast<HttpResponseJson>(response));
404}
405
406} // end of namespace isc::http
407} // 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.
Exception thrown on attempt to use a default credential.
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.
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
boost::shared_ptr< HttpResponseJson > HttpResponseJsonPtr
Pointer to the HttpResponseJson object.
const isc::log::MessageID HTTP_CLIENT_REQUEST_BAD_AUTH_HEADER
const isc::log::MessageID HTTP_CLIENT_REQUEST_AUTHORIZED
boost::shared_ptr< HttpResponse > HttpResponsePtr
Pointer to the HttpResponse object.
Definition response.h:81
const isc::log::MessageID HTTP_CLIENT_REQUEST_NO_AUTH_HEADER
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:29
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.
static void check(const std::string &value)
Check if the value is a default credential.
void contextToElement(data::ElementPtr map) const
Merge unparse a user_context object.
void setContext(const data::ConstElementPtr &ctx)
Sets user context.