152cc112dSEd Tanous #pragma once
252cc112dSEd Tanous 
33ccb3adbSEd Tanous #include "event_service_store.hpp"
43ccb3adbSEd Tanous #include "http_request.hpp"
53ccb3adbSEd Tanous #include "http_response.hpp"
62c6ffdb0SEd Tanous #include "ossl_random.hpp"
73ccb3adbSEd Tanous #include "sessions.hpp"
83ccb3adbSEd Tanous 
9601c71aeSEd Tanous #include <boost/beast/http/fields.hpp>
1052cc112dSEd Tanous #include <nlohmann/json.hpp>
1152cc112dSEd Tanous 
1252cc112dSEd Tanous #include <filesystem>
1352cc112dSEd Tanous #include <fstream>
1452cc112dSEd Tanous #include <random>
1552cc112dSEd Tanous 
1652cc112dSEd Tanous namespace persistent_data
1752cc112dSEd Tanous {
1852cc112dSEd Tanous 
1952cc112dSEd Tanous class ConfigFile
2052cc112dSEd Tanous {
2152cc112dSEd Tanous     uint64_t jsonRevision = 1;
2252cc112dSEd Tanous 
2352cc112dSEd Tanous   public:
2452cc112dSEd Tanous     // todo(ed) should read this from a fixed location somewhere, not CWD
2552cc112dSEd Tanous     static constexpr const char* filename = "bmcweb_persistent_data.json";
2652cc112dSEd Tanous 
2752cc112dSEd Tanous     ConfigFile()
2852cc112dSEd Tanous     {
2952cc112dSEd Tanous         readData();
3052cc112dSEd Tanous     }
3152cc112dSEd Tanous 
3252cc112dSEd Tanous     ~ConfigFile()
3352cc112dSEd Tanous     {
3483cf8189SGunnar Mills         // Make sure we aren't writing stale sessions
3583cf8189SGunnar Mills         persistent_data::SessionStore::getInstance().applySessionTimeouts();
3652cc112dSEd Tanous         if (persistent_data::SessionStore::getInstance().needsWrite())
3752cc112dSEd Tanous         {
3852cc112dSEd Tanous             writeData();
3952cc112dSEd Tanous         }
4052cc112dSEd Tanous     }
4152cc112dSEd Tanous 
42ecd6a3a2SEd Tanous     ConfigFile(const ConfigFile&) = delete;
43ecd6a3a2SEd Tanous     ConfigFile(ConfigFile&&) = delete;
44ecd6a3a2SEd Tanous     ConfigFile& operator=(const ConfigFile&) = delete;
45ecd6a3a2SEd Tanous     ConfigFile& operator=(ConfigFile&&) = delete;
46ecd6a3a2SEd Tanous 
4752cc112dSEd Tanous     // TODO(ed) this should really use protobuf, or some other serialization
4852cc112dSEd Tanous     // library, but adding another dependency is somewhat outside the scope of
4952cc112dSEd Tanous     // this application for the moment
5052cc112dSEd Tanous     void readData()
5152cc112dSEd Tanous     {
5252cc112dSEd Tanous         std::ifstream persistentFile(filename);
5352cc112dSEd Tanous         uint64_t fileRevision = 0;
5452cc112dSEd Tanous         if (persistentFile.is_open())
5552cc112dSEd Tanous         {
5652cc112dSEd Tanous             // call with exceptions disabled
5752cc112dSEd Tanous             auto data = nlohmann::json::parse(persistentFile, nullptr, false);
5852cc112dSEd Tanous             if (data.is_discarded())
5952cc112dSEd Tanous             {
60*62598e31SEd Tanous                 BMCWEB_LOG_ERROR("Error parsing persistent data in json file.");
6152cc112dSEd Tanous             }
6252cc112dSEd Tanous             else
6352cc112dSEd Tanous             {
6452cc112dSEd Tanous                 for (const auto& item : data.items())
6552cc112dSEd Tanous                 {
6652cc112dSEd Tanous                     if (item.key() == "revision")
6752cc112dSEd Tanous                     {
6852cc112dSEd Tanous                         fileRevision = 0;
6952cc112dSEd Tanous 
7052cc112dSEd Tanous                         const uint64_t* uintPtr =
7152cc112dSEd Tanous                             item.value().get_ptr<const uint64_t*>();
7252cc112dSEd Tanous                         if (uintPtr == nullptr)
7352cc112dSEd Tanous                         {
74*62598e31SEd Tanous                             BMCWEB_LOG_ERROR("Failed to read revision flag");
7552cc112dSEd Tanous                         }
7652cc112dSEd Tanous                         else
7752cc112dSEd Tanous                         {
7852cc112dSEd Tanous                             fileRevision = *uintPtr;
7952cc112dSEd Tanous                         }
8052cc112dSEd Tanous                     }
8152cc112dSEd Tanous                     else if (item.key() == "system_uuid")
8252cc112dSEd Tanous                     {
8352cc112dSEd Tanous                         const std::string* jSystemUuid =
8452cc112dSEd Tanous                             item.value().get_ptr<const std::string*>();
8552cc112dSEd Tanous                         if (jSystemUuid != nullptr)
8652cc112dSEd Tanous                         {
8752cc112dSEd Tanous                             systemUuid = *jSystemUuid;
8852cc112dSEd Tanous                         }
8952cc112dSEd Tanous                     }
9052cc112dSEd Tanous                     else if (item.key() == "auth_config")
9152cc112dSEd Tanous                     {
9252cc112dSEd Tanous                         SessionStore::getInstance()
9352cc112dSEd Tanous                             .getAuthMethodsConfig()
9452cc112dSEd Tanous                             .fromJson(item.value());
9552cc112dSEd Tanous                     }
9652cc112dSEd Tanous                     else if (item.key() == "sessions")
9752cc112dSEd Tanous                     {
9852cc112dSEd Tanous                         for (const auto& elem : item.value())
9952cc112dSEd Tanous                         {
10052cc112dSEd Tanous                             std::shared_ptr<UserSession> newSession =
10152cc112dSEd Tanous                                 UserSession::fromJson(elem);
10252cc112dSEd Tanous 
10352cc112dSEd Tanous                             if (newSession == nullptr)
10452cc112dSEd Tanous                             {
105*62598e31SEd Tanous                                 BMCWEB_LOG_ERROR("Problem reading session "
106*62598e31SEd Tanous                                                  "from persistent store");
10752cc112dSEd Tanous                                 continue;
10852cc112dSEd Tanous                             }
10952cc112dSEd Tanous 
110*62598e31SEd Tanous                             BMCWEB_LOG_DEBUG("Restored session: {} {} {}",
111*62598e31SEd Tanous                                              newSession->csrfToken,
112*62598e31SEd Tanous                                              newSession->uniqueId,
113*62598e31SEd Tanous                                              newSession->sessionToken);
11452cc112dSEd Tanous                             SessionStore::getInstance().authTokens.emplace(
11552cc112dSEd Tanous                                 newSession->sessionToken, newSession);
11652cc112dSEd Tanous                         }
11752cc112dSEd Tanous                     }
118f2a4a606SManojkiran Eda                     else if (item.key() == "timeout")
119f2a4a606SManojkiran Eda                     {
120f2a4a606SManojkiran Eda                         const int64_t* jTimeout =
121f2a4a606SManojkiran Eda                             item.value().get_ptr<int64_t*>();
122f2a4a606SManojkiran Eda                         if (jTimeout == nullptr)
123f2a4a606SManojkiran Eda                         {
124*62598e31SEd Tanous                             BMCWEB_LOG_DEBUG(
125*62598e31SEd Tanous                                 "Problem reading session timeout value");
126f2a4a606SManojkiran Eda                             continue;
127f2a4a606SManojkiran Eda                         }
128f2a4a606SManojkiran Eda                         std::chrono::seconds sessionTimeoutInseconds(*jTimeout);
129*62598e31SEd Tanous                         BMCWEB_LOG_DEBUG("Restored Session Timeout: {}",
130*62598e31SEd Tanous                                          sessionTimeoutInseconds.count());
131f2a4a606SManojkiran Eda                         SessionStore::getInstance().updateSessionTimeout(
132f2a4a606SManojkiran Eda                             sessionTimeoutInseconds);
133f2a4a606SManojkiran Eda                     }
13428afb49cSJunLin Chen                     else if (item.key() == "eventservice_config")
13528afb49cSJunLin Chen                     {
13628afb49cSJunLin Chen                         EventServiceStore::getInstance()
13728afb49cSJunLin Chen                             .getEventServiceConfig()
13828afb49cSJunLin Chen                             .fromJson(item.value());
13928afb49cSJunLin Chen                     }
14028afb49cSJunLin Chen                     else if (item.key() == "subscriptions")
14128afb49cSJunLin Chen                     {
14228afb49cSJunLin Chen                         for (const auto& elem : item.value())
14328afb49cSJunLin Chen                         {
14428afb49cSJunLin Chen                             std::shared_ptr<UserSubscription> newSubscription =
14528afb49cSJunLin Chen                                 UserSubscription::fromJson(elem);
14628afb49cSJunLin Chen 
14728afb49cSJunLin Chen                             if (newSubscription == nullptr)
14828afb49cSJunLin Chen                             {
149*62598e31SEd Tanous                                 BMCWEB_LOG_ERROR("Problem reading subscription "
150*62598e31SEd Tanous                                                  "from persistent store");
15128afb49cSJunLin Chen                                 continue;
15228afb49cSJunLin Chen                             }
15328afb49cSJunLin Chen 
154*62598e31SEd Tanous                             BMCWEB_LOG_DEBUG("Restored subscription: {} {}",
155*62598e31SEd Tanous                                              newSubscription->id,
156*62598e31SEd Tanous                                              newSubscription->customText);
15728afb49cSJunLin Chen                             EventServiceStore::getInstance()
15828afb49cSJunLin Chen                                 .subscriptionsConfigMap.emplace(
15928afb49cSJunLin Chen                                     newSubscription->id, newSubscription);
16028afb49cSJunLin Chen                         }
16128afb49cSJunLin Chen                     }
16252cc112dSEd Tanous                     else
16352cc112dSEd Tanous                     {
16452cc112dSEd Tanous                         // Do nothing in the case of extra fields.  We may have
16552cc112dSEd Tanous                         // cases where fields are added in the future, and we
16652cc112dSEd Tanous                         // want to at least attempt to gracefully support
16752cc112dSEd Tanous                         // downgrades in that case, even if we don't officially
16852cc112dSEd Tanous                         // support it
16952cc112dSEd Tanous                     }
17052cc112dSEd Tanous                 }
17152cc112dSEd Tanous             }
17252cc112dSEd Tanous         }
17352cc112dSEd Tanous         bool needWrite = false;
17452cc112dSEd Tanous 
17552cc112dSEd Tanous         if (systemUuid.empty())
17652cc112dSEd Tanous         {
1772c6ffdb0SEd Tanous             systemUuid = bmcweb::getRandomUUID();
17852cc112dSEd Tanous             needWrite = true;
17952cc112dSEd Tanous         }
18052cc112dSEd Tanous         if (fileRevision < jsonRevision)
18152cc112dSEd Tanous         {
18252cc112dSEd Tanous             needWrite = true;
18352cc112dSEd Tanous         }
18452cc112dSEd Tanous         // write revision changes or system uuid changes immediately
18552cc112dSEd Tanous         if (needWrite)
18652cc112dSEd Tanous         {
18752cc112dSEd Tanous             writeData();
18852cc112dSEd Tanous         }
18952cc112dSEd Tanous     }
19052cc112dSEd Tanous 
19152cc112dSEd Tanous     void writeData()
19252cc112dSEd Tanous     {
19352cc112dSEd Tanous         std::ofstream persistentFile(filename);
19452cc112dSEd Tanous 
19552cc112dSEd Tanous         // set the permission of the file to 640
19652cc112dSEd Tanous         std::filesystem::perms permission =
19752cc112dSEd Tanous             std::filesystem::perms::owner_read |
19852cc112dSEd Tanous             std::filesystem::perms::owner_write |
19952cc112dSEd Tanous             std::filesystem::perms::group_read;
20052cc112dSEd Tanous         std::filesystem::permissions(filename, permission);
2015fb91ba4SEd Tanous         const auto& c = SessionStore::getInstance().getAuthMethodsConfig();
20228afb49cSJunLin Chen         const auto& eventServiceConfig =
20328afb49cSJunLin Chen             EventServiceStore::getInstance().getEventServiceConfig();
2041476687dSEd Tanous         nlohmann::json::object_t data;
2051476687dSEd Tanous         nlohmann::json& authConfig = data["auth_config"];
20652cc112dSEd Tanous 
2071476687dSEd Tanous         authConfig["XToken"] = c.xtoken;
2081476687dSEd Tanous         authConfig["Cookie"] = c.cookie;
2091476687dSEd Tanous         authConfig["SessionToken"] = c.sessionToken;
2101476687dSEd Tanous         authConfig["BasicAuth"] = c.basic;
2111476687dSEd Tanous         authConfig["TLS"] = c.tls;
21228afb49cSJunLin Chen 
2131476687dSEd Tanous         nlohmann::json& eventserviceConfig = data["eventservice_config"];
2141476687dSEd Tanous         eventserviceConfig["ServiceEnabled"] = eventServiceConfig.enabled;
2151476687dSEd Tanous         eventserviceConfig["DeliveryRetryAttempts"] =
2161476687dSEd Tanous             eventServiceConfig.retryAttempts;
2171476687dSEd Tanous         eventserviceConfig["DeliveryRetryIntervalSeconds"] =
2181476687dSEd Tanous             eventServiceConfig.retryTimeoutInterval;
2191476687dSEd Tanous 
2201476687dSEd Tanous         data["system_uuid"] = systemUuid;
2211476687dSEd Tanous         data["revision"] = jsonRevision;
2221476687dSEd Tanous         data["timeout"] = SessionStore::getInstance().getTimeoutInSeconds();
2235fb91ba4SEd Tanous 
2245fb91ba4SEd Tanous         nlohmann::json& sessions = data["sessions"];
2255fb91ba4SEd Tanous         sessions = nlohmann::json::array();
2265fb91ba4SEd Tanous         for (const auto& p : SessionStore::getInstance().authTokens)
2275fb91ba4SEd Tanous         {
2285fb91ba4SEd Tanous             if (p.second->persistence !=
2295fb91ba4SEd Tanous                 persistent_data::PersistenceType::SINGLE_REQUEST)
2305fb91ba4SEd Tanous             {
2311476687dSEd Tanous                 nlohmann::json::object_t session;
2321476687dSEd Tanous                 session["unique_id"] = p.second->uniqueId;
2331476687dSEd Tanous                 session["session_token"] = p.second->sessionToken;
2341476687dSEd Tanous                 session["username"] = p.second->username;
2351476687dSEd Tanous                 session["csrf_token"] = p.second->csrfToken;
2361476687dSEd Tanous                 session["client_ip"] = p.second->clientIp;
237bb759e3aSEd Tanous                 if (p.second->clientId)
238bb759e3aSEd Tanous                 {
239bb759e3aSEd Tanous                     session["client_id"] = *p.second->clientId;
240bb759e3aSEd Tanous                 }
241b2ba3072SPatrick Williams                 sessions.emplace_back(std::move(session));
2425fb91ba4SEd Tanous             }
2435fb91ba4SEd Tanous         }
24428afb49cSJunLin Chen         nlohmann::json& subscriptions = data["subscriptions"];
24528afb49cSJunLin Chen         subscriptions = nlohmann::json::array();
24628afb49cSJunLin Chen         for (const auto& it :
24728afb49cSJunLin Chen              EventServiceStore::getInstance().subscriptionsConfigMap)
24828afb49cSJunLin Chen         {
24928afb49cSJunLin Chen             std::shared_ptr<UserSubscription> subValue = it.second;
25028afb49cSJunLin Chen             if (subValue->subscriptionType == "SSE")
25128afb49cSJunLin Chen             {
252*62598e31SEd Tanous                 BMCWEB_LOG_DEBUG("The subscription type is SSE, so skipping.");
25328afb49cSJunLin Chen                 continue;
25428afb49cSJunLin Chen             }
255601c71aeSEd Tanous             nlohmann::json::object_t headers;
256601c71aeSEd Tanous             for (const boost::beast::http::fields::value_type& header :
257601c71aeSEd Tanous                  subValue->httpHeaders)
258601c71aeSEd Tanous             {
259601c71aeSEd Tanous                 // Note, these are technically copies because nlohmann doesn't
260601c71aeSEd Tanous                 // support key lookup by std::string_view.  At least the
261601c71aeSEd Tanous                 // following code can use move
262601c71aeSEd Tanous                 // https://github.com/nlohmann/json/issues/1529
263601c71aeSEd Tanous                 std::string name(header.name_string());
264601c71aeSEd Tanous                 headers[std::move(name)] = header.value();
265601c71aeSEd Tanous             }
266601c71aeSEd Tanous 
2671476687dSEd Tanous             nlohmann::json::object_t subscription;
2681476687dSEd Tanous 
2691476687dSEd Tanous             subscription["Id"] = subValue->id;
2701476687dSEd Tanous             subscription["Context"] = subValue->customText;
2711476687dSEd Tanous             subscription["DeliveryRetryPolicy"] = subValue->retryPolicy;
2721476687dSEd Tanous             subscription["Destination"] = subValue->destinationUrl;
2731476687dSEd Tanous             subscription["EventFormatType"] = subValue->eventFormatType;
2741476687dSEd Tanous             subscription["HttpHeaders"] = std::move(headers);
2751476687dSEd Tanous             subscription["MessageIds"] = subValue->registryMsgIds;
2761476687dSEd Tanous             subscription["Protocol"] = subValue->protocol;
2771476687dSEd Tanous             subscription["RegistryPrefixes"] = subValue->registryPrefixes;
2781476687dSEd Tanous             subscription["ResourceTypes"] = subValue->resourceTypes;
2791476687dSEd Tanous             subscription["SubscriptionType"] = subValue->subscriptionType;
2801476687dSEd Tanous             subscription["MetricReportDefinitions"] =
2811476687dSEd Tanous                 subValue->metricReportDefinitions;
2821476687dSEd Tanous 
283b2ba3072SPatrick Williams             subscriptions.emplace_back(std::move(subscription));
28428afb49cSJunLin Chen         }
28552cc112dSEd Tanous         persistentFile << data;
28652cc112dSEd Tanous     }
28752cc112dSEd Tanous 
288e05aec50SEd Tanous     std::string systemUuid;
28952cc112dSEd Tanous };
29052cc112dSEd Tanous 
29152cc112dSEd Tanous inline ConfigFile& getConfig()
29252cc112dSEd Tanous {
29352cc112dSEd Tanous     static ConfigFile f;
29452cc112dSEd Tanous     return f;
29552cc112dSEd Tanous }
29652cc112dSEd Tanous 
29752cc112dSEd Tanous } // namespace persistent_data
298