| #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 "request_stats.hpp" | |
| #include "sessions.hpp" | |
| #include <nlohmann/json.hpp> | |
| #include "managed_store.hpp" | |
| 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() == "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(); | |
| 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; | |
| // 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)); | |
| } | |
| } | |
| 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_ |