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
ConfigFile()27 ConfigFile()
28 {
29 readData();
30 }
31
~ConfigFile()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
readData()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::shared_ptr<UserSubscription> newSubscription =
161 UserSubscription::fromJson(elem);
162
163 if (newSubscription == nullptr)
164 {
165 BMCWEB_LOG_ERROR("Problem reading subscription "
166 "from persistent store");
167 continue;
168 }
169
170 BMCWEB_LOG_DEBUG("Restored subscription: {} {}",
171 newSubscription->id,
172 newSubscription->customText);
173 EventServiceStore::getInstance()
174 .subscriptionsConfigMap.emplace(
175 newSubscription->id, newSubscription);
176 }
177 }
178 else
179 {
180 // Do nothing in the case of extra fields. We may have
181 // cases where fields are added in the future, and we
182 // want to at least attempt to gracefully support
183 // downgrades in that case, even if we don't officially
184 // support it
185 }
186 }
187 }
188 }
189 bool needWrite = false;
190
191 if (systemUuid.empty())
192 {
193 systemUuid = bmcweb::getRandomUUID();
194 needWrite = true;
195 }
196 if (fileRevision < jsonRevision)
197 {
198 needWrite = true;
199 }
200 // write revision changes or system uuid changes immediately
201 if (needWrite)
202 {
203 writeData();
204 }
205 }
206
writeData()207 void writeData()
208 {
209 std::ofstream persistentFile(filename);
210
211 // set the permission of the file to 640
212 std::filesystem::perms permission =
213 std::filesystem::perms::owner_read |
214 std::filesystem::perms::owner_write |
215 std::filesystem::perms::group_read;
216 std::filesystem::permissions(filename, permission);
217 const auto& c = SessionStore::getInstance().getAuthMethodsConfig();
218 const auto& eventServiceConfig =
219 EventServiceStore::getInstance().getEventServiceConfig();
220 nlohmann::json::object_t data;
221 nlohmann::json& authConfig = data["auth_config"];
222
223 authConfig["XToken"] = c.xtoken;
224 authConfig["Cookie"] = c.cookie;
225 authConfig["SessionToken"] = c.sessionToken;
226 authConfig["BasicAuth"] = c.basic;
227 authConfig["TLS"] = c.tls;
228
229 nlohmann::json& eventserviceConfig = data["eventservice_config"];
230 eventserviceConfig["ServiceEnabled"] = eventServiceConfig.enabled;
231 eventserviceConfig["DeliveryRetryAttempts"] =
232 eventServiceConfig.retryAttempts;
233 eventserviceConfig["DeliveryRetryIntervalSeconds"] =
234 eventServiceConfig.retryTimeoutInterval;
235
236 data["system_uuid"] = systemUuid;
237 data["revision"] = jsonRevision;
238 data["timeout"] = SessionStore::getInstance().getTimeoutInSeconds();
239
240 nlohmann::json& sessions = data["sessions"];
241 sessions = nlohmann::json::array();
242 for (const auto& p : SessionStore::getInstance().authTokens)
243 {
244 if (p.second->persistence !=
245 persistent_data::PersistenceType::SINGLE_REQUEST)
246 {
247 nlohmann::json::object_t session;
248 session["unique_id"] = p.second->uniqueId;
249 session["session_token"] = p.second->sessionToken;
250 session["username"] = p.second->username;
251 session["csrf_token"] = p.second->csrfToken;
252 session["client_ip"] = p.second->clientIp;
253 const std::optional<std::string>& clientId = p.second->clientId;
254 if (clientId)
255 {
256 session["client_id"] = *clientId;
257 }
258 sessions.emplace_back(std::move(session));
259 }
260 }
261 nlohmann::json& subscriptions = data["subscriptions"];
262 subscriptions = nlohmann::json::array();
263 for (const auto& it :
264 EventServiceStore::getInstance().subscriptionsConfigMap)
265 {
266 std::shared_ptr<UserSubscription> subValue = it.second;
267 if (subValue->subscriptionType == "SSE")
268 {
269 BMCWEB_LOG_DEBUG("The subscription type is SSE, so skipping.");
270 continue;
271 }
272 nlohmann::json::object_t headers;
273 for (const boost::beast::http::fields::value_type& header :
274 subValue->httpHeaders)
275 {
276 // Note, these are technically copies because nlohmann doesn't
277 // support key lookup by std::string_view. At least the
278 // following code can use move
279 // https://github.com/nlohmann/json/issues/1529
280 std::string name(header.name_string());
281 headers[std::move(name)] = header.value();
282 }
283
284 nlohmann::json::object_t subscription;
285
286 subscription["Id"] = subValue->id;
287 subscription["Context"] = subValue->customText;
288 subscription["DeliveryRetryPolicy"] = subValue->retryPolicy;
289 subscription["Destination"] = subValue->destinationUrl;
290 subscription["EventFormatType"] = subValue->eventFormatType;
291 subscription["HttpHeaders"] = std::move(headers);
292 subscription["MessageIds"] = subValue->registryMsgIds;
293 subscription["Protocol"] = subValue->protocol;
294 subscription["RegistryPrefixes"] = subValue->registryPrefixes;
295 subscription["ResourceTypes"] = subValue->resourceTypes;
296 subscription["SubscriptionType"] = subValue->subscriptionType;
297 subscription["MetricReportDefinitions"] =
298 subValue->metricReportDefinitions;
299
300 subscriptions.emplace_back(std::move(subscription));
301 }
302 persistentFile << data;
303 }
304
305 std::string systemUuid;
306 };
307
getConfig()308 inline ConfigFile& getConfig()
309 {
310 static ConfigFile f;
311 return f;
312 }
313
314 } // namespace persistent_data
315