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