blob: ca4330f2d6f8f68fdcb0c4b8bff8a07ff5ffc643 [file] [log] [blame]
#ifndef THIRD_PARTY_GBMCWEB_INCLUDE_PERSISTENT_DATA_H_
#define THIRD_PARTY_GBMCWEB_INCLUDE_PERSISTENT_DATA_H_
#include <chrono> // NOLINT
#include <cstdint>
#include <filesystem> // NOLINT
#include <fstream>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#ifdef GOOGLE3_GBMCWEB_BUILD
#include "absl/base/no_destructor.h"
#endif
#include "boost/beast/http/fields.hpp" // NOLINT
#include "boost/uuid/uuid.hpp" // NOLINT
#include "boost/uuid/uuid_generators.hpp" // NOLINT
#include "boost/uuid/uuid_io.hpp" // NOLINT
#include "logging.hpp"
#include "event_service_store.hpp"
#include "request_stats.hpp"
#include "sessions.hpp"
#include <nlohmann/json.hpp>
#include "managed_store.hpp"
#ifdef UNIT_TEST_BUILD
#include "test/g3/mock_managed_store.hpp" // NOLINT
#endif
namespace persistent_data {
class ConfigFile {
uint64_t json_revision_ = 1;
public:
// todo(ed) should read this from a fixed location somewhere, not CWD
static constexpr const char* kFileName = "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 persistent_file(kFileName);
uint64_t file_revision = 0;
if (persistent_file.is_open()) {
// call with exceptions disabled
auto data = nlohmann::json::parse(persistent_file, nullptr, false);
if (data.is_discarded()) {
BMCWEB_LOG_ERROR << "Error parsing persistent data in json file.";
} else {
for (const auto& item : data.items()) {
if (item.key() == "revision") {
file_revision = 0;
const uint64_t* uint_ptr = item.value().get_ptr<const uint64_t*>();
if (uint_ptr == nullptr) {
BMCWEB_LOG_ERROR << "Failed to read revision flag";
} else {
file_revision = *uint_ptr;
}
} else if (item.key() == "system_uuid") {
const std::string* j_system_uuid =
item.value().get_ptr<const std::string*>();
if (j_system_uuid != nullptr) {
systemUuid = *j_system_uuid;
}
} else if (item.key() == "auth_config") {
SessionStore::getInstance().getAuthMethodsConfig().fromJson(
item.value());
} else if (item.key() == "sessions") {
for (const auto& elem : item.value()) {
std::shared_ptr<UserSession> new_session =
UserSession::fromJson(elem);
if (new_session == nullptr) {
BMCWEB_LOG_ERROR << "Problem reading session "
"from persistent store";
continue;
}
BMCWEB_LOG_DEBUG << "Restored session: " << new_session->csrfToken
<< " " << new_session->uniqueId << " "
<< new_session->sessionToken;
SessionStore::getInstance().authTokens.emplace(
new_session->sessionToken, new_session);
}
} else if (item.key() == "timeout") {
const int64_t* j_timeout = item.value().get_ptr<int64_t*>();
if (j_timeout == nullptr) {
BMCWEB_LOG_DEBUG << "Problem reading session timeout value";
continue;
}
std::chrono::seconds session_timeout_in_seconds(*j_timeout);
BMCWEB_LOG_DEBUG << "Restored Session Timeout: "
<< session_timeout_in_seconds.count();
SessionStore::getInstance().updateSessionTimeout(
session_timeout_in_seconds);
} else if (item.key() == "eventservice_config") {
EventServiceStore::getInstance().getEventServiceConfig().fromJson(
item.value());
} else if (item.key() == "subscriptions") {
for (const auto& elem : item.value()) {
std::shared_ptr<UserSubscription> new_subscription =
UserSubscription::fromJson(elem);
if (new_subscription == nullptr) {
BMCWEB_LOG_ERROR << "Problem reading subscription "
"from persistent store";
continue;
}
BMCWEB_LOG_DEBUG
<< "Restored subscription: " << new_subscription->id << " "
<< new_subscription->customText;
EventServiceStore::getInstance().subscriptionsConfigMap.emplace(
new_subscription->id, new_subscription);
}
} else if (item.key() == "stateful") {
BMCWEB_LOG_STATEFUL_ALWAYS << "=> ConfigFile: stateful: "
<< item.value().dump();
this->isStatefulEnabled =
item.value().get<nlohmann::json::boolean_t>();
} else if (item.key() == "request_stats") {
BMCWEB_LOG_STATEFUL_ALWAYS << "=> ConfigFile: request_stats: "
<< item.value().dump();
this->isRequestStatsEnabled =
item.value().get<nlohmann::json::boolean_t>();
} else if (item.key() == "stateful_logs") {
BMCWEB_LOG_STATEFUL_ALWAYS << "=> ConfigFile: stateful_logs: "
<< item.value().dump();
this->isStatefulLogsEnabled =
item.value().get<nlohmann::json::boolean_t>();
} 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 need_write = false;
if (systemUuid.empty()) {
systemUuid = boost::uuids::to_string(boost::uuids::random_generator()());
need_write = true;
}
if (file_revision < json_revision_) {
need_write = true;
}
// write revision changes or system uuid changes immediately
if (need_write) {
writeData();
}
}
void writeData() {
std::ofstream persistent_file(kFileName);
// 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(kFileName, permission);
const auto& c = SessionStore::getInstance().getAuthMethodsConfig();
const auto& event_service_config =
EventServiceStore::getInstance().getEventServiceConfig();
nlohmann::json::object_t data;
nlohmann::json& auth_config = data["auth_config"];
auth_config["XToken"] = c.xtoken;
auth_config["Cookie"] = c.cookie;
auth_config["SessionToken"] = c.sessionToken;
auth_config["BasicAuth"] = c.basic;
auth_config["TLS"] = c.tls;
nlohmann::json& eventservice_config = data["eventservice_config"];
eventservice_config["ServiceEnabled"] = event_service_config.enabled;
eventservice_config["DeliveryRetryAttempts"] =
event_service_config.retryAttempts;
eventservice_config["DeliveryRetryIntervalSeconds"] =
event_service_config.retryTimeoutInterval;
// export the managed_store_config:
if (managedStore::GetManagedObjectStore() != nullptr) {
data["managed_store_config"] =
managedStore::GetManagedObjectStore()->getConfig().toJson();
data["stateful"] =
managedStore::GetManagedObjectStore()->getConfig().isEnabled;
BMCWEB_LOG_STATEFUL_ALWAYS << "=> managed_store_config: "
<< data["managed_store_config"].dump();
}
{
data["request_stats_config"] =
managedStore::RequestStatsStore::instance().getConfig().toJson();
data["request_stats"] = managedStore::RequestStatsStore::isEnabled();
BMCWEB_LOG_STATEFUL_ALWAYS << "=> request_stats_config: "
<< data["request_stats_config"].dump();
}
data["system_uuid"] = systemUuid;
data["revision"] = json_revision_;
data["timeout"] = SessionStore::getInstance().getTimeoutInSeconds();
nlohmann::json& sessions = data["sessions"];
sessions = nlohmann::json::array();
for (const auto& p : SessionStore::getInstance().authTokens) {
if (p.second->persistence !=
persistent_data::PersistenceType::SINGLE_REQUEST) {
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;
if (p.second->clientId) {
session["client_id"] = *p.second->clientId;
}
sessions.push_back(std::move(session));
}
}
nlohmann::json& subscriptions = data["subscriptions"];
subscriptions = nlohmann::json::array();
for (const auto& it :
EventServiceStore::getInstance().subscriptionsConfigMap) {
std::shared_ptr<UserSubscription> sub_value = it.second;
if (sub_value->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 :
sub_value->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"] = sub_value->id;
subscription["Context"] = sub_value->customText;
subscription["DeliveryRetryPolicy"] = sub_value->retryPolicy;
subscription["Destination"] = sub_value->destinationUrl;
subscription["EventFormatType"] = sub_value->eventFormatType;
subscription["HttpHeaders"] = std::move(headers);
subscription["MessageIds"] = sub_value->registryMsgIds;
subscription["Protocol"] = sub_value->protocol;
subscription["RegistryPrefixes"] = sub_value->registryPrefixes;
subscription["ResourceTypes"] = sub_value->resourceTypes;
subscription["SubscriptionType"] = sub_value->subscriptionType;
subscription["MetricReportDefinitions"] =
sub_value->metricReportDefinitions;
subscriptions.push_back(std::move(subscription));
}
persistent_file << data;
}
std::string systemUuid; // NOLINT
// 'stateful' enable/disable
std::optional<bool> isStatefulEnabled; // NOLINT
// 'request stats' enable/disable
std::optional<bool> isRequestStatsEnabled; // NOLINT
// 'stateful_logs' enable/disable
std::optional<bool> isStatefulLogsEnabled; // NOLINT
};
inline ConfigFile& getConfig() {
#ifdef GOOGLE3_GBMCWEB_BUILD
static absl::NoDestructor<ConfigFile> f;
return *f;
#else
static ConfigFile f; // NOLINT
return f;
#endif
}
} // namespace persistent_data
#endif // THIRD_PARTY_GBMCWEB_INCLUDE_PERSISTENT_DATA_H_