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