1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 #pragma once
4
5 #include "event_service_store.hpp"
6 #include "logging.hpp"
7 #include "ossl_random.hpp"
8 #include "sessions.hpp"
9 // NOLINTNEXTLINE(misc-include-cleaner)
10 #include "utility.hpp"
11
12 #include <boost/beast/core/file_base.hpp>
13 #include <boost/beast/core/file_posix.hpp>
14 #include <boost/beast/http/fields.hpp>
15 #include <nlohmann/json.hpp>
16
17 #include <chrono>
18 #include <cstdint>
19 #include <filesystem>
20 #include <fstream>
21 #include <memory>
22 #include <optional>
23 #include <string>
24 #include <system_error>
25 #include <utility>
26
27 namespace persistent_data
28 {
29
30 class ConfigFile
31 {
32 uint64_t jsonRevision = 1;
33
34 public:
35 // todo(ed) should read this from a fixed location somewhere, not CWD
36 static constexpr const char* filename = "bmcweb_persistent_data.json";
37
ConfigFile()38 ConfigFile()
39 {
40 readData();
41 }
42
~ConfigFile()43 ~ConfigFile()
44 {
45 // Make sure we aren't writing stale sessions
46 persistent_data::SessionStore::getInstance().applySessionTimeouts();
47 if (persistent_data::SessionStore::getInstance().needsWrite())
48 {
49 writeData();
50 }
51 }
52
53 ConfigFile(const ConfigFile&) = delete;
54 ConfigFile(ConfigFile&&) = delete;
55 ConfigFile& operator=(const ConfigFile&) = delete;
56 ConfigFile& operator=(ConfigFile&&) = delete;
57
58 // TODO(ed) this should really use protobuf, or some other serialization
59 // library, but adding another dependency is somewhat outside the scope of
60 // this application for the moment
readData()61 void readData()
62 {
63 std::ifstream persistentFile(filename);
64 uint64_t fileRevision = 0;
65 if (persistentFile.is_open())
66 {
67 // call with exceptions disabled
68 auto data = nlohmann::json::parse(persistentFile, nullptr, false);
69 if (data.is_discarded())
70 {
71 BMCWEB_LOG_ERROR("Error parsing persistent data in json file.");
72 }
73 else
74 {
75 const nlohmann::json::object_t* obj =
76 data.get_ptr<nlohmann::json::object_t*>();
77 if (obj == nullptr)
78 {
79 return;
80 }
81 for (const auto& item : *obj)
82 {
83 if (item.first == "revision")
84 {
85 fileRevision = 0;
86
87 const uint64_t* uintPtr =
88 item.second.get_ptr<const uint64_t*>();
89 if (uintPtr == nullptr)
90 {
91 BMCWEB_LOG_ERROR("Failed to read revision flag");
92 }
93 else
94 {
95 fileRevision = *uintPtr;
96 }
97 }
98 else if (item.first == "system_uuid")
99 {
100 const std::string* jSystemUuid =
101 item.second.get_ptr<const std::string*>();
102 if (jSystemUuid != nullptr)
103 {
104 systemUuid = *jSystemUuid;
105 }
106 }
107 else if (item.first == "auth_config")
108 {
109 const nlohmann::json::object_t* jObj =
110 item.second
111 .get_ptr<const nlohmann::json::object_t*>();
112 if (jObj == nullptr)
113 {
114 continue;
115 }
116 SessionStore::getInstance()
117 .getAuthMethodsConfig()
118 .fromJson(*jObj);
119 }
120 else if (item.first == "sessions")
121 {
122 for (const auto& elem : item.second)
123 {
124 const nlohmann::json::object_t* jObj =
125 elem.get_ptr<const nlohmann::json::object_t*>();
126 if (jObj == nullptr)
127 {
128 continue;
129 }
130 std::shared_ptr<UserSession> newSession =
131 UserSession::fromJson(*jObj);
132
133 if (newSession == nullptr)
134 {
135 BMCWEB_LOG_ERROR("Problem reading session "
136 "from persistent store");
137 continue;
138 }
139
140 BMCWEB_LOG_DEBUG("Restored session: {} {} {}",
141 newSession->csrfToken,
142 newSession->uniqueId,
143 newSession->sessionToken);
144 SessionStore::getInstance().authTokens.emplace(
145 newSession->sessionToken, newSession);
146 }
147 }
148 else if (item.first == "timeout")
149 {
150 const int64_t* jTimeout =
151 item.second.get_ptr<const int64_t*>();
152 if (jTimeout == nullptr)
153 {
154 BMCWEB_LOG_DEBUG(
155 "Problem reading session timeout value");
156 continue;
157 }
158 std::chrono::seconds sessionTimeoutInseconds(*jTimeout);
159 BMCWEB_LOG_DEBUG("Restored Session Timeout: {}",
160 sessionTimeoutInseconds.count());
161 SessionStore::getInstance().updateSessionTimeout(
162 sessionTimeoutInseconds);
163 }
164 else if (item.first == "eventservice_config")
165 {
166 const nlohmann::json::object_t* esobj =
167 item.second
168 .get_ptr<const nlohmann::json::object_t*>();
169 if (esobj == nullptr)
170 {
171 BMCWEB_LOG_DEBUG(
172 "Problem reading EventService value");
173 continue;
174 }
175
176 EventServiceStore::getInstance()
177 .getEventServiceConfig()
178 .fromJson(*esobj);
179 }
180 else if (item.first == "subscriptions")
181 {
182 for (const auto& elem : item.second)
183 {
184 const nlohmann::json::object_t* subobj =
185 elem.get_ptr<const nlohmann::json::object_t*>();
186 if (subobj == nullptr)
187 {
188 continue;
189 }
190
191 std::optional<UserSubscription> newSub =
192 UserSubscription::fromJson(*subobj);
193
194 if (!newSub)
195 {
196 BMCWEB_LOG_ERROR(
197 "Problem reading subscription from persistent store");
198 continue;
199 }
200
201 std::string id = newSub->id;
202 BMCWEB_LOG_DEBUG("Restored subscription: {} {}", id,
203 newSub->customText);
204
205 EventServiceStore::getInstance()
206 .subscriptionsConfigMap.emplace(
207 id, std::make_shared<UserSubscription>(
208 std::move(*newSub)));
209 }
210 }
211 else
212 {
213 // Do nothing in the case of extra fields. We may have
214 // cases where fields are added in the future, and we
215 // want to at least attempt to gracefully support
216 // downgrades in that case, even if we don't officially
217 // support it
218 }
219 }
220 }
221 }
222 bool needWrite = false;
223
224 if (systemUuid.empty())
225 {
226 systemUuid = bmcweb::getRandomUUID();
227 needWrite = true;
228 }
229 if (fileRevision < jsonRevision)
230 {
231 needWrite = true;
232 }
233 // write revision changes or system uuid changes immediately
234 if (needWrite)
235 {
236 writeData();
237 }
238 }
239
writeData()240 void writeData()
241 {
242 std::filesystem::path path(filename);
243 path = path.parent_path();
244 if (!path.empty())
245 {
246 std::error_code ecDir;
247 std::filesystem::create_directories(path, ecDir);
248 if (ecDir)
249 {
250 BMCWEB_LOG_CRITICAL("Can't create persistent folders {}",
251 ecDir.message());
252 return;
253 }
254 }
255 boost::beast::file_posix persistentFile;
256 boost::system::error_code ec;
257 persistentFile.open(filename, boost::beast::file_mode::write, ec);
258 if (ec)
259 {
260 BMCWEB_LOG_CRITICAL("Unable to store persistent data to file {}",
261 ec.message());
262 return;
263 }
264
265 // set the permission of the file to 640
266 std::filesystem::perms permission =
267 std::filesystem::perms::owner_read |
268 std::filesystem::perms::owner_write |
269 std::filesystem::perms::group_read;
270 std::filesystem::permissions(filename, permission, ec);
271 if (ec)
272 {
273 BMCWEB_LOG_CRITICAL("Failed to set filesystem permissions {}",
274 ec.message());
275 return;
276 }
277 const AuthConfigMethods& c =
278 SessionStore::getInstance().getAuthMethodsConfig();
279 const auto& eventServiceConfig =
280 EventServiceStore::getInstance().getEventServiceConfig();
281 nlohmann::json::object_t data;
282 nlohmann::json& authConfig = data["auth_config"];
283
284 authConfig["XToken"] = c.xtoken;
285 authConfig["Cookie"] = c.cookie;
286 authConfig["SessionToken"] = c.sessionToken;
287 authConfig["BasicAuth"] = c.basic;
288 authConfig["TLS"] = c.tls;
289 authConfig["TLSStrict"] = c.tlsStrict;
290 authConfig["MTLSCommonNameParseMode"] =
291 static_cast<int>(c.mTLSCommonNameParsingMode);
292
293 nlohmann::json& eventserviceConfig = data["eventservice_config"];
294 eventserviceConfig["ServiceEnabled"] = eventServiceConfig.enabled;
295 eventserviceConfig["DeliveryRetryAttempts"] =
296 eventServiceConfig.retryAttempts;
297 eventserviceConfig["DeliveryRetryIntervalSeconds"] =
298 eventServiceConfig.retryTimeoutInterval;
299
300 data["system_uuid"] = systemUuid;
301 data["revision"] = jsonRevision;
302 data["timeout"] = SessionStore::getInstance().getTimeoutInSeconds();
303
304 nlohmann::json& sessions = data["sessions"];
305 sessions = nlohmann::json::array();
306 for (const auto& p : SessionStore::getInstance().authTokens)
307 {
308 if (p.second->sessionType != persistent_data::SessionType::Basic &&
309 p.second->sessionType !=
310 persistent_data::SessionType::MutualTLS)
311 {
312 nlohmann::json::object_t session;
313 session["unique_id"] = p.second->uniqueId;
314 session["session_token"] = p.second->sessionToken;
315 session["username"] = p.second->username;
316 session["csrf_token"] = p.second->csrfToken;
317 session["client_ip"] = p.second->clientIp;
318 const std::optional<std::string>& clientId = p.second->clientId;
319 if (clientId)
320 {
321 session["client_id"] = *clientId;
322 }
323 sessions.emplace_back(std::move(session));
324 }
325 }
326 nlohmann::json& subscriptions = data["subscriptions"];
327 subscriptions = nlohmann::json::array();
328 for (const auto& it :
329 EventServiceStore::getInstance().subscriptionsConfigMap)
330 {
331 if (it.second == nullptr)
332 {
333 continue;
334 }
335 const UserSubscription& subValue = *it.second;
336 if (subValue.subscriptionType == "SSE")
337 {
338 BMCWEB_LOG_DEBUG("The subscription type is SSE, so skipping.");
339 continue;
340 }
341 nlohmann::json::object_t headers;
342 for (const boost::beast::http::fields::value_type& header :
343 subValue.httpHeaders)
344 {
345 // Note, these are technically copies because nlohmann doesn't
346 // support key lookup by std::string_view. At least the
347 // following code can use move
348 // https://github.com/nlohmann/json/issues/1529
349 std::string name(header.name_string());
350 headers[std::move(name)] = header.value();
351 }
352
353 nlohmann::json::object_t subscription;
354
355 subscription["Id"] = subValue.id;
356 subscription["Context"] = subValue.customText;
357 subscription["DeliveryRetryPolicy"] = subValue.retryPolicy;
358 subscription["SendHeartbeat"] = subValue.sendHeartbeat;
359 subscription["HeartbeatIntervalMinutes"] =
360 subValue.hbIntervalMinutes;
361 subscription["Destination"] = subValue.destinationUrl;
362 subscription["EventFormatType"] = subValue.eventFormatType;
363 subscription["HttpHeaders"] = std::move(headers);
364 subscription["MessageIds"] = subValue.registryMsgIds;
365 subscription["Protocol"] = subValue.protocol;
366 subscription["RegistryPrefixes"] = subValue.registryPrefixes;
367 subscription["OriginResources"] = subValue.originResources;
368 subscription["ResourceTypes"] = subValue.resourceTypes;
369 subscription["SubscriptionType"] = subValue.subscriptionType;
370 subscription["MetricReportDefinitions"] =
371 subValue.metricReportDefinitions;
372 subscription["VerifyCertificate"] = subValue.verifyCertificate;
373
374 subscriptions.emplace_back(std::move(subscription));
375 }
376 std::string out = nlohmann::json(data).dump(
377 -1, ' ', true, nlohmann::json::error_handler_t::replace);
378 persistentFile.write(out.data(), out.size(), ec);
379 if (ec)
380 {
381 BMCWEB_LOG_ERROR("Failed to write file {}", ec.message());
382 }
383 }
384
385 std::string systemUuid;
386 };
387
getConfig()388 inline ConfigFile& getConfig()
389 {
390 static ConfigFile f;
391 return f;
392 }
393
394 } // namespace persistent_data
395