1 #pragma once 2 3 #include "app.hpp" 4 #include "event_service_store.hpp" 5 #include "http_request.hpp" 6 #include "http_response.hpp" 7 #include "sessions.hpp" 8 9 #include <boost/beast/http/fields.hpp> 10 #include <boost/uuid/uuid.hpp> 11 #include <boost/uuid/uuid_generators.hpp> 12 #include <boost/uuid/uuid_io.hpp> 13 #include <nlohmann/json.hpp> 14 15 #include <filesystem> 16 #include <fstream> 17 #include <random> 18 19 namespace persistent_data 20 { 21 22 class ConfigFile 23 { 24 uint64_t jsonRevision = 1; 25 26 public: 27 // todo(ed) should read this from a fixed location somewhere, not CWD 28 static constexpr const char* filename = "bmcweb_persistent_data.json"; 29 30 ConfigFile() 31 { 32 readData(); 33 } 34 35 ~ConfigFile() 36 { 37 // Make sure we aren't writing stale sessions 38 persistent_data::SessionStore::getInstance().applySessionTimeouts(); 39 if (persistent_data::SessionStore::getInstance().needsWrite()) 40 { 41 writeData(); 42 } 43 } 44 45 ConfigFile(const ConfigFile&) = delete; 46 ConfigFile(ConfigFile&&) = delete; 47 ConfigFile& operator=(const ConfigFile&) = delete; 48 ConfigFile& operator=(ConfigFile&&) = delete; 49 50 // TODO(ed) this should really use protobuf, or some other serialization 51 // library, but adding another dependency is somewhat outside the scope of 52 // this application for the moment 53 void readData() 54 { 55 std::ifstream persistentFile(filename); 56 uint64_t fileRevision = 0; 57 if (persistentFile.is_open()) 58 { 59 // call with exceptions disabled 60 auto data = nlohmann::json::parse(persistentFile, nullptr, false); 61 if (data.is_discarded()) 62 { 63 BMCWEB_LOG_ERROR 64 << "Error parsing persistent data in json file."; 65 } 66 else 67 { 68 for (const auto& item : data.items()) 69 { 70 if (item.key() == "revision") 71 { 72 fileRevision = 0; 73 74 const uint64_t* uintPtr = 75 item.value().get_ptr<const uint64_t*>(); 76 if (uintPtr == nullptr) 77 { 78 BMCWEB_LOG_ERROR << "Failed to read revision flag"; 79 } 80 else 81 { 82 fileRevision = *uintPtr; 83 } 84 } 85 else if (item.key() == "system_uuid") 86 { 87 const std::string* jSystemUuid = 88 item.value().get_ptr<const std::string*>(); 89 if (jSystemUuid != nullptr) 90 { 91 systemUuid = *jSystemUuid; 92 } 93 } 94 else if (item.key() == "auth_config") 95 { 96 SessionStore::getInstance() 97 .getAuthMethodsConfig() 98 .fromJson(item.value()); 99 } 100 else if (item.key() == "sessions") 101 { 102 for (const auto& elem : item.value()) 103 { 104 std::shared_ptr<UserSession> newSession = 105 UserSession::fromJson(elem); 106 107 if (newSession == nullptr) 108 { 109 BMCWEB_LOG_ERROR << "Problem reading session " 110 "from persistent store"; 111 continue; 112 } 113 114 BMCWEB_LOG_DEBUG 115 << "Restored session: " << newSession->csrfToken 116 << " " << newSession->uniqueId << " " 117 << newSession->sessionToken; 118 SessionStore::getInstance().authTokens.emplace( 119 newSession->sessionToken, newSession); 120 } 121 } 122 else if (item.key() == "timeout") 123 { 124 const int64_t* jTimeout = 125 item.value().get_ptr<int64_t*>(); 126 if (jTimeout == nullptr) 127 { 128 BMCWEB_LOG_DEBUG 129 << "Problem reading session timeout value"; 130 continue; 131 } 132 std::chrono::seconds sessionTimeoutInseconds(*jTimeout); 133 BMCWEB_LOG_DEBUG << "Restored Session Timeout: " 134 << sessionTimeoutInseconds.count(); 135 SessionStore::getInstance().updateSessionTimeout( 136 sessionTimeoutInseconds); 137 } 138 else if (item.key() == "eventservice_config") 139 { 140 EventServiceStore::getInstance() 141 .getEventServiceConfig() 142 .fromJson(item.value()); 143 } 144 else if (item.key() == "subscriptions") 145 { 146 for (const auto& elem : item.value()) 147 { 148 std::shared_ptr<UserSubscription> newSubscription = 149 UserSubscription::fromJson(elem); 150 151 if (newSubscription == nullptr) 152 { 153 BMCWEB_LOG_ERROR 154 << "Problem reading subscription " 155 "from persistent store"; 156 continue; 157 } 158 159 BMCWEB_LOG_DEBUG << "Restored subscription: " 160 << newSubscription->id << " " 161 << newSubscription->customText; 162 EventServiceStore::getInstance() 163 .subscriptionsConfigMap.emplace( 164 newSubscription->id, newSubscription); 165 } 166 } 167 else 168 { 169 // Do nothing in the case of extra fields. We may have 170 // cases where fields are added in the future, and we 171 // want to at least attempt to gracefully support 172 // downgrades in that case, even if we don't officially 173 // support it 174 } 175 } 176 } 177 } 178 bool needWrite = false; 179 180 if (systemUuid.empty()) 181 { 182 systemUuid = 183 boost::uuids::to_string(boost::uuids::random_generator()()); 184 needWrite = true; 185 } 186 if (fileRevision < jsonRevision) 187 { 188 needWrite = true; 189 } 190 // write revision changes or system uuid changes immediately 191 if (needWrite) 192 { 193 writeData(); 194 } 195 } 196 197 void writeData() 198 { 199 std::ofstream persistentFile(filename); 200 201 // set the permission of the file to 640 202 std::filesystem::perms permission = 203 std::filesystem::perms::owner_read | 204 std::filesystem::perms::owner_write | 205 std::filesystem::perms::group_read; 206 std::filesystem::permissions(filename, permission); 207 const auto& c = SessionStore::getInstance().getAuthMethodsConfig(); 208 const auto& eventServiceConfig = 209 EventServiceStore::getInstance().getEventServiceConfig(); 210 nlohmann::json::object_t data; 211 nlohmann::json& authConfig = data["auth_config"]; 212 213 authConfig["XToken"] = c.xtoken; 214 authConfig["Cookie"] = c.cookie; 215 authConfig["SessionToken"] = c.sessionToken; 216 authConfig["BasicAuth"] = c.basic; 217 authConfig["TLS"] = c.tls; 218 219 nlohmann::json& eventserviceConfig = data["eventservice_config"]; 220 eventserviceConfig["ServiceEnabled"] = eventServiceConfig.enabled; 221 eventserviceConfig["DeliveryRetryAttempts"] = 222 eventServiceConfig.retryAttempts; 223 eventserviceConfig["DeliveryRetryIntervalSeconds"] = 224 eventServiceConfig.retryTimeoutInterval; 225 226 data["system_uuid"] = systemUuid; 227 data["revision"] = jsonRevision; 228 data["timeout"] = SessionStore::getInstance().getTimeoutInSeconds(); 229 230 nlohmann::json& sessions = data["sessions"]; 231 sessions = nlohmann::json::array(); 232 for (const auto& p : SessionStore::getInstance().authTokens) 233 { 234 if (p.second->persistence != 235 persistent_data::PersistenceType::SINGLE_REQUEST) 236 { 237 nlohmann::json::object_t session; 238 session["unique_id"] = p.second->uniqueId; 239 session["session_token"] = p.second->sessionToken; 240 session["username"] = p.second->username; 241 session["csrf_token"] = p.second->csrfToken; 242 session["client_ip"] = p.second->clientIp; 243 if (p.second->clientId) 244 { 245 session["client_id"] = *p.second->clientId; 246 } 247 sessions.push_back(std::move(session)); 248 } 249 } 250 nlohmann::json& subscriptions = data["subscriptions"]; 251 subscriptions = nlohmann::json::array(); 252 for (const auto& it : 253 EventServiceStore::getInstance().subscriptionsConfigMap) 254 { 255 std::shared_ptr<UserSubscription> subValue = it.second; 256 if (subValue->subscriptionType == "SSE") 257 { 258 BMCWEB_LOG_DEBUG 259 << "The subscription type is SSE, so skipping."; 260 continue; 261 } 262 nlohmann::json::object_t headers; 263 for (const boost::beast::http::fields::value_type& header : 264 subValue->httpHeaders) 265 { 266 // Note, these are technically copies because nlohmann doesn't 267 // support key lookup by std::string_view. At least the 268 // following code can use move 269 // https://github.com/nlohmann/json/issues/1529 270 std::string name(header.name_string()); 271 headers[std::move(name)] = header.value(); 272 } 273 274 nlohmann::json::object_t subscription; 275 276 subscription["Id"] = subValue->id; 277 subscription["Context"] = subValue->customText; 278 subscription["DeliveryRetryPolicy"] = subValue->retryPolicy; 279 subscription["Destination"] = subValue->destinationUrl; 280 subscription["EventFormatType"] = subValue->eventFormatType; 281 subscription["HttpHeaders"] = std::move(headers); 282 subscription["MessageIds"] = subValue->registryMsgIds; 283 subscription["Protocol"] = subValue->protocol; 284 subscription["RegistryPrefixes"] = subValue->registryPrefixes; 285 subscription["ResourceTypes"] = subValue->resourceTypes; 286 subscription["SubscriptionType"] = subValue->subscriptionType; 287 subscription["MetricReportDefinitions"] = 288 subValue->metricReportDefinitions; 289 290 subscriptions.push_back(std::move(subscription)); 291 } 292 persistentFile << data; 293 } 294 295 std::string systemUuid; 296 }; 297 298 inline ConfigFile& getConfig() 299 { 300 static ConfigFile f; 301 return f; 302 } 303 304 } // namespace persistent_data 305