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