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