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