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 27 ConfigFile() 28 { 29 readData(); 30 } 31 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 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::optional<UserSubscription> newSub = 161 UserSubscription::fromJson(elem); 162 163 if (!newSub) 164 { 165 BMCWEB_LOG_ERROR("Problem reading subscription " 166 "from persistent store"); 167 continue; 168 } 169 170 BMCWEB_LOG_DEBUG("Restored subscription: {} {}", 171 newSub->id, newSub->customText); 172 173 boost::container::flat_map< 174 std::string, UserSubscription>& configMap = 175 EventServiceStore::getInstance() 176 .subscriptionsConfigMap; 177 configMap.emplace(newSub->id, *newSub); 178 } 179 } 180 else 181 { 182 // Do nothing in the case of extra fields. We may have 183 // cases where fields are added in the future, and we 184 // want to at least attempt to gracefully support 185 // downgrades in that case, even if we don't officially 186 // support it 187 } 188 } 189 } 190 } 191 bool needWrite = false; 192 193 if (systemUuid.empty()) 194 { 195 systemUuid = bmcweb::getRandomUUID(); 196 needWrite = true; 197 } 198 if (fileRevision < jsonRevision) 199 { 200 needWrite = true; 201 } 202 // write revision changes or system uuid changes immediately 203 if (needWrite) 204 { 205 writeData(); 206 } 207 } 208 209 void writeData() 210 { 211 std::ofstream persistentFile(filename); 212 213 // set the permission of the file to 640 214 std::filesystem::perms permission = 215 std::filesystem::perms::owner_read | 216 std::filesystem::perms::owner_write | 217 std::filesystem::perms::group_read; 218 std::filesystem::permissions(filename, permission); 219 const AuthConfigMethods& c = 220 SessionStore::getInstance().getAuthMethodsConfig(); 221 const auto& eventServiceConfig = 222 EventServiceStore::getInstance().getEventServiceConfig(); 223 nlohmann::json::object_t data; 224 nlohmann::json& authConfig = data["auth_config"]; 225 226 authConfig["XToken"] = c.xtoken; 227 authConfig["Cookie"] = c.cookie; 228 authConfig["SessionToken"] = c.sessionToken; 229 authConfig["BasicAuth"] = c.basic; 230 authConfig["TLS"] = c.tls; 231 authConfig["TLSStrict"] = c.tlsStrict; 232 authConfig["TLSCommonNameParseMode"] = 233 static_cast<int>(c.mTLSCommonNameParsingMode); 234 235 nlohmann::json& eventserviceConfig = data["eventservice_config"]; 236 eventserviceConfig["ServiceEnabled"] = eventServiceConfig.enabled; 237 eventserviceConfig["DeliveryRetryAttempts"] = 238 eventServiceConfig.retryAttempts; 239 eventserviceConfig["DeliveryRetryIntervalSeconds"] = 240 eventServiceConfig.retryTimeoutInterval; 241 242 data["system_uuid"] = systemUuid; 243 data["revision"] = jsonRevision; 244 data["timeout"] = SessionStore::getInstance().getTimeoutInSeconds(); 245 246 nlohmann::json& sessions = data["sessions"]; 247 sessions = nlohmann::json::array(); 248 for (const auto& p : SessionStore::getInstance().authTokens) 249 { 250 if (p.second->sessionType != persistent_data::SessionType::Basic && 251 p.second->sessionType != 252 persistent_data::SessionType::MutualTLS) 253 { 254 nlohmann::json::object_t session; 255 session["unique_id"] = p.second->uniqueId; 256 session["session_token"] = p.second->sessionToken; 257 session["username"] = p.second->username; 258 session["csrf_token"] = p.second->csrfToken; 259 session["client_ip"] = p.second->clientIp; 260 const std::optional<std::string>& clientId = p.second->clientId; 261 if (clientId) 262 { 263 session["client_id"] = *clientId; 264 } 265 sessions.emplace_back(std::move(session)); 266 } 267 } 268 nlohmann::json& subscriptions = data["subscriptions"]; 269 subscriptions = nlohmann::json::array(); 270 for (const auto& it : 271 EventServiceStore::getInstance().subscriptionsConfigMap) 272 { 273 const UserSubscription& subValue = it.second; 274 if (subValue.subscriptionType == "SSE") 275 { 276 BMCWEB_LOG_DEBUG("The subscription type is SSE, so skipping."); 277 continue; 278 } 279 nlohmann::json::object_t headers; 280 for (const boost::beast::http::fields::value_type& header : 281 subValue.httpHeaders) 282 { 283 // Note, these are technically copies because nlohmann doesn't 284 // support key lookup by std::string_view. At least the 285 // following code can use move 286 // https://github.com/nlohmann/json/issues/1529 287 std::string name(header.name_string()); 288 headers[std::move(name)] = header.value(); 289 } 290 291 nlohmann::json::object_t subscription; 292 293 subscription["Id"] = subValue.id; 294 subscription["Context"] = subValue.customText; 295 subscription["DeliveryRetryPolicy"] = subValue.retryPolicy; 296 subscription["Destination"] = subValue.destinationUrl; 297 subscription["EventFormatType"] = subValue.eventFormatType; 298 subscription["HttpHeaders"] = std::move(headers); 299 subscription["MessageIds"] = subValue.registryMsgIds; 300 subscription["Protocol"] = subValue.protocol; 301 subscription["RegistryPrefixes"] = subValue.registryPrefixes; 302 subscription["OriginResources"] = subValue.originResources; 303 subscription["ResourceTypes"] = subValue.resourceTypes; 304 subscription["SubscriptionType"] = subValue.subscriptionType; 305 subscription["MetricReportDefinitions"] = 306 subValue.metricReportDefinitions; 307 subscription["VerifyCertificate"] = subValue.verifyCertificate; 308 309 subscriptions.emplace_back(std::move(subscription)); 310 } 311 persistentFile << data; 312 } 313 314 std::string systemUuid; 315 }; 316 317 inline ConfigFile& getConfig() 318 { 319 static ConfigFile f; 320 return f; 321 } 322 323 } // namespace persistent_data 324