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