xref: /openbmc/bmcweb/include/persistent_data.hpp (revision 4b712a29debc1a0860cc04850b262203cad402a5)
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                 const nlohmann::json::object_t* obj =
65                     data.get_ptr<nlohmann::json::object_t*>();
66                 if (obj == nullptr)
67                 {
68                     return;
69                 }
70                 for (const auto& item : *obj)
71                 {
72                     if (item.first == "revision")
73                     {
74                         fileRevision = 0;
75 
76                         const uint64_t* uintPtr =
77                             item.second.get_ptr<const uint64_t*>();
78                         if (uintPtr == nullptr)
79                         {
80                             BMCWEB_LOG_ERROR("Failed to read revision flag");
81                         }
82                         else
83                         {
84                             fileRevision = *uintPtr;
85                         }
86                     }
87                     else if (item.first == "system_uuid")
88                     {
89                         const std::string* jSystemUuid =
90                             item.second.get_ptr<const std::string*>();
91                         if (jSystemUuid != nullptr)
92                         {
93                             systemUuid = *jSystemUuid;
94                         }
95                     }
96                     else if (item.first == "auth_config")
97                     {
98                         SessionStore::getInstance()
99                             .getAuthMethodsConfig()
100                             .fromJson(item.second);
101                     }
102                     else if (item.first == "sessions")
103                     {
104                         for (const auto& elem : item.second)
105                         {
106                             std::shared_ptr<UserSession> newSession =
107                                 UserSession::fromJson(elem);
108 
109                             if (newSession == nullptr)
110                             {
111                                 BMCWEB_LOG_ERROR("Problem reading session "
112                                                  "from persistent store");
113                                 continue;
114                             }
115 
116                             BMCWEB_LOG_DEBUG("Restored session: {} {} {}",
117                                              newSession->csrfToken,
118                                              newSession->uniqueId,
119                                              newSession->sessionToken);
120                             SessionStore::getInstance().authTokens.emplace(
121                                 newSession->sessionToken, newSession);
122                         }
123                     }
124                     else if (item.first == "timeout")
125                     {
126                         const int64_t* jTimeout =
127                             item.second.get_ptr<const int64_t*>();
128                         if (jTimeout == nullptr)
129                         {
130                             BMCWEB_LOG_DEBUG(
131                                 "Problem reading session timeout value");
132                             continue;
133                         }
134                         std::chrono::seconds sessionTimeoutInseconds(*jTimeout);
135                         BMCWEB_LOG_DEBUG("Restored Session Timeout: {}",
136                                          sessionTimeoutInseconds.count());
137                         SessionStore::getInstance().updateSessionTimeout(
138                             sessionTimeoutInseconds);
139                     }
140                     else if (item.first == "eventservice_config")
141                     {
142                         const nlohmann::json::object_t* esobj =
143                             item.second
144                                 .get_ptr<const nlohmann::json::object_t*>();
145                         if (esobj == nullptr)
146                         {
147                             BMCWEB_LOG_DEBUG(
148                                 "Problem reading EventService value");
149                             continue;
150                         }
151 
152                         EventServiceStore::getInstance()
153                             .getEventServiceConfig()
154                             .fromJson(*esobj);
155                     }
156                     else if (item.first == "subscriptions")
157                     {
158                         for (const auto& elem : item.second)
159                         {
160                             std::optional<UserSubscription> newSub =
161                                 UserSubscription::fromJson(elem);
162 
163                             if (!newSub)
164                             {
165                                 BMCWEB_LOG_ERROR("Problem reading subscription "
166                                                  "from persistent store");
167                                 continue;
168                             }
169 
170                             BMCWEB_LOG_DEBUG("Restored subscription: {} {}",
171                                              newSub->id, newSub->customText);
172 
173                             boost::container::flat_map<
174                                 std::string, UserSubscription>& configMap =
175                                 EventServiceStore::getInstance()
176                                     .subscriptionsConfigMap;
177                             configMap.emplace(newSub->id, *newSub);
178                         }
179                     }
180                     else
181                     {
182                         // Do nothing in the case of extra fields.  We may have
183                         // cases where fields are added in the future, and we
184                         // want to at least attempt to gracefully support
185                         // downgrades in that case, even if we don't officially
186                         // support it
187                     }
188                 }
189             }
190         }
191         bool needWrite = false;
192 
193         if (systemUuid.empty())
194         {
195             systemUuid = bmcweb::getRandomUUID();
196             needWrite = true;
197         }
198         if (fileRevision < jsonRevision)
199         {
200             needWrite = true;
201         }
202         // write revision changes or system uuid changes immediately
203         if (needWrite)
204         {
205             writeData();
206         }
207     }
208 
209     void writeData()
210     {
211         std::ofstream persistentFile(filename);
212 
213         // set the permission of the file to 640
214         std::filesystem::perms permission =
215             std::filesystem::perms::owner_read |
216             std::filesystem::perms::owner_write |
217             std::filesystem::perms::group_read;
218         std::filesystem::permissions(filename, permission);
219         const AuthConfigMethods& c =
220             SessionStore::getInstance().getAuthMethodsConfig();
221         const auto& eventServiceConfig =
222             EventServiceStore::getInstance().getEventServiceConfig();
223         nlohmann::json::object_t data;
224         nlohmann::json& authConfig = data["auth_config"];
225 
226         authConfig["XToken"] = c.xtoken;
227         authConfig["Cookie"] = c.cookie;
228         authConfig["SessionToken"] = c.sessionToken;
229         authConfig["BasicAuth"] = c.basic;
230         authConfig["TLS"] = c.tls;
231         authConfig["TLSStrict"] = c.tlsStrict;
232         authConfig["TLSCommonNameParseMode"] =
233             static_cast<int>(c.mTLSCommonNameParsingMode);
234 
235         nlohmann::json& eventserviceConfig = data["eventservice_config"];
236         eventserviceConfig["ServiceEnabled"] = eventServiceConfig.enabled;
237         eventserviceConfig["DeliveryRetryAttempts"] =
238             eventServiceConfig.retryAttempts;
239         eventserviceConfig["DeliveryRetryIntervalSeconds"] =
240             eventServiceConfig.retryTimeoutInterval;
241 
242         data["system_uuid"] = systemUuid;
243         data["revision"] = jsonRevision;
244         data["timeout"] = SessionStore::getInstance().getTimeoutInSeconds();
245 
246         nlohmann::json& sessions = data["sessions"];
247         sessions = nlohmann::json::array();
248         for (const auto& p : SessionStore::getInstance().authTokens)
249         {
250             if (p.second->sessionType != persistent_data::SessionType::Basic &&
251                 p.second->sessionType !=
252                     persistent_data::SessionType::MutualTLS)
253             {
254                 nlohmann::json::object_t session;
255                 session["unique_id"] = p.second->uniqueId;
256                 session["session_token"] = p.second->sessionToken;
257                 session["username"] = p.second->username;
258                 session["csrf_token"] = p.second->csrfToken;
259                 session["client_ip"] = p.second->clientIp;
260                 const std::optional<std::string>& clientId = p.second->clientId;
261                 if (clientId)
262                 {
263                     session["client_id"] = *clientId;
264                 }
265                 sessions.emplace_back(std::move(session));
266             }
267         }
268         nlohmann::json& subscriptions = data["subscriptions"];
269         subscriptions = nlohmann::json::array();
270         for (const auto& it :
271              EventServiceStore::getInstance().subscriptionsConfigMap)
272         {
273             const UserSubscription& subValue = it.second;
274             if (subValue.subscriptionType == "SSE")
275             {
276                 BMCWEB_LOG_DEBUG("The subscription type is SSE, so skipping.");
277                 continue;
278             }
279             nlohmann::json::object_t headers;
280             for (const boost::beast::http::fields::value_type& header :
281                  subValue.httpHeaders)
282             {
283                 // Note, these are technically copies because nlohmann doesn't
284                 // support key lookup by std::string_view.  At least the
285                 // following code can use move
286                 // https://github.com/nlohmann/json/issues/1529
287                 std::string name(header.name_string());
288                 headers[std::move(name)] = header.value();
289             }
290 
291             nlohmann::json::object_t subscription;
292 
293             subscription["Id"] = subValue.id;
294             subscription["Context"] = subValue.customText;
295             subscription["DeliveryRetryPolicy"] = subValue.retryPolicy;
296             subscription["Destination"] = subValue.destinationUrl;
297             subscription["EventFormatType"] = subValue.eventFormatType;
298             subscription["HttpHeaders"] = std::move(headers);
299             subscription["MessageIds"] = subValue.registryMsgIds;
300             subscription["Protocol"] = subValue.protocol;
301             subscription["RegistryPrefixes"] = subValue.registryPrefixes;
302             subscription["OriginResources"] = subValue.originResources;
303             subscription["ResourceTypes"] = subValue.resourceTypes;
304             subscription["SubscriptionType"] = subValue.subscriptionType;
305             subscription["MetricReportDefinitions"] =
306                 subValue.metricReportDefinitions;
307             subscription["VerifyCertificate"] = subValue.verifyCertificate;
308 
309             subscriptions.emplace_back(std::move(subscription));
310         }
311         persistentFile << data;
312     }
313 
314     std::string systemUuid;
315 };
316 
317 inline ConfigFile& getConfig()
318 {
319     static ConfigFile f;
320     return f;
321 }
322 
323 } // namespace persistent_data
324