1 #pragma once 2 3 #include <app.hpp> 4 #include <boost/beast/http/fields.hpp> 5 #include <boost/container/flat_map.hpp> 6 #include <boost/uuid/uuid.hpp> 7 #include <boost/uuid/uuid_generators.hpp> 8 #include <boost/uuid/uuid_io.hpp> 9 #include <event_service_store.hpp> 10 #include <http_request.hpp> 11 #include <http_response.hpp> 12 #include <nlohmann/json.hpp> 13 #include <pam_authenticate.hpp> 14 #include <sessions.hpp> 15 16 #include <filesystem> 17 #include <fstream> 18 #include <random> 19 20 namespace persistent_data 21 { 22 23 class ConfigFile 24 { 25 uint64_t jsonRevision = 1; 26 27 public: 28 // todo(ed) should read this from a fixed location somewhere, not CWD 29 static constexpr const char* filename = "bmcweb_persistent_data.json"; 30 31 ConfigFile() 32 { 33 readData(); 34 } 35 36 ~ConfigFile() 37 { 38 // Make sure we aren't writing stale sessions 39 persistent_data::SessionStore::getInstance().applySessionTimeouts(); 40 if (persistent_data::SessionStore::getInstance().needsWrite()) 41 { 42 writeData(); 43 } 44 } 45 46 // TODO(ed) this should really use protobuf, or some other serialization 47 // library, but adding another dependency is somewhat outside the scope of 48 // this application for the moment 49 void readData() 50 { 51 std::ifstream persistentFile(filename); 52 uint64_t fileRevision = 0; 53 if (persistentFile.is_open()) 54 { 55 // call with exceptions disabled 56 auto data = nlohmann::json::parse(persistentFile, nullptr, false); 57 if (data.is_discarded()) 58 { 59 BMCWEB_LOG_ERROR 60 << "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 111 << "Restored session: " << 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 150 << "Problem reading subscription " 151 "from persistent store"; 152 continue; 153 } 154 155 BMCWEB_LOG_DEBUG << "Restored subscription: " 156 << newSubscription->id << " " 157 << newSubscription->customText; 158 EventServiceStore::getInstance() 159 .subscriptionsConfigMap.emplace( 160 newSubscription->id, newSubscription); 161 } 162 } 163 else 164 { 165 // Do nothing in the case of extra fields. We may have 166 // cases where fields are added in the future, and we 167 // want to at least attempt to gracefully support 168 // downgrades in that case, even if we don't officially 169 // support it 170 } 171 } 172 } 173 } 174 bool needWrite = false; 175 176 if (systemUuid.empty()) 177 { 178 systemUuid = 179 boost::uuids::to_string(boost::uuids::random_generator()()); 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 data{ 207 {"auth_config", 208 {{"XToken", c.xtoken}, 209 {"Cookie", c.cookie}, 210 {"SessionToken", c.sessionToken}, 211 {"BasicAuth", c.basic}, 212 {"TLS", c.tls}} 213 214 }, 215 {"eventservice_config", 216 {{"ServiceEnabled", eventServiceConfig.enabled}, 217 {"DeliveryRetryAttempts", eventServiceConfig.retryAttempts}, 218 {"DeliveryRetryIntervalSeconds", 219 eventServiceConfig.retryTimeoutInterval}} 220 221 }, 222 {"system_uuid", systemUuid}, 223 {"revision", jsonRevision}, 224 {"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 sessions.push_back({ 234 {"unique_id", p.second->uniqueId}, 235 {"session_token", p.second->sessionToken}, 236 {"username", p.second->username}, 237 {"csrf_token", p.second->csrfToken}, 238 {"client_ip", p.second->clientIp}, 239 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE 240 {"client_id", p.second->clientId}, 241 #endif 242 }); 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 254 << "The subscription type is SSE, so skipping."; 255 continue; 256 } 257 nlohmann::json::object_t headers; 258 for (const boost::beast::http::fields::value_type& header : 259 subValue->httpHeaders) 260 { 261 // Note, these are technically copies because nlohmann doesn't 262 // support key lookup by std::string_view. At least the 263 // following code can use move 264 // https://github.com/nlohmann/json/issues/1529 265 std::string name(header.name_string()); 266 headers[std::move(name)] = header.value(); 267 } 268 269 subscriptions.push_back({ 270 {"Id", subValue->id}, 271 {"Context", subValue->customText}, 272 {"DeliveryRetryPolicy", subValue->retryPolicy}, 273 {"Destination", subValue->destinationUrl}, 274 {"EventFormatType", subValue->eventFormatType}, 275 {"HttpHeaders", std::move(headers)}, 276 {"MessageIds", subValue->registryMsgIds}, 277 {"Protocol", subValue->protocol}, 278 {"RegistryPrefixes", subValue->registryPrefixes}, 279 {"ResourceTypes", subValue->resourceTypes}, 280 {"SubscriptionType", subValue->subscriptionType}, 281 {"MetricReportDefinitions", subValue->metricReportDefinitions}, 282 }); 283 } 284 persistentFile << data; 285 } 286 287 std::string systemUuid{""}; 288 }; 289 290 inline ConfigFile& getConfig() 291 { 292 static ConfigFile f; 293 return f; 294 } 295 296 } // namespace persistent_data 297