| #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/core/file_posix.hpp> |
| #include <boost/beast/http/fields.hpp> |
| #include <nlohmann/json.hpp> |
| |
| #include <filesystem> |
| #include <fstream> |
| #include <random> |
| #include <system_error> |
| |
| 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::optional<UserSubscription> newSub = |
| UserSubscription::fromJson(elem); |
| |
| if (!newSub) |
| { |
| BMCWEB_LOG_ERROR("Problem reading subscription " |
| "from persistent store"); |
| continue; |
| } |
| |
| BMCWEB_LOG_DEBUG("Restored subscription: {} {}", |
| newSub->id, newSub->customText); |
| |
| EventServiceStore::getInstance() |
| .subscriptionsConfigMap.emplace( |
| newSub->id, |
| std::make_shared<UserSubscription>( |
| std::move(*newSub))); |
| } |
| } |
| 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::filesystem::path path(filename); |
| path = path.parent_path(); |
| if (!path.empty()) |
| { |
| std::error_code ecDir; |
| std::filesystem::create_directories(path, ecDir); |
| if (ecDir) |
| { |
| BMCWEB_LOG_CRITICAL("Can't create persistent folders {}", |
| ecDir.message()); |
| return; |
| } |
| } |
| boost::beast::file_posix persistentFile; |
| boost::system::error_code ec; |
| persistentFile.open(filename, boost::beast::file_mode::write, ec); |
| if (ec) |
| { |
| BMCWEB_LOG_CRITICAL("Unable to store persistent data to file {}", |
| ec.message()); |
| return; |
| } |
| |
| // 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, ec); |
| if (ec) |
| { |
| BMCWEB_LOG_CRITICAL("Failed to set filesystem permissions {}", |
| ec.message()); |
| return; |
| } |
| 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) |
| { |
| if (it.second == nullptr) |
| { |
| continue; |
| } |
| 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["OriginResources"] = subValue.originResources; |
| subscription["ResourceTypes"] = subValue.resourceTypes; |
| subscription["SubscriptionType"] = subValue.subscriptionType; |
| subscription["MetricReportDefinitions"] = |
| subValue.metricReportDefinitions; |
| subscription["VerifyCertificate"] = subValue.verifyCertificate; |
| |
| subscriptions.emplace_back(std::move(subscription)); |
| } |
| std::string out = nlohmann::json(data).dump( |
| -1, ' ', true, nlohmann::json::error_handler_t::replace); |
| persistentFile.write(out.data(), out.size(), ec); |
| if (ec) |
| { |
| BMCWEB_LOG_ERROR("Failed to write file {}", ec.message()); |
| } |
| } |
| |
| std::string systemUuid; |
| }; |
| |
| inline ConfigFile& getConfig() |
| { |
| static ConfigFile f; |
| return f; |
| } |
| |
| } // namespace persistent_data |