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