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