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