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