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