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