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