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