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