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::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 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["ResourceTypes"] = subValue.resourceTypes; 301 subscription["SubscriptionType"] = subValue.subscriptionType; 302 subscription["MetricReportDefinitions"] = 303 subValue.metricReportDefinitions; 304 subscription["VerifyCertificate"] = subValue.verifyCertificate; 305 306 subscriptions.emplace_back(std::move(subscription)); 307 } 308 persistentFile << data; 309 } 310 311 std::string systemUuid; 312 }; 313 314 inline ConfigFile& getConfig() 315 { 316 static ConfigFile f; 317 return f; 318 } 319 320 } // namespace persistent_data 321