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 29 ConfigFile() 30 { 31 readData(); 32 } 33 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 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 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 356 inline ConfigFile& getConfig() 357 { 358 static ConfigFile f; 359 return f; 360 } 361 362 } // namespace persistent_data 363