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