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 AuthConfigMethods& c =
218 SessionStore::getInstance().getAuthMethodsConfig();
219 const auto& eventServiceConfig =
220 EventServiceStore::getInstance().getEventServiceConfig();
221 nlohmann::json::object_t data;
222 nlohmann::json& authConfig = data["auth_config"];
223
224 authConfig["XToken"] = c.xtoken;
225 authConfig["Cookie"] = c.cookie;
226 authConfig["SessionToken"] = c.sessionToken;
227 authConfig["BasicAuth"] = c.basic;
228 authConfig["TLS"] = c.tls;
229 authConfig["TLSStrict"] = c.tlsStrict;
230 authConfig["TLSCommonNameParseMode"] =
231 static_cast<int>(c.mTLSCommonNameParsingMode);
232
233 nlohmann::json& eventserviceConfig = data["eventservice_config"];
234 eventserviceConfig["ServiceEnabled"] = eventServiceConfig.enabled;
235 eventserviceConfig["DeliveryRetryAttempts"] =
236 eventServiceConfig.retryAttempts;
237 eventserviceConfig["DeliveryRetryIntervalSeconds"] =
238 eventServiceConfig.retryTimeoutInterval;
239
240 data["system_uuid"] = systemUuid;
241 data["revision"] = jsonRevision;
242 data["timeout"] = SessionStore::getInstance().getTimeoutInSeconds();
243
244 nlohmann::json& sessions = data["sessions"];
245 sessions = nlohmann::json::array();
246 for (const auto& p : SessionStore::getInstance().authTokens)
247 {
248 if (p.second->sessionType != persistent_data::SessionType::Basic &&
249 p.second->sessionType !=
250 persistent_data::SessionType::MutualTLS)
251 {
252 nlohmann::json::object_t session;
253 session["unique_id"] = p.second->uniqueId;
254 session["session_token"] = p.second->sessionToken;
255 session["username"] = p.second->username;
256 session["csrf_token"] = p.second->csrfToken;
257 session["client_ip"] = p.second->clientIp;
258 const std::optional<std::string>& clientId = p.second->clientId;
259 if (clientId)
260 {
261 session["client_id"] = *clientId;
262 }
263 sessions.emplace_back(std::move(session));
264 }
265 }
266 nlohmann::json& subscriptions = data["subscriptions"];
267 subscriptions = nlohmann::json::array();
268 for (const auto& it :
269 EventServiceStore::getInstance().subscriptionsConfigMap)
270 {
271 const UserSubscription& subValue = *it.second;
272 if (subValue.subscriptionType == "SSE")
273 {
274 BMCWEB_LOG_DEBUG("The subscription type is SSE, so skipping.");
275 continue;
276 }
277 nlohmann::json::object_t headers;
278 for (const boost::beast::http::fields::value_type& header :
279 subValue.httpHeaders)
280 {
281 // Note, these are technically copies because nlohmann doesn't
282 // support key lookup by std::string_view. At least the
283 // following code can use move
284 // https://github.com/nlohmann/json/issues/1529
285 std::string name(header.name_string());
286 headers[std::move(name)] = header.value();
287 }
288
289 nlohmann::json::object_t subscription;
290
291 subscription["Id"] = subValue.id;
292 subscription["Context"] = subValue.customText;
293 subscription["DeliveryRetryPolicy"] = subValue.retryPolicy;
294 subscription["Destination"] = subValue.destinationUrl;
295 subscription["EventFormatType"] = subValue.eventFormatType;
296 subscription["HttpHeaders"] = std::move(headers);
297 subscription["MessageIds"] = subValue.registryMsgIds;
298 subscription["Protocol"] = subValue.protocol;
299 subscription["RegistryPrefixes"] = subValue.registryPrefixes;
300 subscription["OriginResources"] = subValue.originResources;
301 subscription["ResourceTypes"] = subValue.resourceTypes;
302 subscription["SubscriptionType"] = subValue.subscriptionType;
303 subscription["MetricReportDefinitions"] =
304 subValue.metricReportDefinitions;
305 subscription["VerifyCertificate"] = subValue.verifyCertificate;
306
307 subscriptions.emplace_back(std::move(subscription));
308 }
309 persistentFile << data;
310 }
311
312 std::string systemUuid;
313 };
314
getConfig()315 inline ConfigFile& getConfig()
316 {
317 static ConfigFile f;
318 return f;
319 }
320
321 } // namespace persistent_data
322