blob: 50299b8f20203c81390daa48e26772498e3be943 [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 Tanous2c6ffdb2023-06-28 11:28:38 -07004#include "ossl_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>
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010014
Ed Tanous1abe55e2018-09-05 08:30:59 -070015namespace persistent_data
16{
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010017
Ed Tanous51dae672018-09-05 16:07:32 -070018// entropy: 20 characters, 62 possibilities. log2(62^20) = 119 bits of
19// entropy. OWASP recommends at least 64
20// https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#session-id-entropy
21constexpr std::size_t sessionTokenSize = 20;
22
Ed Tanous1abe55e2018-09-05 08:30:59 -070023enum class PersistenceType
24{
25 TIMEOUT, // User session times out after a predetermined amount of time
26 SINGLE_REQUEST // User times out once this request is completed.
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010027};
28
Ed Tanous1abe55e2018-09-05 08:30:59 -070029struct UserSession
30{
31 std::string uniqueId;
32 std::string sessionToken;
33 std::string username;
34 std::string csrfToken;
Ed Tanousbb759e32022-08-02 17:07:54 -070035 std::optional<std::string> clientId;
Sunitha Harish92f68222020-05-28 05:09:09 -050036 std::string clientIp;
Ed Tanous1abe55e2018-09-05 08:30:59 -070037 std::chrono::time_point<std::chrono::steady_clock> lastUpdated;
Ed Tanousd3a9e082022-01-07 09:30:41 -080038 PersistenceType persistence{PersistenceType::TIMEOUT};
Ed Tanous7e9c08e2023-06-16 11:29:37 -070039 bool cookieAuth = false;
Joseph Reynolds3bf4e632020-02-06 14:44:32 -060040 bool isConfigureSelfOnly = false;
Ed Tanous47f29342024-03-19 12:18:06 -070041 std::string userRole;
42 std::vector<std::string> userGroups;
Joseph Reynolds3bf4e632020-02-06 14:44:32 -060043
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.
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +010051
Ed Tanous1abe55e2018-09-05 08:30:59 -070052 /**
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 {
Ed Tanous62598e32023-07-17 17:06:25 -070072 BMCWEB_LOG_ERROR(
73 "Error reading persistent store. Property {} was not of type string",
74 element.key());
Ed Tanousdc511aa2020-10-21 12:33:42 -070075 continue;
Ed Tanous1abe55e2018-09-05 08:30:59 -070076 }
77 if (element.key() == "unique_id")
78 {
79 userSession->uniqueId = *thisValue;
80 }
81 else if (element.key() == "session_token")
82 {
83 userSession->sessionToken = *thisValue;
84 }
85 else if (element.key() == "csrf_token")
86 {
87 userSession->csrfToken = *thisValue;
88 }
89 else if (element.key() == "username")
90 {
91 userSession->username = *thisValue;
92 }
Sunitha Harish08bdcc72020-05-12 05:17:57 -050093 else if (element.key() == "client_id")
94 {
95 userSession->clientId = *thisValue;
96 }
Sunitha Harish92f68222020-05-28 05:09:09 -050097 else if (element.key() == "client_ip")
98 {
99 userSession->clientIp = *thisValue;
100 }
101
Ed Tanous1abe55e2018-09-05 08:30:59 -0700102 else
103 {
Ed Tanous62598e32023-07-17 17:06:25 -0700104 BMCWEB_LOG_ERROR(
105 "Got unexpected property reading persistent file: {}",
106 element.key());
Ed Tanousdc511aa2020-10-21 12:33:42 -0700107 continue;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700108 }
109 }
Ed Tanousdc511aa2020-10-21 12:33:42 -0700110 // If any of these fields are missing, we can't restore the session, as
111 // we don't have enough information. These 4 fields have been present
112 // in every version of this file in bmcwebs history, so any file, even
113 // on upgrade, should have these present
114 if (userSession->uniqueId.empty() || userSession->username.empty() ||
115 userSession->sessionToken.empty() || userSession->csrfToken.empty())
116 {
Ed Tanous62598e32023-07-17 17:06:25 -0700117 BMCWEB_LOG_DEBUG("Session missing required security "
118 "information, refusing to restore");
Ed Tanousdc511aa2020-10-21 12:33:42 -0700119 return nullptr;
120 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700121
122 // For now, sessions that were persisted through a reboot get their idle
123 // timer reset. This could probably be overcome with a better
124 // understanding of wall clock time and steady timer time, possibly
125 // persisting values with wall clock time instead of steady timer, but
126 // the tradeoffs of all the corner cases involved are non-trivial, so
127 // this is done temporarily
128 userSession->lastUpdated = std::chrono::steady_clock::now();
129 userSession->persistence = PersistenceType::TIMEOUT;
130
131 return userSession;
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +0100132 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100133};
134
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100135struct AuthConfigMethods
136{
Ed Tanous25b54db2024-04-17 15:40:31 -0700137 bool basic = BMCWEB_BASIC_AUTH;
138 bool sessionToken = BMCWEB_SESSION_AUTH;
139 bool xtoken = BMCWEB_XTOKEN_AUTH;
140 bool cookie = BMCWEB_COOKIE_AUTH;
141 bool tls = BMCWEB_MUTUAL_TLS_AUTH;
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100142
143 void fromJson(const nlohmann::json& j)
144 {
145 for (const auto& element : j.items())
146 {
147 const bool* value = element.value().get_ptr<const bool*>();
148 if (value == nullptr)
149 {
150 continue;
151 }
152
153 if (element.key() == "XToken")
154 {
155 xtoken = *value;
156 }
157 else if (element.key() == "Cookie")
158 {
159 cookie = *value;
160 }
161 else if (element.key() == "SessionToken")
162 {
163 sessionToken = *value;
164 }
165 else if (element.key() == "BasicAuth")
166 {
167 basic = *value;
168 }
Zbigniew Kurzynski501f1e52019-10-02 11:22:11 +0200169 else if (element.key() == "TLS")
170 {
171 tls = *value;
172 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100173 }
174 }
175};
176
Ed Tanous1abe55e2018-09-05 08:30:59 -0700177class SessionStore
178{
179 public:
180 std::shared_ptr<UserSession> generateUserSession(
Ed Tanous26ccae32023-02-16 10:28:44 -0800181 std::string_view username, const boost::asio::ip::address& clientIp,
Ed Tanousbb759e32022-08-02 17:07:54 -0700182 const std::optional<std::string>& clientId,
Joseph Reynolds3bf4e632020-02-06 14:44:32 -0600183 PersistenceType persistence = PersistenceType::TIMEOUT,
Sunitha Harishd3239222021-02-24 15:33:29 +0530184 bool isConfigureSelfOnly = false)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700185 {
186 // TODO(ed) find a secure way to not generate session identifiers if
187 // persistence is set to SINGLE_REQUEST
188 static constexpr std::array<char, 62> alphanum = {
Joseph Reynolds368b1d42019-08-15 15:29:06 -0500189 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
190 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
191 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
Ed Tanous1abe55e2018-09-05 08:30:59 -0700192 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
193 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100194
Ed Tanous1abe55e2018-09-05 08:30:59 -0700195 std::string sessionToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700196 sessionToken.resize(sessionTokenSize, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700197 std::uniform_int_distribution<size_t> dist(0, alphanum.size() - 1);
James Feista68a8042020-04-15 15:46:44 -0700198
Ed Tanousfc76b8a2020-09-28 17:21:52 -0700199 bmcweb::OpenSSLGenerator gen;
James Feista68a8042020-04-15 15:46:44 -0700200
Ed Tanous0dfeda62019-10-24 11:21:38 -0700201 for (char& sessionChar : sessionToken)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700202 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700203 sessionChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700204 if (gen.error())
205 {
206 return nullptr;
207 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100208 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700209 // Only need csrf tokens for cookie based auth, token doesn't matter
210 std::string csrfToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700211 csrfToken.resize(sessionTokenSize, '0');
Ed Tanous0dfeda62019-10-24 11:21:38 -0700212 for (char& csrfChar : csrfToken)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700213 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700214 csrfChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700215 if (gen.error())
216 {
217 return nullptr;
218 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700219 }
220
221 std::string uniqueId;
222 uniqueId.resize(10, '0');
Ed Tanous0dfeda62019-10-24 11:21:38 -0700223 for (char& uidChar : uniqueId)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700224 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700225 uidChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700226 if (gen.error())
227 {
228 return nullptr;
229 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700230 }
Jiaqing Zhao41d61c82021-12-07 13:21:47 +0800231
Ed Tanous47f29342024-03-19 12:18:06 -0700232 auto session = std::make_shared<UserSession>(
233 UserSession{uniqueId,
234 sessionToken,
235 std::string(username),
236 csrfToken,
237 clientId,
238 redfish::ip_util::toString(clientIp),
239 std::chrono::steady_clock::now(),
240 persistence,
241 false,
242 isConfigureSelfOnly,
243 "",
244 {}});
Patrick Williams41713dd2022-09-28 06:48:07 -0500245 auto it = authTokens.emplace(sessionToken, session);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700246 // Only need to write to disk if session isn't about to be destroyed.
247 needWrite = persistence == PersistenceType::TIMEOUT;
248 return it.first->second;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100249 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700250
Ed Tanous26ccae32023-02-16 10:28:44 -0800251 std::shared_ptr<UserSession> loginSessionByToken(std::string_view token)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700252 {
253 applySessionTimeouts();
Ed Tanous51dae672018-09-05 16:07:32 -0700254 if (token.size() != sessionTokenSize)
255 {
256 return nullptr;
257 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700258 auto sessionIt = authTokens.find(std::string(token));
259 if (sessionIt == authTokens.end())
260 {
261 return nullptr;
262 }
263 std::shared_ptr<UserSession> userSession = sessionIt->second;
264 userSession->lastUpdated = std::chrono::steady_clock::now();
265 return userSession;
266 }
267
Ed Tanous26ccae32023-02-16 10:28:44 -0800268 std::shared_ptr<UserSession> getSessionByUid(std::string_view uid)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700269 {
270 applySessionTimeouts();
271 // TODO(Ed) this is inefficient
272 auto sessionIt = authTokens.begin();
273 while (sessionIt != authTokens.end())
274 {
275 if (sessionIt->second->uniqueId == uid)
276 {
277 return sessionIt->second;
278 }
279 sessionIt++;
280 }
281 return nullptr;
282 }
283
Ed Tanousb5a76932020-09-29 16:16:58 -0700284 void removeSession(const std::shared_ptr<UserSession>& session)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700285 {
286 authTokens.erase(session->sessionToken);
287 needWrite = true;
288 }
289
290 std::vector<const std::string*> getUniqueIds(
291 bool getAll = true,
292 const PersistenceType& type = PersistenceType::SINGLE_REQUEST)
293 {
294 applySessionTimeouts();
295
296 std::vector<const std::string*> ret;
297 ret.reserve(authTokens.size());
298 for (auto& session : authTokens)
299 {
300 if (getAll || type == session.second->persistence)
301 {
302 ret.push_back(&session.second->uniqueId);
303 }
304 }
305 return ret;
306 }
307
Xie Ning9fa06f12022-06-29 18:27:47 +0800308 void removeSessionsByUsername(std::string_view username)
309 {
310 std::erase_if(authTokens, [username](const auto& value) {
311 if (value.second == nullptr)
312 {
313 return false;
314 }
315 return value.second->username == username;
316 });
317 }
318
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100319 void updateAuthMethodsConfig(const AuthConfigMethods& config)
320 {
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100321 bool isTLSchanged = (authMethodsConfig.tls != config.tls);
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100322 authMethodsConfig = config;
323 needWrite = true;
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100324 if (isTLSchanged)
325 {
326 // recreate socket connections with new settings
327 std::raise(SIGHUP);
328 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100329 }
330
331 AuthConfigMethods& getAuthMethodsConfig()
332 {
333 return authMethodsConfig;
334 }
335
Ed Tanous9eb808c2022-01-25 10:19:23 -0800336 bool needsWrite() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700337 {
338 return needWrite;
339 }
Ed Tanous271584a2019-07-09 16:24:22 -0700340 int64_t getTimeoutInSeconds() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700341 {
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530342 return std::chrono::seconds(timeoutInSeconds).count();
343 }
344
345 void updateSessionTimeout(std::chrono::seconds newTimeoutInSeconds)
346 {
347 timeoutInSeconds = newTimeoutInSeconds;
348 needWrite = true;
Ed Tanous23a21a12020-07-25 04:45:05 +0000349 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700350
Ed Tanous1abe55e2018-09-05 08:30:59 -0700351 static SessionStore& getInstance()
352 {
353 static SessionStore sessionStore;
354 return sessionStore;
355 }
356
Ed Tanous1abe55e2018-09-05 08:30:59 -0700357 void applySessionTimeouts()
358 {
359 auto timeNow = std::chrono::steady_clock::now();
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530360 if (timeNow - lastTimeoutUpdate > std::chrono::seconds(1))
Ed Tanous1abe55e2018-09-05 08:30:59 -0700361 {
362 lastTimeoutUpdate = timeNow;
363 auto authTokensIt = authTokens.begin();
364 while (authTokensIt != authTokens.end())
365 {
366 if (timeNow - authTokensIt->second->lastUpdated >=
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530367 timeoutInSeconds)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700368 {
369 authTokensIt = authTokens.erase(authTokensIt);
Ratan Gupta07386c62019-12-14 14:06:09 +0530370
Ed Tanous1abe55e2018-09-05 08:30:59 -0700371 needWrite = true;
372 }
373 else
374 {
375 authTokensIt++;
376 }
377 }
378 }
379 }
Gunnar Mills83cf8182020-11-11 15:37:34 -0600380
381 SessionStore(const SessionStore&) = delete;
382 SessionStore& operator=(const SessionStore&) = delete;
Ed Tanousecd6a3a2022-01-07 09:18:40 -0800383 SessionStore(SessionStore&&) = delete;
384 SessionStore& operator=(const SessionStore&&) = delete;
385 ~SessionStore() = default;
Gunnar Mills83cf8182020-11-11 15:37:34 -0600386
387 std::unordered_map<std::string, std::shared_ptr<UserSession>,
388 std::hash<std::string>,
389 crow::utility::ConstantTimeCompare>
390 authTokens;
391
392 std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate;
393 bool needWrite{false};
394 std::chrono::seconds timeoutInSeconds;
395 AuthConfigMethods authMethodsConfig;
396
397 private:
Patrick Williams89492a12023-05-10 07:51:34 -0500398 SessionStore() : timeoutInSeconds(1800) {}
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100399};
400
Ed Tanous1abe55e2018-09-05 08:30:59 -0700401} // namespace persistent_data