// Copyright (C) 2020 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. #include #include #include #include using namespace isc; using namespace isc::data; using namespace isc::dhcp; using namespace isc::util; using namespace std; namespace isc { namespace http { BasicHttpAuthClient::BasicHttpAuthClient(const std::string& user, const std::string& password, const isc::data::ConstElementPtr& user_context) : user_(user), password_(password) { if (user_context) { setContext(user_context); } } ElementPtr BasicHttpAuthClient::toElement() const { ElementPtr result = Element::createMap(); // Set user-context contextToElement(result); // Set user result->set("user", Element::create(user_)); // Set password result->set("password", Element::create(password_)); return (result); } void BasicHttpAuthConfig::add(const std::string& user, const std::string& password, const ConstElementPtr& user_context) { BasicHttpAuth basic_auth(user, password); list_.push_back(BasicHttpAuthClient(user, password, user_context)); map_[basic_auth.getCredential()] = user; } void BasicHttpAuthConfig::clear() { list_.clear(); map_.clear(); } bool BasicHttpAuthConfig::empty() const { return (map_.empty()); } ElementPtr BasicHttpAuthConfig::toElement() const { ElementPtr result = Element::createMap(); // Set user-context contextToElement(result); // Set type result->set("type", Element::create(string("basic"))); // Set realm result->set("realm", Element::create(getRealm())); // Set clients ElementPtr clients = Element::createList(); for (auto client : list_) { clients->add(client.toElement()); } result->set("clients", clients); return (result); } void BasicHttpAuthConfig::parse(const ConstElementPtr& config) { if (!config) { return; } if (config->getType() != Element::map) { isc_throw(DhcpConfigError, "authentication must be a map (" << config->getPosition() << ")"); } // Get and verify the type. ConstElementPtr type = config->get("type"); if (!type) { isc_throw(DhcpConfigError, "type is required in authentication (" << config->getPosition() << ")"); } if (type->getType() != Element::string) { isc_throw(DhcpConfigError, "type is must be a string (" << type->getPosition() << ")"); } if (type->stringValue() != "basic") { isc_throw(DhcpConfigError, "only basic HTTP authentication is " << "supported: type is '" << type->stringValue() << "' not 'basic' (" << type->getPosition() << ")"); } // Get the realm. ConstElementPtr realm = config->get("realm"); if (realm) { if (realm->getType() != Element::string) { isc_throw(DhcpConfigError, "realm is must be a string (" << realm->getPosition() << ")"); } setRealm(realm->stringValue()); } // Get user context ConstElementPtr user_context_cfg = config->get("user-context"); if (user_context_cfg) { if (user_context_cfg->getType() != Element::map) { isc_throw(DhcpConfigError, "user-context must be a map (" << user_context_cfg->getPosition() << ")"); } setContext(user_context_cfg); } // Get clients. ConstElementPtr clients = config->get("clients"); if (!clients) { return; } if (clients->getType() != Element::list) { isc_throw(DhcpConfigError, "clients must be a list (" << clients->getPosition() << ")"); } // Iterate on clients. for (auto client : clients->listValue()) { if (client->getType() != Element::map) { isc_throw(DhcpConfigError, "clients items must be maps (" << client->getPosition() << ")"); } // user ConstElementPtr user_cfg = client->get("user"); if (!user_cfg) { isc_throw(DhcpConfigError, "user is required in clients items (" << client->getPosition() << ")"); } if (user_cfg->getType() != Element::string) { isc_throw(DhcpConfigError, "user must be a string (" << user_cfg->getPosition() << ")"); } string user = user_cfg->stringValue(); if (user.empty()) { isc_throw(DhcpConfigError, "user must be not be empty (" << user_cfg->getPosition() << ")"); } if (user.find(':') != string::npos) { isc_throw(DhcpConfigError, "user must not contain a ':': '" << user << "' (" << user_cfg->getPosition() << ")"); } // password string password; ConstElementPtr password_cfg = client->get("password"); if (password_cfg) { if (password_cfg->getType() != Element::string) { isc_throw(DhcpConfigError, "password must be a string (" << password_cfg->getPosition() << ")"); } password = password_cfg->stringValue(); } // user context ConstElementPtr user_context = client->get("user-context"); if (user_context) { if (user_context->getType() != Element::map) { isc_throw(DhcpConfigError, "user-context must be a map (" << user_context->getPosition() << ")"); } } // add it. try { add(user, password, user_context); } catch (const std::exception& ex) { isc_throw(DhcpConfigError, ex.what() << " (" << client->getPosition() << ")"); } } } HttpResponseJsonPtr BasicHttpAuthConfig::checkAuth(const HttpResponseCreator& creator, const HttpRequestPtr& request) const { const BasicHttpAuthMap& credentials = getCredentialMap(); bool authentic = false; if (credentials.empty()) { authentic = true; } else try { string value = request->getHeaderValue("Authorization"); // Trim space characters. value = str::trim(value); if (value.size() < 8) { isc_throw(BadValue, "header content is too short"); } // Get the authentication scheme which must be "basic". string scheme = value.substr(0, 5); str::lowercase(scheme); if (scheme != "basic") { isc_throw(BadValue, "not basic authentication"); } // Skip the authentication scheme name and space characters. value = value.substr(5); value = str::trim(value); // Verify the credential is in the list. const auto it = credentials.find(value); if (it != credentials.end()) { LOG_INFO(http_logger, HTTP_CLIENT_REQUEST_AUTHORIZED) .arg(it->second); authentic = true; } else { LOG_INFO(http_logger, HTTP_CLIENT_REQUEST_NOT_AUTHORIZED); authentic = false; } } catch (const HttpMessageNonExistingHeader&) { LOG_INFO(http_logger, HTTP_CLIENT_REQUEST_NO_AUTH_HEADER); } catch (const BadValue& ex) { LOG_INFO(http_logger, HTTP_CLIENT_REQUEST_BAD_AUTH_HEADER) .arg(ex.what()); } if (authentic) { return (HttpResponseJsonPtr()); } const string& realm = getRealm(); const string& scheme = "Basic"; HttpResponsePtr response = creator.createStockHttpResponse(request, HttpStatusCode::UNAUTHORIZED); response->reset(); response->context()->headers_.push_back( HttpHeaderContext("WWW-Authenticate", scheme + " realm=\"" + realm + "\"")); response->finalize(); return (boost::dynamic_pointer_cast(response)); } } // end of namespace isc::http } // end of namespace isc