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