xref: /openbmc/bmcweb/include/sessions.hpp (revision 3174e4dfd3185c131a07371b4b5a5b40cf0e0bdb)
1 #pragma once
2 
3 #include "logging.h"
4 #include "utility.h"
5 
6 #include "random.hpp"
7 
8 #include <openssl/rand.h>
9 
10 #include <boost/container/flat_map.hpp>
11 #include <boost/uuid/uuid.hpp>
12 #include <boost/uuid/uuid_generators.hpp>
13 #include <boost/uuid/uuid_io.hpp>
14 #include <dbus_singleton.hpp>
15 #include <nlohmann/json.hpp>
16 #include <pam_authenticate.hpp>
17 #include <sdbusplus/bus/match.hpp>
18 
19 #include <csignal>
20 #include <random>
21 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
22 #include <ibm/locks.hpp>
23 #endif
24 
25 namespace persistent_data
26 {
27 
28 // entropy: 20 characters, 62 possibilities.  log2(62^20) = 119 bits of
29 // entropy.  OWASP recommends at least 64
30 // https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#session-id-entropy
31 constexpr std::size_t sessionTokenSize = 20;
32 
33 enum class PersistenceType
34 {
35     TIMEOUT, // User session times out after a predetermined amount of time
36     SINGLE_REQUEST // User times out once this request is completed.
37 };
38 
39 struct UserSession
40 {
41     std::string uniqueId;
42     std::string sessionToken;
43     std::string username;
44     std::string csrfToken;
45     std::string clientId;
46     std::string clientIp;
47     std::chrono::time_point<std::chrono::steady_clock> lastUpdated;
48     PersistenceType persistence;
49     bool cookieAuth = false;
50     bool isConfigureSelfOnly = false;
51 
52     // There are two sources of truth for isConfigureSelfOnly:
53     //  1. When pamAuthenticateUser() returns PAM_NEW_AUTHTOK_REQD.
54     //  2. D-Bus User.Manager.GetUserInfo property UserPasswordExpired.
55     // These should be in sync, but the underlying condition can change at any
56     // time.  For example, a password can expire or be changed outside of
57     // bmcweb.  The value stored here is updated at the start of each
58     // operation and used as the truth within bmcweb.
59 
60     /**
61      * @brief Fills object with data from UserSession's JSON representation
62      *
63      * This replaces nlohmann's from_json to ensure no-throw approach
64      *
65      * @param[in] j   JSON object from which data should be loaded
66      *
67      * @return a shared pointer if data has been loaded properly, nullptr
68      * otherwise
69      */
70     static std::shared_ptr<UserSession> fromJson(const nlohmann::json& j)
71     {
72         std::shared_ptr<UserSession> userSession =
73             std::make_shared<UserSession>();
74         for (const auto& element : j.items())
75         {
76             const std::string* thisValue =
77                 element.value().get_ptr<const std::string*>();
78             if (thisValue == nullptr)
79             {
80                 BMCWEB_LOG_ERROR << "Error reading persistent store.  Property "
81                                  << element.key() << " was not of type string";
82                 return nullptr;
83             }
84             if (element.key() == "unique_id")
85             {
86                 userSession->uniqueId = *thisValue;
87             }
88             else if (element.key() == "session_token")
89             {
90                 userSession->sessionToken = *thisValue;
91             }
92             else if (element.key() == "csrf_token")
93             {
94                 userSession->csrfToken = *thisValue;
95             }
96             else if (element.key() == "username")
97             {
98                 userSession->username = *thisValue;
99             }
100             else if (element.key() == "client_id")
101             {
102                 userSession->clientId = *thisValue;
103             }
104             else if (element.key() == "client_ip")
105             {
106                 userSession->clientIp = *thisValue;
107             }
108 
109             else
110             {
111                 BMCWEB_LOG_ERROR
112                     << "Got unexpected property reading persistent file: "
113                     << element.key();
114                 return nullptr;
115             }
116         }
117 
118         // For now, sessions that were persisted through a reboot get their idle
119         // timer reset.  This could probably be overcome with a better
120         // understanding of wall clock time and steady timer time, possibly
121         // persisting values with wall clock time instead of steady timer, but
122         // the tradeoffs of all the corner cases involved are non-trivial, so
123         // this is done temporarily
124         userSession->lastUpdated = std::chrono::steady_clock::now();
125         userSession->persistence = PersistenceType::TIMEOUT;
126 
127         return userSession;
128     }
129 };
130 
131 struct AuthConfigMethods
132 {
133     bool xtoken = true;
134     bool cookie = true;
135     bool sessionToken = true;
136     bool basic = true;
137     bool tls = false;
138 
139     void fromJson(const nlohmann::json& j)
140     {
141         for (const auto& element : j.items())
142         {
143             const bool* value = element.value().get_ptr<const bool*>();
144             if (value == nullptr)
145             {
146                 continue;
147             }
148 
149             if (element.key() == "XToken")
150             {
151                 xtoken = *value;
152             }
153             else if (element.key() == "Cookie")
154             {
155                 cookie = *value;
156             }
157             else if (element.key() == "SessionToken")
158             {
159                 sessionToken = *value;
160             }
161             else if (element.key() == "BasicAuth")
162             {
163                 basic = *value;
164             }
165             else if (element.key() == "TLS")
166             {
167                 tls = *value;
168             }
169         }
170     }
171 };
172 
173 class SessionStore
174 {
175   public:
176     std::shared_ptr<UserSession> generateUserSession(
177         const std::string_view username,
178         PersistenceType persistence = PersistenceType::TIMEOUT,
179         bool isConfigureSelfOnly = false, const std::string_view clientId = "",
180         const std::string_view clientIp = "")
181     {
182         // TODO(ed) find a secure way to not generate session identifiers if
183         // persistence is set to SINGLE_REQUEST
184         static constexpr std::array<char, 62> alphanum = {
185             '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
186             'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
187             'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
188             'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
189             'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
190 
191         std::string sessionToken;
192         sessionToken.resize(sessionTokenSize, '0');
193         std::uniform_int_distribution<size_t> dist(0, alphanum.size() - 1);
194 
195         bmcweb::OpenSSLGenerator gen;
196 
197         for (char& sessionChar : sessionToken)
198         {
199             sessionChar = alphanum[dist(gen)];
200             if (gen.error())
201             {
202                 return nullptr;
203             }
204         }
205         // Only need csrf tokens for cookie based auth, token doesn't matter
206         std::string csrfToken;
207         csrfToken.resize(sessionTokenSize, '0');
208         for (char& csrfChar : csrfToken)
209         {
210             csrfChar = alphanum[dist(gen)];
211             if (gen.error())
212             {
213                 return nullptr;
214             }
215         }
216 
217         std::string uniqueId;
218         uniqueId.resize(10, '0');
219         for (char& uidChar : uniqueId)
220         {
221             uidChar = alphanum[dist(gen)];
222             if (gen.error())
223             {
224                 return nullptr;
225             }
226         }
227         auto session = std::make_shared<UserSession>(
228             UserSession{uniqueId, sessionToken, std::string(username),
229                         csrfToken, std::string(clientId), std::string(clientIp),
230                         std::chrono::steady_clock::now(), persistence, false,
231                         isConfigureSelfOnly});
232         auto it = authTokens.emplace(std::make_pair(sessionToken, session));
233         // Only need to write to disk if session isn't about to be destroyed.
234         needWrite = persistence == PersistenceType::TIMEOUT;
235         return it.first->second;
236     }
237 
238     std::shared_ptr<UserSession>
239         loginSessionByToken(const std::string_view token)
240     {
241         applySessionTimeouts();
242         if (token.size() != sessionTokenSize)
243         {
244             return nullptr;
245         }
246         auto sessionIt = authTokens.find(std::string(token));
247         if (sessionIt == authTokens.end())
248         {
249             return nullptr;
250         }
251         std::shared_ptr<UserSession> userSession = sessionIt->second;
252         userSession->lastUpdated = std::chrono::steady_clock::now();
253         return userSession;
254     }
255 
256     std::shared_ptr<UserSession> getSessionByUid(const std::string_view uid)
257     {
258         applySessionTimeouts();
259         // TODO(Ed) this is inefficient
260         auto sessionIt = authTokens.begin();
261         while (sessionIt != authTokens.end())
262         {
263             if (sessionIt->second->uniqueId == uid)
264             {
265                 return sessionIt->second;
266             }
267             sessionIt++;
268         }
269         return nullptr;
270     }
271 
272     void removeSession(std::shared_ptr<UserSession> session)
273     {
274 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
275         crow::ibm_mc_lock::Lock::getInstance().releaseLock(session->uniqueId);
276 #endif
277         authTokens.erase(session->sessionToken);
278         needWrite = true;
279     }
280 
281     std::vector<const std::string*> getUniqueIds(
282         bool getAll = true,
283         const PersistenceType& type = PersistenceType::SINGLE_REQUEST)
284     {
285         applySessionTimeouts();
286 
287         std::vector<const std::string*> ret;
288         ret.reserve(authTokens.size());
289         for (auto& session : authTokens)
290         {
291             if (getAll || type == session.second->persistence)
292             {
293                 ret.push_back(&session.second->uniqueId);
294             }
295         }
296         return ret;
297     }
298 
299     void updateAuthMethodsConfig(const AuthConfigMethods& config)
300     {
301         bool isTLSchanged = (authMethodsConfig.tls != config.tls);
302         authMethodsConfig = config;
303         needWrite = true;
304         if (isTLSchanged)
305         {
306             // recreate socket connections with new settings
307             std::raise(SIGHUP);
308         }
309     }
310 
311     AuthConfigMethods& getAuthMethodsConfig()
312     {
313         return authMethodsConfig;
314     }
315 
316     bool needsWrite()
317     {
318         return needWrite;
319     }
320     int64_t getTimeoutInSeconds() const
321     {
322         return std::chrono::seconds(timeoutInSeconds).count();
323     }
324 
325     void updateSessionTimeout(std::chrono::seconds newTimeoutInSeconds)
326     {
327         timeoutInSeconds = newTimeoutInSeconds;
328         needWrite = true;
329     }
330 
331     static SessionStore& getInstance()
332     {
333         static SessionStore sessionStore;
334         return sessionStore;
335     }
336 
337     SessionStore(const SessionStore&) = delete;
338     SessionStore& operator=(const SessionStore&) = delete;
339 
340     std::unordered_map<std::string, std::shared_ptr<UserSession>,
341                        std::hash<std::string>,
342                        crow::utility::ConstantTimeCompare>
343         authTokens;
344 
345     std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate;
346     bool needWrite{false};
347     std::chrono::seconds timeoutInSeconds;
348     AuthConfigMethods authMethodsConfig;
349 
350   private:
351     SessionStore() : timeoutInSeconds(3600)
352     {}
353 
354     void applySessionTimeouts()
355     {
356         auto timeNow = std::chrono::steady_clock::now();
357         if (timeNow - lastTimeoutUpdate > std::chrono::seconds(1))
358         {
359             lastTimeoutUpdate = timeNow;
360             auto authTokensIt = authTokens.begin();
361             while (authTokensIt != authTokens.end())
362             {
363                 if (timeNow - authTokensIt->second->lastUpdated >=
364                     timeoutInSeconds)
365                 {
366 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
367                     crow::ibm_mc_lock::Lock::getInstance().releaseLock(
368                         authTokensIt->second->uniqueId);
369 #endif
370                     authTokensIt = authTokens.erase(authTokensIt);
371 
372                     needWrite = true;
373                 }
374                 else
375                 {
376                     authTokensIt++;
377                 }
378             }
379         }
380     }
381 };
382 
383 } // namespace persistent_data
384