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