| #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_ |