blob: 0f14143676ada62a3bca73753ff55b358168805e [file] [log] [blame]
#pragma once
#include "event_service_store.hpp"
#include "http_request.hpp"
#include "http_response.hpp"
#include "ossl_random.hpp"
#include "sessions.hpp"
#include <boost/beast/http/fields.hpp>
#include <nlohmann/json.hpp>
#include <filesystem>
#include <fstream>
#include <random>
namespace persistent_data
{
class ConfigFile
{
uint64_t jsonRevision = 1;
public:
// todo(ed) should read this from a fixed location somewhere, not CWD
static constexpr const char* filename = "bmcweb_persistent_data.json";
ConfigFile()
{
readData();
}
~ConfigFile()
{
// Make sure we aren't writing stale sessions
persistent_data::SessionStore::getInstance().applySessionTimeouts();
if (persistent_data::SessionStore::getInstance().needsWrite())
{
writeData();
}
}
ConfigFile(const ConfigFile&) = delete;
ConfigFile(ConfigFile&&) = delete;
ConfigFile& operator=(const ConfigFile&) = delete;
ConfigFile& operator=(ConfigFile&&) = delete;
// TODO(ed) this should really use protobuf, or some other serialization
// library, but adding another dependency is somewhat outside the scope of
// this application for the moment
void readData()
{
std::ifstream persistentFile(filename);
uint64_t fileRevision = 0;
if (persistentFile.is_open())
{
// call with exceptions disabled
auto data = nlohmann::json::parse(persistentFile, nullptr, false);
if (data.is_discarded())
{
BMCWEB_LOG_ERROR("Error parsing persistent data in json file.");
}
else
{
const nlohmann::json::object_t* obj =
data.get_ptr<nlohmann::json::object_t*>();
if (obj == nullptr)
{
return;
}
for (const auto& item : *obj)
{
if (item.first == "revision")
{
fileRevision = 0;
const uint64_t* uintPtr =
item.second.get_ptr<const uint64_t*>();
if (uintPtr == nullptr)
{
BMCWEB_LOG_ERROR("Failed to read revision flag");
}
else
{
fileRevision = *uintPtr;
}
}
else if (item.first == "system_uuid")
{
const std::string* jSystemUuid =
item.second.get_ptr<const std::string*>();
if (jSystemUuid != nullptr)
{
systemUuid = *jSystemUuid;
}
}
else if (item.first == "auth_config")
{
SessionStore::getInstance()
.getAuthMethodsConfig()
.fromJson(item.second);
}
else if (item.first == "sessions")
{
for (const auto& elem : item.second)
{
std::shared_ptr<UserSession> newSession =
UserSession::fromJson(elem);
if (newSession == nullptr)
{
BMCWEB_LOG_ERROR("Problem reading session "
"from persistent store");
continue;
}
BMCWEB_LOG_DEBUG("Restored session: {} {} {}",
newSession->csrfToken,
newSession->uniqueId,
newSession->sessionToken);
SessionStore::getInstance().authTokens.emplace(
newSession->sessionToken, newSession);
}
}
else if (item.first == "timeout")
{
const int64_t* jTimeout =
item.second.get_ptr<const int64_t*>();
if (jTimeout == nullptr)
{
BMCWEB_LOG_DEBUG(
"Problem reading session timeout value");
continue;
}
std::chrono::seconds sessionTimeoutInseconds(*jTimeout);
BMCWEB_LOG_DEBUG("Restored Session Timeout: {}",
sessionTimeoutInseconds.count());
SessionStore::getInstance().updateSessionTimeout(
sessionTimeoutInseconds);
}
else if (item.first == "eventservice_config")
{
const nlohmann::json::object_t* esobj =
item.second
.get_ptr<const nlohmann::json::object_t*>();
if (esobj == nullptr)
{
BMCWEB_LOG_DEBUG(
"Problem reading EventService value");
continue;
}
EventServiceStore::getInstance()
.getEventServiceConfig()
.fromJson(*esobj);
}
else if (item.first == "subscriptions")
{
for (const auto& elem : item.second)
{
std::shared_ptr<UserSubscription> newSubscription =
UserSubscription::fromJson(elem);
if (newSubscription == nullptr)
{
BMCWEB_LOG_ERROR("Problem reading subscription "
"from persistent store");
continue;
}
BMCWEB_LOG_DEBUG("Restored subscription: {} {}",
newSubscription->id,
newSubscription->customText);
EventServiceStore::getInstance()
.subscriptionsConfigMap.emplace(
newSubscription->id, newSubscription);
}
}
else
{
// Do nothing in the case of extra fields. We may have
// cases where fields are added in the future, and we
// want to at least attempt to gracefully support
// downgrades in that case, even if we don't officially
// support it
}
}
}
}
bool needWrite = false;
if (systemUuid.empty())
{
systemUuid = bmcweb::getRandomUUID();
needWrite = true;
}
if (fileRevision < jsonRevision)
{
needWrite = true;
}
// write revision changes or system uuid changes immediately
if (needWrite)
{
writeData();
}
}
void writeData()
{
std::ofstream persistentFile(filename);
// set the permission of the file to 640
std::filesystem::perms permission =
std::filesystem::perms::owner_read |
std::filesystem::perms::owner_write |
std::filesystem::perms::group_read;
std::filesystem::permissions(filename, permission);
const AuthConfigMethods& c =
SessionStore::getInstance().getAuthMethodsConfig();
const auto& eventServiceConfig =
EventServiceStore::getInstance().getEventServiceConfig();
nlohmann::json::object_t data;
nlohmann::json& authConfig = data["auth_config"];
authConfig["XToken"] = c.xtoken;
authConfig["Cookie"] = c.cookie;
authConfig["SessionToken"] = c.sessionToken;
authConfig["BasicAuth"] = c.basic;
authConfig["TLS"] = c.tls;
authConfig["TLSStrict"] = c.tlsStrict;
authConfig["TLSCommonNameParseMode"] =
static_cast<int>(c.mTLSCommonNameParsingMode);
nlohmann::json& eventserviceConfig = data["eventservice_config"];
eventserviceConfig["ServiceEnabled"] = eventServiceConfig.enabled;
eventserviceConfig["DeliveryRetryAttempts"] =
eventServiceConfig.retryAttempts;
eventserviceConfig["DeliveryRetryIntervalSeconds"] =
eventServiceConfig.retryTimeoutInterval;
data["system_uuid"] = systemUuid;
data["revision"] = jsonRevision;
data["timeout"] = SessionStore::getInstance().getTimeoutInSeconds();
nlohmann::json& sessions = data["sessions"];
sessions = nlohmann::json::array();
for (const auto& p : SessionStore::getInstance().authTokens)
{
if (p.second->sessionType != persistent_data::SessionType::Basic &&
p.second->sessionType !=
persistent_data::SessionType::MutualTLS)
{
nlohmann::json::object_t session;
session["unique_id"] = p.second->uniqueId;
session["session_token"] = p.second->sessionToken;
session["username"] = p.second->username;
session["csrf_token"] = p.second->csrfToken;
session["client_ip"] = p.second->clientIp;
const std::optional<std::string>& clientId = p.second->clientId;
if (clientId)
{
session["client_id"] = *clientId;
}
sessions.emplace_back(std::move(session));
}
}
nlohmann::json& subscriptions = data["subscriptions"];
subscriptions = nlohmann::json::array();
for (const auto& it :
EventServiceStore::getInstance().subscriptionsConfigMap)
{
const UserSubscription& subValue = *it.second;
if (subValue.subscriptionType == "SSE")
{
BMCWEB_LOG_DEBUG("The subscription type is SSE, so skipping.");
continue;
}
nlohmann::json::object_t headers;
for (const boost::beast::http::fields::value_type& header :
subValue.httpHeaders)
{
// Note, these are technically copies because nlohmann doesn't
// support key lookup by std::string_view. At least the
// following code can use move
// https://github.com/nlohmann/json/issues/1529
std::string name(header.name_string());
headers[std::move(name)] = header.value();
}
nlohmann::json::object_t subscription;
subscription["Id"] = subValue.id;
subscription["Context"] = subValue.customText;
subscription["DeliveryRetryPolicy"] = subValue.retryPolicy;
subscription["Destination"] = subValue.destinationUrl;
subscription["EventFormatType"] = subValue.eventFormatType;
subscription["HttpHeaders"] = std::move(headers);
subscription["MessageIds"] = subValue.registryMsgIds;
subscription["Protocol"] = subValue.protocol;
subscription["RegistryPrefixes"] = subValue.registryPrefixes;
subscription["ResourceTypes"] = subValue.resourceTypes;
subscription["SubscriptionType"] = subValue.subscriptionType;
subscription["MetricReportDefinitions"] =
subValue.metricReportDefinitions;
subscription["VerifyCertificate"] = subValue.verifyCertificate;
subscriptions.emplace_back(std::move(subscription));
}
persistentFile << data;
}
std::string systemUuid;
};
inline ConfigFile& getConfig()
{
static ConfigFile f;
return f;
}
} // namespace persistent_data