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