blob: 26b30306fa6ff4eb8adcb8d95f0d70f5097bb31f [file] [log] [blame]
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +01001#pragma once
2
Ed Tanous04e438c2020-10-03 08:06:26 -07003#include "logging.hpp"
Ed Tanousfc76b8a2020-09-28 17:21:52 -07004#include "random.hpp"
Ed Tanous04e438c2020-10-03 08:06:26 -07005#include "utility.hpp"
Ed Tanous3ccb3ad2023-01-13 17:40:03 -08006#include "utils/ip_utils.hpp"
Ed Tanousfc76b8a2020-09-28 17:21:52 -07007
Ed Tanous1abe55e2018-09-05 08:30:59 -07008#include <nlohmann/json.hpp>
Ratan Gupta12c04ef2019-04-03 10:08:11 +05309
Xie Ning9fa06f12022-06-29 18:27:47 +080010#include <algorithm>
Gunnar Mills1214b7e2020-06-04 10:11:30 -050011#include <csignal>
Ed Tanousbb759e32022-08-02 17:07:54 -070012#include <optional>
Gunnar Mills1214b7e2020-06-04 10:11:30 -050013#include <random>
Ratan Gupta07386c62019-12-14 14:06:09 +053014#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
Ed Tanous3ccb3ad2023-01-13 17:40:03 -080015#include "ibm/locks.hpp"
Ratan Gupta07386c62019-12-14 14:06:09 +053016#endif
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010017
Ed Tanous1abe55e2018-09-05 08:30:59 -070018namespace persistent_data
19{
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010020
Ed Tanous51dae672018-09-05 16:07:32 -070021// 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
24constexpr std::size_t sessionTokenSize = 20;
25
Ed Tanous1abe55e2018-09-05 08:30:59 -070026enum 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.
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010030};
31
Ed Tanous1abe55e2018-09-05 08:30:59 -070032struct UserSession
33{
34 std::string uniqueId;
35 std::string sessionToken;
36 std::string username;
37 std::string csrfToken;
Ed Tanousbb759e32022-08-02 17:07:54 -070038 std::optional<std::string> clientId;
Sunitha Harish92f68222020-05-28 05:09:09 -050039 std::string clientIp;
Ed Tanous1abe55e2018-09-05 08:30:59 -070040 std::chrono::time_point<std::chrono::steady_clock> lastUpdated;
Ed Tanousd3a9e082022-01-07 09:30:41 -080041 PersistenceType persistence{PersistenceType::TIMEOUT};
Joseph Reynolds3bf4e632020-02-06 14:44:32 -060042 bool isConfigureSelfOnly = false;
Ninad Palsule3e72c202023-03-27 17:19:55 -050043 std::string userRole{};
44 std::vector<std::string> userGroups{};
Joseph Reynolds3bf4e632020-02-06 14:44:32 -060045
46 // There are two sources of truth for isConfigureSelfOnly:
47 // 1. When pamAuthenticateUser() returns PAM_NEW_AUTHTOK_REQD.
48 // 2. D-Bus User.Manager.GetUserInfo property UserPasswordExpired.
49 // These should be in sync, but the underlying condition can change at any
50 // time. For example, a password can expire or be changed outside of
51 // bmcweb. The value stored here is updated at the start of each
52 // operation and used as the truth within bmcweb.
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +010053
Ed Tanous1abe55e2018-09-05 08:30:59 -070054 /**
55 * @brief Fills object with data from UserSession's JSON representation
56 *
57 * This replaces nlohmann's from_json to ensure no-throw approach
58 *
59 * @param[in] j JSON object from which data should be loaded
60 *
61 * @return a shared pointer if data has been loaded properly, nullptr
62 * otherwise
63 */
64 static std::shared_ptr<UserSession> fromJson(const nlohmann::json& j)
65 {
66 std::shared_ptr<UserSession> userSession =
67 std::make_shared<UserSession>();
68 for (const auto& element : j.items())
69 {
70 const std::string* thisValue =
71 element.value().get_ptr<const std::string*>();
72 if (thisValue == nullptr)
73 {
74 BMCWEB_LOG_ERROR << "Error reading persistent store. Property "
75 << element.key() << " was not of type string";
Ed Tanousdc511aa2020-10-21 12:33:42 -070076 continue;
Ed Tanous1abe55e2018-09-05 08:30:59 -070077 }
78 if (element.key() == "unique_id")
79 {
80 userSession->uniqueId = *thisValue;
81 }
82 else if (element.key() == "session_token")
83 {
84 userSession->sessionToken = *thisValue;
85 }
86 else if (element.key() == "csrf_token")
87 {
88 userSession->csrfToken = *thisValue;
89 }
90 else if (element.key() == "username")
91 {
92 userSession->username = *thisValue;
93 }
Sunitha Harish08bdcc72020-05-12 05:17:57 -050094 else if (element.key() == "client_id")
95 {
96 userSession->clientId = *thisValue;
97 }
Sunitha Harish92f68222020-05-28 05:09:09 -050098 else if (element.key() == "client_ip")
99 {
100 userSession->clientIp = *thisValue;
101 }
102
Ed Tanous1abe55e2018-09-05 08:30:59 -0700103 else
104 {
105 BMCWEB_LOG_ERROR
106 << "Got unexpected property reading persistent file: "
107 << element.key();
Ed Tanousdc511aa2020-10-21 12:33:42 -0700108 continue;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700109 }
110 }
Ed Tanousdc511aa2020-10-21 12:33:42 -0700111 // If any of these fields are missing, we can't restore the session, as
112 // we don't have enough information. These 4 fields have been present
113 // in every version of this file in bmcwebs history, so any file, even
114 // on upgrade, should have these present
115 if (userSession->uniqueId.empty() || userSession->username.empty() ||
116 userSession->sessionToken.empty() || userSession->csrfToken.empty())
117 {
118 BMCWEB_LOG_DEBUG << "Session missing required security "
119 "information, refusing to restore";
120 return nullptr;
121 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700122
123 // For now, sessions that were persisted through a reboot get their idle
124 // timer reset. This could probably be overcome with a better
125 // understanding of wall clock time and steady timer time, possibly
126 // persisting values with wall clock time instead of steady timer, but
127 // the tradeoffs of all the corner cases involved are non-trivial, so
128 // this is done temporarily
129 userSession->lastUpdated = std::chrono::steady_clock::now();
130 userSession->persistence = PersistenceType::TIMEOUT;
131
132 return userSession;
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +0100133 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100134};
135
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100136struct AuthConfigMethods
137{
Alan Kuof16f6262020-12-08 19:29:59 +0800138#ifdef BMCWEB_ENABLE_BASIC_AUTHENTICATION
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100139 bool basic = true;
Alan Kuof16f6262020-12-08 19:29:59 +0800140#else
141 bool basic = false;
142#endif
143
144#ifdef BMCWEB_ENABLE_SESSION_AUTHENTICATION
145 bool sessionToken = true;
146#else
147 bool sessionToken = false;
148#endif
149
150#ifdef BMCWEB_ENABLE_XTOKEN_AUTHENTICATION
151 bool xtoken = true;
152#else
153 bool xtoken = false;
154#endif
155
156#ifdef BMCWEB_ENABLE_COOKIE_AUTHENTICATION
157 bool cookie = true;
158#else
159 bool cookie = false;
160#endif
161
162#ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
163 bool tls = true;
164#else
Zbigniew Kurzynskicac94c52019-11-07 12:55:04 +0100165 bool tls = false;
Alan Kuof16f6262020-12-08 19:29:59 +0800166#endif
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100167
168 void fromJson(const nlohmann::json& j)
169 {
170 for (const auto& element : j.items())
171 {
172 const bool* value = element.value().get_ptr<const bool*>();
173 if (value == nullptr)
174 {
175 continue;
176 }
177
178 if (element.key() == "XToken")
179 {
180 xtoken = *value;
181 }
182 else if (element.key() == "Cookie")
183 {
184 cookie = *value;
185 }
186 else if (element.key() == "SessionToken")
187 {
188 sessionToken = *value;
189 }
190 else if (element.key() == "BasicAuth")
191 {
192 basic = *value;
193 }
Zbigniew Kurzynski501f1e52019-10-02 11:22:11 +0200194 else if (element.key() == "TLS")
195 {
196 tls = *value;
197 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100198 }
199 }
200};
201
Ed Tanous1abe55e2018-09-05 08:30:59 -0700202class SessionStore
203{
204 public:
205 std::shared_ptr<UserSession> generateUserSession(
Ed Tanous26ccae32023-02-16 10:28:44 -0800206 std::string_view username, const boost::asio::ip::address& clientIp,
Ed Tanousbb759e32022-08-02 17:07:54 -0700207 const std::optional<std::string>& clientId,
Joseph Reynolds3bf4e632020-02-06 14:44:32 -0600208 PersistenceType persistence = PersistenceType::TIMEOUT,
Sunitha Harishd3239222021-02-24 15:33:29 +0530209 bool isConfigureSelfOnly = false)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700210 {
211 // TODO(ed) find a secure way to not generate session identifiers if
212 // persistence is set to SINGLE_REQUEST
213 static constexpr std::array<char, 62> alphanum = {
Joseph Reynolds368b1d42019-08-15 15:29:06 -0500214 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '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', 'a', 'b', 'c',
Ed Tanous1abe55e2018-09-05 08:30:59 -0700217 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
218 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100219
Ed Tanous1abe55e2018-09-05 08:30:59 -0700220 std::string sessionToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700221 sessionToken.resize(sessionTokenSize, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700222 std::uniform_int_distribution<size_t> dist(0, alphanum.size() - 1);
James Feista68a8042020-04-15 15:46:44 -0700223
Ed Tanousfc76b8a2020-09-28 17:21:52 -0700224 bmcweb::OpenSSLGenerator gen;
James Feista68a8042020-04-15 15:46:44 -0700225
Ed Tanous0dfeda62019-10-24 11:21:38 -0700226 for (char& sessionChar : sessionToken)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700227 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700228 sessionChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700229 if (gen.error())
230 {
231 return nullptr;
232 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100233 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700234 // Only need csrf tokens for cookie based auth, token doesn't matter
235 std::string csrfToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700236 csrfToken.resize(sessionTokenSize, '0');
Ed Tanous0dfeda62019-10-24 11:21:38 -0700237 for (char& csrfChar : csrfToken)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700238 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700239 csrfChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700240 if (gen.error())
241 {
242 return nullptr;
243 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700244 }
245
246 std::string uniqueId;
247 uniqueId.resize(10, '0');
Ed Tanous0dfeda62019-10-24 11:21:38 -0700248 for (char& uidChar : uniqueId)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700249 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700250 uidChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700251 if (gen.error())
252 {
253 return nullptr;
254 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700255 }
Jiaqing Zhao41d61c82021-12-07 13:21:47 +0800256
257 auto session = std::make_shared<UserSession>(UserSession{
Ed Tanousbb759e32022-08-02 17:07:54 -0700258 uniqueId, sessionToken, std::string(username), csrfToken, clientId,
259 redfish::ip_util::toString(clientIp),
Gunnar Millse628df82023-04-04 10:15:42 -0500260 std::chrono::steady_clock::now(), persistence,
Jiaqing Zhao41d61c82021-12-07 13:21:47 +0800261 isConfigureSelfOnly});
Patrick Williams41713dd2022-09-28 06:48:07 -0500262 auto it = authTokens.emplace(sessionToken, session);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700263 // Only need to write to disk if session isn't about to be destroyed.
264 needWrite = persistence == PersistenceType::TIMEOUT;
265 return it.first->second;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100266 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700267
Ed Tanous26ccae32023-02-16 10:28:44 -0800268 std::shared_ptr<UserSession> loginSessionByToken(std::string_view token)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700269 {
270 applySessionTimeouts();
Ed Tanous51dae672018-09-05 16:07:32 -0700271 if (token.size() != sessionTokenSize)
272 {
273 return nullptr;
274 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700275 auto sessionIt = authTokens.find(std::string(token));
276 if (sessionIt == authTokens.end())
277 {
278 return nullptr;
279 }
280 std::shared_ptr<UserSession> userSession = sessionIt->second;
281 userSession->lastUpdated = std::chrono::steady_clock::now();
282 return userSession;
283 }
284
Ed Tanous26ccae32023-02-16 10:28:44 -0800285 std::shared_ptr<UserSession> getSessionByUid(std::string_view uid)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700286 {
287 applySessionTimeouts();
288 // TODO(Ed) this is inefficient
289 auto sessionIt = authTokens.begin();
290 while (sessionIt != authTokens.end())
291 {
292 if (sessionIt->second->uniqueId == uid)
293 {
294 return sessionIt->second;
295 }
296 sessionIt++;
297 }
298 return nullptr;
299 }
300
Ed Tanousb5a76932020-09-29 16:16:58 -0700301 void removeSession(const std::shared_ptr<UserSession>& session)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700302 {
Ratan Gupta07386c62019-12-14 14:06:09 +0530303#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
304 crow::ibm_mc_lock::Lock::getInstance().releaseLock(session->uniqueId);
305#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700306 authTokens.erase(session->sessionToken);
307 needWrite = true;
308 }
309
310 std::vector<const std::string*> getUniqueIds(
311 bool getAll = true,
312 const PersistenceType& type = PersistenceType::SINGLE_REQUEST)
313 {
314 applySessionTimeouts();
315
316 std::vector<const std::string*> ret;
317 ret.reserve(authTokens.size());
318 for (auto& session : authTokens)
319 {
320 if (getAll || type == session.second->persistence)
321 {
322 ret.push_back(&session.second->uniqueId);
323 }
324 }
325 return ret;
326 }
327
Xie Ning9fa06f12022-06-29 18:27:47 +0800328 void removeSessionsByUsername(std::string_view username)
329 {
330 std::erase_if(authTokens, [username](const auto& value) {
331 if (value.second == nullptr)
332 {
333 return false;
334 }
335 return value.second->username == username;
336 });
337 }
338
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100339 void updateAuthMethodsConfig(const AuthConfigMethods& config)
340 {
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100341 bool isTLSchanged = (authMethodsConfig.tls != config.tls);
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100342 authMethodsConfig = config;
343 needWrite = true;
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100344 if (isTLSchanged)
345 {
346 // recreate socket connections with new settings
347 std::raise(SIGHUP);
348 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100349 }
350
351 AuthConfigMethods& getAuthMethodsConfig()
352 {
353 return authMethodsConfig;
354 }
355
Ed Tanous9eb808c2022-01-25 10:19:23 -0800356 bool needsWrite() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700357 {
358 return needWrite;
359 }
Ed Tanous271584a2019-07-09 16:24:22 -0700360 int64_t getTimeoutInSeconds() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700361 {
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530362 return std::chrono::seconds(timeoutInSeconds).count();
363 }
364
365 void updateSessionTimeout(std::chrono::seconds newTimeoutInSeconds)
366 {
367 timeoutInSeconds = newTimeoutInSeconds;
368 needWrite = true;
Ed Tanous23a21a12020-07-25 04:45:05 +0000369 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700370
Ed Tanous1abe55e2018-09-05 08:30:59 -0700371 static SessionStore& getInstance()
372 {
373 static SessionStore sessionStore;
374 return sessionStore;
375 }
376
Ed Tanous1abe55e2018-09-05 08:30:59 -0700377 void applySessionTimeouts()
378 {
379 auto timeNow = std::chrono::steady_clock::now();
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530380 if (timeNow - lastTimeoutUpdate > std::chrono::seconds(1))
Ed Tanous1abe55e2018-09-05 08:30:59 -0700381 {
382 lastTimeoutUpdate = timeNow;
383 auto authTokensIt = authTokens.begin();
384 while (authTokensIt != authTokens.end())
385 {
386 if (timeNow - authTokensIt->second->lastUpdated >=
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530387 timeoutInSeconds)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700388 {
Ratan Gupta07386c62019-12-14 14:06:09 +0530389#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
390 crow::ibm_mc_lock::Lock::getInstance().releaseLock(
391 authTokensIt->second->uniqueId);
392#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700393 authTokensIt = authTokens.erase(authTokensIt);
Ratan Gupta07386c62019-12-14 14:06:09 +0530394
Ed Tanous1abe55e2018-09-05 08:30:59 -0700395 needWrite = true;
396 }
397 else
398 {
399 authTokensIt++;
400 }
401 }
402 }
403 }
Gunnar Mills83cf8182020-11-11 15:37:34 -0600404
405 SessionStore(const SessionStore&) = delete;
406 SessionStore& operator=(const SessionStore&) = delete;
Ed Tanousecd6a3a2022-01-07 09:18:40 -0800407 SessionStore(SessionStore&&) = delete;
408 SessionStore& operator=(const SessionStore&&) = delete;
409 ~SessionStore() = default;
Gunnar Mills83cf8182020-11-11 15:37:34 -0600410
411 std::unordered_map<std::string, std::shared_ptr<UserSession>,
412 std::hash<std::string>,
413 crow::utility::ConstantTimeCompare>
414 authTokens;
415
416 std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate;
417 bool needWrite{false};
418 std::chrono::seconds timeoutInSeconds;
419 AuthConfigMethods authMethodsConfig;
420
421 private:
Patrick Williams89492a12023-05-10 07:51:34 -0500422 SessionStore() : timeoutInSeconds(1800) {}
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100423};
424
Ed Tanous1abe55e2018-09-05 08:30:59 -0700425} // namespace persistent_data