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 ConfigFile(const ConfigFile&) = delete; 47 ConfigFile(ConfigFile&&) = delete; 48 ConfigFile& operator=(const ConfigFile&) = delete; 49 ConfigFile& operator=(ConfigFile&&) = delete; 50 51 // TODO(ed) this should really use protobuf, or some other serialization 52 // library, but adding another dependency is somewhat outside the scope of 53 // this application for the moment 54 void readData() 55 { 56 std::ifstream persistentFile(filename); 57 uint64_t fileRevision = 0; 58 if (persistentFile.is_open()) 59 { 60 // call with exceptions disabled 61 auto data = nlohmann::json::parse(persistentFile, nullptr, false); 62 if (data.is_discarded()) 63 { 64 BMCWEB_LOG_ERROR 65 << "Error parsing persistent data in json file."; 66 } 67 else 68 { 69 for (const auto& item : data.items()) 70 { 71 if (item.key() == "revision") 72 { 73 fileRevision = 0; 74 75 const uint64_t* uintPtr = 76 item.value().get_ptr<const uint64_t*>(); 77 if (uintPtr == nullptr) 78 { 79 BMCWEB_LOG_ERROR << "Failed to read revision flag"; 80 } 81 else 82 { 83 fileRevision = *uintPtr; 84 } 85 } 86 else if (item.key() == "system_uuid") 87 { 88 const std::string* jSystemUuid = 89 item.value().get_ptr<const std::string*>(); 90 if (jSystemUuid != nullptr) 91 { 92 systemUuid = *jSystemUuid; 93 } 94 } 95 else if (item.key() == "auth_config") 96 { 97 SessionStore::getInstance() 98 .getAuthMethodsConfig() 99 .fromJson(item.value()); 100 } 101 else if (item.key() == "sessions") 102 { 103 for (const auto& elem : item.value()) 104 { 105 std::shared_ptr<UserSession> newSession = 106 UserSession::fromJson(elem); 107 108 if (newSession == nullptr) 109 { 110 BMCWEB_LOG_ERROR << "Problem reading session " 111 "from persistent store"; 112 continue; 113 } 114 115 BMCWEB_LOG_DEBUG 116 << "Restored session: " << newSession->csrfToken 117 << " " << newSession->uniqueId << " " 118 << newSession->sessionToken; 119 SessionStore::getInstance().authTokens.emplace( 120 newSession->sessionToken, newSession); 121 } 122 } 123 else if (item.key() == "timeout") 124 { 125 const int64_t* jTimeout = 126 item.value().get_ptr<int64_t*>(); 127 if (jTimeout == nullptr) 128 { 129 BMCWEB_LOG_DEBUG 130 << "Problem reading session timeout value"; 131 continue; 132 } 133 std::chrono::seconds sessionTimeoutInseconds(*jTimeout); 134 BMCWEB_LOG_DEBUG << "Restored Session Timeout: " 135 << sessionTimeoutInseconds.count(); 136 SessionStore::getInstance().updateSessionTimeout( 137 sessionTimeoutInseconds); 138 } 139 else if (item.key() == "eventservice_config") 140 { 141 EventServiceStore::getInstance() 142 .getEventServiceConfig() 143 .fromJson(item.value()); 144 } 145 else if (item.key() == "subscriptions") 146 { 147 for (const auto& elem : item.value()) 148 { 149 std::shared_ptr<UserSubscription> newSubscription = 150 UserSubscription::fromJson(elem); 151 152 if (newSubscription == nullptr) 153 { 154 BMCWEB_LOG_ERROR 155 << "Problem reading subscription " 156 "from persistent store"; 157 continue; 158 } 159 160 BMCWEB_LOG_DEBUG << "Restored subscription: " 161 << newSubscription->id << " " 162 << newSubscription->customText; 163 EventServiceStore::getInstance() 164 .subscriptionsConfigMap.emplace( 165 newSubscription->id, newSubscription); 166 } 167 } 168 else 169 { 170 // Do nothing in the case of extra fields. We may have 171 // cases where fields are added in the future, and we 172 // want to at least attempt to gracefully support 173 // downgrades in that case, even if we don't officially 174 // support it 175 } 176 } 177 } 178 } 179 bool needWrite = false; 180 181 if (systemUuid.empty()) 182 { 183 systemUuid = 184 boost::uuids::to_string(boost::uuids::random_generator()()); 185 needWrite = true; 186 } 187 if (fileRevision < jsonRevision) 188 { 189 needWrite = true; 190 } 191 // write revision changes or system uuid changes immediately 192 if (needWrite) 193 { 194 writeData(); 195 } 196 } 197 198 void writeData() 199 { 200 std::ofstream persistentFile(filename); 201 202 // set the permission of the file to 640 203 std::filesystem::perms permission = 204 std::filesystem::perms::owner_read | 205 std::filesystem::perms::owner_write | 206 std::filesystem::perms::group_read; 207 std::filesystem::permissions(filename, permission); 208 const auto& c = SessionStore::getInstance().getAuthMethodsConfig(); 209 const auto& eventServiceConfig = 210 EventServiceStore::getInstance().getEventServiceConfig(); 211 nlohmann::json data{ 212 {"auth_config", 213 {{"XToken", c.xtoken}, 214 {"Cookie", c.cookie}, 215 {"SessionToken", c.sessionToken}, 216 {"BasicAuth", c.basic}, 217 {"TLS", c.tls}} 218 219 }, 220 {"eventservice_config", 221 {{"ServiceEnabled", eventServiceConfig.enabled}, 222 {"DeliveryRetryAttempts", eventServiceConfig.retryAttempts}, 223 {"DeliveryRetryIntervalSeconds", 224 eventServiceConfig.retryTimeoutInterval}} 225 226 }, 227 {"system_uuid", systemUuid}, 228 {"revision", jsonRevision}, 229 {"timeout", SessionStore::getInstance().getTimeoutInSeconds()}}; 230 231 nlohmann::json& sessions = data["sessions"]; 232 sessions = nlohmann::json::array(); 233 for (const auto& p : SessionStore::getInstance().authTokens) 234 { 235 if (p.second->persistence != 236 persistent_data::PersistenceType::SINGLE_REQUEST) 237 { 238 sessions.push_back({ 239 {"unique_id", p.second->uniqueId}, 240 {"session_token", p.second->sessionToken}, 241 {"username", p.second->username}, 242 {"csrf_token", p.second->csrfToken}, 243 {"client_ip", p.second->clientIp}, 244 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE 245 {"client_id", p.second->clientId}, 246 #endif 247 }); 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 subscriptions.push_back({ 275 {"Id", subValue->id}, 276 {"Context", subValue->customText}, 277 {"DeliveryRetryPolicy", subValue->retryPolicy}, 278 {"Destination", subValue->destinationUrl}, 279 {"EventFormatType", subValue->eventFormatType}, 280 {"HttpHeaders", std::move(headers)}, 281 {"MessageIds", subValue->registryMsgIds}, 282 {"Protocol", subValue->protocol}, 283 {"RegistryPrefixes", subValue->registryPrefixes}, 284 {"ResourceTypes", subValue->resourceTypes}, 285 {"SubscriptionType", subValue->subscriptionType}, 286 {"MetricReportDefinitions", subValue->metricReportDefinitions}, 287 }); 288 } 289 persistentFile << data; 290 } 291 292 std::string systemUuid; 293 }; 294 295 inline ConfigFile& getConfig() 296 { 297 static ConfigFile f; 298 return f; 299 } 300 301 } // namespace persistent_data 302