Kea  2.5.2
basic_auth_config.cc
Go to the documentation of this file.
1 // Copyright (C) 2020-2022 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>
10 #include <http/basic_auth_config.h>
11 #include <util/file_utilities.h>
12 #include <util/strutil.h>
13 
14 using namespace isc;
15 using namespace isc::data;
16 using namespace isc::dhcp;
17 using namespace isc::util;
18 using namespace std;
19 
20 namespace isc {
21 namespace http {
22 
23 BasicHttpAuthClient::BasicHttpAuthClient(const std::string& user,
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 
48  ElementPtr result = Element::createMap();
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 
72 void
73 BasicHttpAuthConfig::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 
86 void
88  list_.clear();
89  map_.clear();
90 }
91 
92 bool
94  return (map_.empty());
95 }
96 
97 string
98 BasicHttpAuthConfig::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 
121  ElementPtr result = Element::createMap();
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
136  ElementPtr clients = Element::createList();
137  for (auto client : list_) {
138  clients->add(client.toElement());
139  }
140  result->set("clients", clients);
141 
142  return (result);
143 }
144 
145 void
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 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  const auto 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.
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.
const BasicHttpAuthMap & getCredentialMap() const
Returns the credential and user id map.
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.
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 & 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
const std::string & getRealm() const
Returns the realm.
Definition: auth_config.h:39
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:26
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:24
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:78
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:27
isc::log::Logger auth_logger("auth")
Defines the logger used by the HTTP authentication.
Definition: auth_log.h:18
string getContent(const string &file_name)
Get the content of a regular file.
void lowercase(std::string &text)
Lowercase String.
Definition: strutil.h:152
string trim(const string &instring)
Trim Leading and Trailing Spaces.
Definition: strutil.cc:53
Definition: edns.h:19
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.