1 #pragma once
2 
3 #include "event_service_store.hpp"
4 #include "http_request.hpp"
5 #include "http_response.hpp"
6 #include "ossl_random.hpp"
7 #include "sessions.hpp"
8 
9 #include <boost/beast/http/fields.hpp>
10 #include <nlohmann/json.hpp>
11 
12 #include <filesystem>
13 #include <fstream>
14 #include <random>
15 
16 namespace persistent_data
17 {
18 
19 class ConfigFile
20 {
21     uint64_t jsonRevision = 1;
22 
23   public:
24     // todo(ed) should read this from a fixed location somewhere, not CWD
25     static constexpr const char* filename = "bmcweb_persistent_data.json";
26 
27     ConfigFile()
28     {
29         readData();
30     }
31 
32     ~ConfigFile()
33     {
34         // Make sure we aren't writing stale sessions
35         persistent_data::SessionStore::getInstance().applySessionTimeouts();
36         if (persistent_data::SessionStore::getInstance().needsWrite())
37         {
38             writeData();
39         }
40     }
41 
42     ConfigFile(const ConfigFile&) = delete;
43     ConfigFile(ConfigFile&&) = delete;
44     ConfigFile& operator=(const ConfigFile&) = delete;
45     ConfigFile& operator=(ConfigFile&&) = delete;
46 
47     // TODO(ed) this should really use protobuf, or some other serialization
48     // library, but adding another dependency is somewhat outside the scope of
49     // this application for the moment
50     void readData()
51     {
52         std::ifstream persistentFile(filename);
53         uint64_t fileRevision = 0;
54         if (persistentFile.is_open())
55         {
56             // call with exceptions disabled
57             auto data = nlohmann::json::parse(persistentFile, nullptr, false);
58             if (data.is_discarded())
59             {
60                 BMCWEB_LOG_ERROR("Error parsing persistent data in json file.");
61             }
62             else
63             {
64                 for (const auto& item : data.items())
65                 {
66                     if (item.key() == "revision")
67                     {
68                         fileRevision = 0;
69 
70                         const uint64_t* uintPtr =
71                             item.value().get_ptr<const uint64_t*>();
72                         if (uintPtr == nullptr)
73                         {
74                             BMCWEB_LOG_ERROR("Failed to read revision flag");
75                         }
76                         else
77                         {
78                             fileRevision = *uintPtr;
79                         }
80                     }
81                     else if (item.key() == "system_uuid")
82                     {
83                         const std::string* jSystemUuid =
84                             item.value().get_ptr<const std::string*>();
85                         if (jSystemUuid != nullptr)
86                         {
87                             systemUuid = *jSystemUuid;
88                         }
89                     }
90                     else if (item.key() == "auth_config")
91                     {
92                         SessionStore::getInstance()
93                             .getAuthMethodsConfig()
94                             .fromJson(item.value());
95                     }
96                     else if (item.key() == "sessions")
97                     {
98                         for (const auto& elem : item.value())
99                         {
100                             std::shared_ptr<UserSession> newSession =
101                                 UserSession::fromJson(elem);
102 
103                             if (newSession == nullptr)
104                             {
105                                 BMCWEB_LOG_ERROR("Problem reading session "
106                                                  "from persistent store");
107                                 continue;
108                             }
109 
110                             BMCWEB_LOG_DEBUG("Restored session: {} {} {}",
111                                              newSession->csrfToken,
112                                              newSession->uniqueId,
113                                              newSession->sessionToken);
114                             SessionStore::getInstance().authTokens.emplace(
115                                 newSession->sessionToken, newSession);
116                         }
117                     }
118                     else if (item.key() == "timeout")
119                     {
120                         const int64_t* jTimeout =
121                             item.value().get_ptr<int64_t*>();
122                         if (jTimeout == nullptr)
123                         {
124                             BMCWEB_LOG_DEBUG(
125                                 "Problem reading session timeout value");
126                             continue;
127                         }
128                         std::chrono::seconds sessionTimeoutInseconds(*jTimeout);
129                         BMCWEB_LOG_DEBUG("Restored Session Timeout: {}",
130                                          sessionTimeoutInseconds.count());
131                         SessionStore::getInstance().updateSessionTimeout(
132                             sessionTimeoutInseconds);
133                     }
134                     else if (item.key() == "eventservice_config")
135                     {
136                         EventServiceStore::getInstance()
137                             .getEventServiceConfig()
138                             .fromJson(item.value());
139                     }
140                     else if (item.key() == "subscriptions")
141                     {
142                         for (const auto& elem : item.value())
143                         {
144                             std::shared_ptr<UserSubscription> newSubscription =
145                                 UserSubscription::fromJson(elem);
146 
147                             if (newSubscription == nullptr)
148                             {
149                                 BMCWEB_LOG_ERROR("Problem reading subscription "
150                                                  "from persistent store");
151                                 continue;
152                             }
153 
154                             BMCWEB_LOG_DEBUG("Restored subscription: {} {}",
155                                              newSubscription->id,
156                                              newSubscription->customText);
157                             EventServiceStore::getInstance()
158                                 .subscriptionsConfigMap.emplace(
159                                     newSubscription->id, newSubscription);
160                         }
161                     }
162                     else
163                     {
164                         // Do nothing in the case of extra fields.  We may have
165                         // cases where fields are added in the future, and we
166                         // want to at least attempt to gracefully support
167                         // downgrades in that case, even if we don't officially
168                         // support it
169                     }
170                 }
171             }
172         }
173         bool needWrite = false;
174 
175         if (systemUuid.empty())
176         {
177             systemUuid = bmcweb::getRandomUUID();
178             needWrite = true;
179         }
180         if (fileRevision < jsonRevision)
181         {
182             needWrite = true;
183         }
184         // write revision changes or system uuid changes immediately
185         if (needWrite)
186         {
187             writeData();
188         }
189     }
190 
191     void writeData()
192     {
193         std::ofstream persistentFile(filename);
194 
195         // set the permission of the file to 640
196         std::filesystem::perms permission =
197             std::filesystem::perms::owner_read |
198             std::filesystem::perms::owner_write |
199             std::filesystem::perms::group_read;
200         std::filesystem::permissions(filename, permission);
201         const auto& c = SessionStore::getInstance().getAuthMethodsConfig();
202         const auto& eventServiceConfig =
203             EventServiceStore::getInstance().getEventServiceConfig();
204         nlohmann::json::object_t data;
205         nlohmann::json& authConfig = data["auth_config"];
206 
207         authConfig["XToken"] = c.xtoken;
208         authConfig["Cookie"] = c.cookie;
209         authConfig["SessionToken"] = c.sessionToken;
210         authConfig["BasicAuth"] = c.basic;
211         authConfig["TLS"] = c.tls;
212 
213         nlohmann::json& eventserviceConfig = data["eventservice_config"];
214         eventserviceConfig["ServiceEnabled"] = eventServiceConfig.enabled;
215         eventserviceConfig["DeliveryRetryAttempts"] =
216             eventServiceConfig.retryAttempts;
217         eventserviceConfig["DeliveryRetryIntervalSeconds"] =
218             eventServiceConfig.retryTimeoutInterval;
219 
220         data["system_uuid"] = systemUuid;
221         data["revision"] = jsonRevision;
222         data["timeout"] = SessionStore::getInstance().getTimeoutInSeconds();
223 
224         nlohmann::json& sessions = data["sessions"];
225         sessions = nlohmann::json::array();
226         for (const auto& p : SessionStore::getInstance().authTokens)
227         {
228             if (p.second->persistence !=
229                 persistent_data::PersistenceType::SINGLE_REQUEST)
230             {
231                 nlohmann::json::object_t session;
232                 session["unique_id"] = p.second->uniqueId;
233                 session["session_token"] = p.second->sessionToken;
234                 session["username"] = p.second->username;
235                 session["csrf_token"] = p.second->csrfToken;
236                 session["client_ip"] = p.second->clientIp;
237                 const std::optional<std::string>& clientId = p.second->clientId;
238                 if (clientId)
239                 {
240                     session["client_id"] = *clientId;
241                 }
242                 sessions.emplace_back(std::move(session));
243             }
244         }
245         nlohmann::json& subscriptions = data["subscriptions"];
246         subscriptions = nlohmann::json::array();
247         for (const auto& it :
248              EventServiceStore::getInstance().subscriptionsConfigMap)
249         {
250             std::shared_ptr<UserSubscription> subValue = it.second;
251             if (subValue->subscriptionType == "SSE")
252             {
253                 BMCWEB_LOG_DEBUG("The subscription type is SSE, so skipping.");
254                 continue;
255             }
256             nlohmann::json::object_t headers;
257             for (const boost::beast::http::fields::value_type& header :
258                  subValue->httpHeaders)
259             {
260                 // Note, these are technically copies because nlohmann doesn't
261                 // support key lookup by std::string_view.  At least the
262                 // following code can use move
263                 // https://github.com/nlohmann/json/issues/1529
264                 std::string name(header.name_string());
265                 headers[std::move(name)] = header.value();
266             }
267 
268             nlohmann::json::object_t subscription;
269 
270             subscription["Id"] = subValue->id;
271             subscription["Context"] = subValue->customText;
272             subscription["DeliveryRetryPolicy"] = subValue->retryPolicy;
273             subscription["Destination"] = subValue->destinationUrl;
274             subscription["EventFormatType"] = subValue->eventFormatType;
275             subscription["HttpHeaders"] = std::move(headers);
276             subscription["MessageIds"] = subValue->registryMsgIds;
277             subscription["Protocol"] = subValue->protocol;
278             subscription["RegistryPrefixes"] = subValue->registryPrefixes;
279             subscription["ResourceTypes"] = subValue->resourceTypes;
280             subscription["SubscriptionType"] = subValue->subscriptionType;
281             subscription["MetricReportDefinitions"] =
282                 subValue->metricReportDefinitions;
283 
284             subscriptions.emplace_back(std::move(subscription));
285         }
286         persistentFile << data;
287     }
288 
289     std::string systemUuid;
290 };
291 
292 inline ConfigFile& getConfig()
293 {
294     static ConfigFile f;
295     return f;
296 }
297 
298 } // namespace persistent_data
299