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 for (const auto& item : data.items()) 65 { 66 if (item.key() == "revision") 67 { 68 fileRevision = 0; 69 70 const uint64_t* uintPtr = 71 item.value().get_ptr<const uint64_t*>(); 72 if (uintPtr == nullptr) 73 { 74 BMCWEB_LOG_ERROR("Failed to read revision flag"); 75 } 76 else 77 { 78 fileRevision = *uintPtr; 79 } 80 } 81 else if (item.key() == "system_uuid") 82 { 83 const std::string* jSystemUuid = 84 item.value().get_ptr<const std::string*>(); 85 if (jSystemUuid != nullptr) 86 { 87 systemUuid = *jSystemUuid; 88 } 89 } 90 else if (item.key() == "auth_config") 91 { 92 SessionStore::getInstance() 93 .getAuthMethodsConfig() 94 .fromJson(item.value()); 95 } 96 else if (item.key() == "sessions") 97 { 98 for (const auto& elem : item.value()) 99 { 100 std::shared_ptr<UserSession> newSession = 101 UserSession::fromJson(elem); 102 103 if (newSession == nullptr) 104 { 105 BMCWEB_LOG_ERROR("Problem reading session " 106 "from persistent store"); 107 continue; 108 } 109 110 BMCWEB_LOG_DEBUG("Restored session: {} {} {}", 111 newSession->csrfToken, 112 newSession->uniqueId, 113 newSession->sessionToken); 114 SessionStore::getInstance().authTokens.emplace( 115 newSession->sessionToken, newSession); 116 } 117 } 118 else if (item.key() == "timeout") 119 { 120 const int64_t* jTimeout = 121 item.value().get_ptr<int64_t*>(); 122 if (jTimeout == nullptr) 123 { 124 BMCWEB_LOG_DEBUG( 125 "Problem reading session timeout value"); 126 continue; 127 } 128 std::chrono::seconds sessionTimeoutInseconds(*jTimeout); 129 BMCWEB_LOG_DEBUG("Restored Session Timeout: {}", 130 sessionTimeoutInseconds.count()); 131 SessionStore::getInstance().updateSessionTimeout( 132 sessionTimeoutInseconds); 133 } 134 else if (item.key() == "eventservice_config") 135 { 136 EventServiceStore::getInstance() 137 .getEventServiceConfig() 138 .fromJson(item.value()); 139 } 140 else if (item.key() == "subscriptions") 141 { 142 for (const auto& elem : item.value()) 143 { 144 std::shared_ptr<UserSubscription> newSubscription = 145 UserSubscription::fromJson(elem); 146 147 if (newSubscription == nullptr) 148 { 149 BMCWEB_LOG_ERROR("Problem reading subscription " 150 "from persistent store"); 151 continue; 152 } 153 154 BMCWEB_LOG_DEBUG("Restored subscription: {} {}", 155 newSubscription->id, 156 newSubscription->customText); 157 EventServiceStore::getInstance() 158 .subscriptionsConfigMap.emplace( 159 newSubscription->id, newSubscription); 160 } 161 } 162 else 163 { 164 // Do nothing in the case of extra fields. We may have 165 // cases where fields are added in the future, and we 166 // want to at least attempt to gracefully support 167 // downgrades in that case, even if we don't officially 168 // support it 169 } 170 } 171 } 172 } 173 bool needWrite = false; 174 175 if (systemUuid.empty()) 176 { 177 systemUuid = bmcweb::getRandomUUID(); 178 needWrite = true; 179 } 180 if (fileRevision < jsonRevision) 181 { 182 needWrite = true; 183 } 184 // write revision changes or system uuid changes immediately 185 if (needWrite) 186 { 187 writeData(); 188 } 189 } 190 191 void writeData() 192 { 193 std::ofstream persistentFile(filename); 194 195 // set the permission of the file to 640 196 std::filesystem::perms permission = 197 std::filesystem::perms::owner_read | 198 std::filesystem::perms::owner_write | 199 std::filesystem::perms::group_read; 200 std::filesystem::permissions(filename, permission); 201 const auto& c = SessionStore::getInstance().getAuthMethodsConfig(); 202 const auto& eventServiceConfig = 203 EventServiceStore::getInstance().getEventServiceConfig(); 204 nlohmann::json::object_t data; 205 nlohmann::json& authConfig = data["auth_config"]; 206 207 authConfig["XToken"] = c.xtoken; 208 authConfig["Cookie"] = c.cookie; 209 authConfig["SessionToken"] = c.sessionToken; 210 authConfig["BasicAuth"] = c.basic; 211 authConfig["TLS"] = c.tls; 212 213 nlohmann::json& eventserviceConfig = data["eventservice_config"]; 214 eventserviceConfig["ServiceEnabled"] = eventServiceConfig.enabled; 215 eventserviceConfig["DeliveryRetryAttempts"] = 216 eventServiceConfig.retryAttempts; 217 eventserviceConfig["DeliveryRetryIntervalSeconds"] = 218 eventServiceConfig.retryTimeoutInterval; 219 220 data["system_uuid"] = systemUuid; 221 data["revision"] = jsonRevision; 222 data["timeout"] = SessionStore::getInstance().getTimeoutInSeconds(); 223 224 nlohmann::json& sessions = data["sessions"]; 225 sessions = nlohmann::json::array(); 226 for (const auto& p : SessionStore::getInstance().authTokens) 227 { 228 if (p.second->persistence != 229 persistent_data::PersistenceType::SINGLE_REQUEST) 230 { 231 nlohmann::json::object_t session; 232 session["unique_id"] = p.second->uniqueId; 233 session["session_token"] = p.second->sessionToken; 234 session["username"] = p.second->username; 235 session["csrf_token"] = p.second->csrfToken; 236 session["client_ip"] = p.second->clientIp; 237 const std::optional<std::string>& clientId = p.second->clientId; 238 if (clientId) 239 { 240 session["client_id"] = *clientId; 241 } 242 sessions.emplace_back(std::move(session)); 243 } 244 } 245 nlohmann::json& subscriptions = data["subscriptions"]; 246 subscriptions = nlohmann::json::array(); 247 for (const auto& it : 248 EventServiceStore::getInstance().subscriptionsConfigMap) 249 { 250 std::shared_ptr<UserSubscription> subValue = it.second; 251 if (subValue->subscriptionType == "SSE") 252 { 253 BMCWEB_LOG_DEBUG("The subscription type is SSE, so skipping."); 254 continue; 255 } 256 nlohmann::json::object_t headers; 257 for (const boost::beast::http::fields::value_type& header : 258 subValue->httpHeaders) 259 { 260 // Note, these are technically copies because nlohmann doesn't 261 // support key lookup by std::string_view. At least the 262 // following code can use move 263 // https://github.com/nlohmann/json/issues/1529 264 std::string name(header.name_string()); 265 headers[std::move(name)] = header.value(); 266 } 267 268 nlohmann::json::object_t subscription; 269 270 subscription["Id"] = subValue->id; 271 subscription["Context"] = subValue->customText; 272 subscription["DeliveryRetryPolicy"] = subValue->retryPolicy; 273 subscription["Destination"] = subValue->destinationUrl; 274 subscription["EventFormatType"] = subValue->eventFormatType; 275 subscription["HttpHeaders"] = std::move(headers); 276 subscription["MessageIds"] = subValue->registryMsgIds; 277 subscription["Protocol"] = subValue->protocol; 278 subscription["RegistryPrefixes"] = subValue->registryPrefixes; 279 subscription["ResourceTypes"] = subValue->resourceTypes; 280 subscription["SubscriptionType"] = subValue->subscriptionType; 281 subscription["MetricReportDefinitions"] = 282 subValue->metricReportDefinitions; 283 284 subscriptions.emplace_back(std::move(subscription)); 285 } 286 persistentFile << data; 287 } 288 289 std::string systemUuid; 290 }; 291 292 inline ConfigFile& getConfig() 293 { 294 static ConfigFile f; 295 return f; 296 } 297 298 } // namespace persistent_data 299