blob: 5621fffd0b19184d3053f0d150e999ded5934a19 [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>
Ed Tanousb7f3a822024-06-05 08:45:25 -070014#include <string>
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010015
Ed Tanous1abe55e2018-09-05 08:30:59 -070016namespace persistent_data
17{
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010018
Ed Tanous51dae672018-09-05 16:07:32 -070019// entropy: 20 characters, 62 possibilities. log2(62^20) = 119 bits of
20// entropy. OWASP recommends at least 64
21// https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#session-id-entropy
22constexpr std::size_t sessionTokenSize = 20;
23
Ed Tanous1abe55e2018-09-05 08:30:59 -070024enum class PersistenceType
25{
26 TIMEOUT, // User session times out after a predetermined amount of time
27 SINGLE_REQUEST // User times out once this request is completed.
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010028};
29
Ed Tanous1abe55e2018-09-05 08:30:59 -070030struct UserSession
31{
32 std::string uniqueId;
33 std::string sessionToken;
34 std::string username;
35 std::string csrfToken;
Ed Tanousbb759e32022-08-02 17:07:54 -070036 std::optional<std::string> clientId;
Sunitha Harish92f68222020-05-28 05:09:09 -050037 std::string clientIp;
Ed Tanous1abe55e2018-09-05 08:30:59 -070038 std::chrono::time_point<std::chrono::steady_clock> lastUpdated;
Ed Tanousd3a9e082022-01-07 09:30:41 -080039 PersistenceType persistence{PersistenceType::TIMEOUT};
Ed Tanous7e9c08e2023-06-16 11:29:37 -070040 bool cookieAuth = false;
Joseph Reynolds3bf4e632020-02-06 14:44:32 -060041 bool isConfigureSelfOnly = false;
Ed Tanous47f29342024-03-19 12:18:06 -070042 std::string userRole;
43 std::vector<std::string> userGroups;
Joseph Reynolds3bf4e632020-02-06 14:44:32 -060044
45 // There are two sources of truth for isConfigureSelfOnly:
46 // 1. When pamAuthenticateUser() returns PAM_NEW_AUTHTOK_REQD.
47 // 2. D-Bus User.Manager.GetUserInfo property UserPasswordExpired.
48 // These should be in sync, but the underlying condition can change at any
49 // time. For example, a password can expire or be changed outside of
50 // bmcweb. The value stored here is updated at the start of each
51 // operation and used as the truth within bmcweb.
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +010052
Ed Tanous1abe55e2018-09-05 08:30:59 -070053 /**
54 * @brief Fills object with data from UserSession's JSON representation
55 *
56 * This replaces nlohmann's from_json to ensure no-throw approach
57 *
58 * @param[in] j JSON object from which data should be loaded
59 *
60 * @return a shared pointer if data has been loaded properly, nullptr
61 * otherwise
62 */
Ed Tanous0bdda662023-08-03 17:27:34 -070063 static std::shared_ptr<UserSession>
64 fromJson(const nlohmann::json::object_t& j)
Ed Tanous1abe55e2018-09-05 08:30:59 -070065 {
66 std::shared_ptr<UserSession> userSession =
67 std::make_shared<UserSession>();
Ed Tanous0bdda662023-08-03 17:27:34 -070068 for (const auto& element : j)
Ed Tanous1abe55e2018-09-05 08:30:59 -070069 {
70 const std::string* thisValue =
Ed Tanous0bdda662023-08-03 17:27:34 -070071 element.second.get_ptr<const std::string*>();
Ed Tanous1abe55e2018-09-05 08:30:59 -070072 if (thisValue == nullptr)
73 {
Ed Tanous62598e32023-07-17 17:06:25 -070074 BMCWEB_LOG_ERROR(
75 "Error reading persistent store. Property {} was not of type string",
Ed Tanous0bdda662023-08-03 17:27:34 -070076 element.first);
Ed Tanousdc511aa2020-10-21 12:33:42 -070077 continue;
Ed Tanous1abe55e2018-09-05 08:30:59 -070078 }
Ed Tanous0bdda662023-08-03 17:27:34 -070079 if (element.first == "unique_id")
Ed Tanous1abe55e2018-09-05 08:30:59 -070080 {
81 userSession->uniqueId = *thisValue;
82 }
Ed Tanous0bdda662023-08-03 17:27:34 -070083 else if (element.first == "session_token")
Ed Tanous1abe55e2018-09-05 08:30:59 -070084 {
85 userSession->sessionToken = *thisValue;
86 }
Ed Tanous0bdda662023-08-03 17:27:34 -070087 else if (element.first == "csrf_token")
Ed Tanous1abe55e2018-09-05 08:30:59 -070088 {
89 userSession->csrfToken = *thisValue;
90 }
Ed Tanous0bdda662023-08-03 17:27:34 -070091 else if (element.first == "username")
Ed Tanous1abe55e2018-09-05 08:30:59 -070092 {
93 userSession->username = *thisValue;
94 }
Ed Tanous0bdda662023-08-03 17:27:34 -070095 else if (element.first == "client_id")
Sunitha Harish08bdcc72020-05-12 05:17:57 -050096 {
97 userSession->clientId = *thisValue;
98 }
Ed Tanous0bdda662023-08-03 17:27:34 -070099 else if (element.first == "client_ip")
Sunitha Harish92f68222020-05-28 05:09:09 -0500100 {
101 userSession->clientIp = *thisValue;
102 }
103
Ed Tanous1abe55e2018-09-05 08:30:59 -0700104 else
105 {
Ed Tanous62598e32023-07-17 17:06:25 -0700106 BMCWEB_LOG_ERROR(
107 "Got unexpected property reading persistent file: {}",
Ed Tanous0bdda662023-08-03 17:27:34 -0700108 element.first);
Ed Tanousdc511aa2020-10-21 12:33:42 -0700109 continue;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700110 }
111 }
Ed Tanousdc511aa2020-10-21 12:33:42 -0700112 // If any of these fields are missing, we can't restore the session, as
113 // we don't have enough information. These 4 fields have been present
114 // in every version of this file in bmcwebs history, so any file, even
115 // on upgrade, should have these present
116 if (userSession->uniqueId.empty() || userSession->username.empty() ||
117 userSession->sessionToken.empty() || userSession->csrfToken.empty())
118 {
Ed Tanous62598e32023-07-17 17:06:25 -0700119 BMCWEB_LOG_DEBUG("Session missing required security "
120 "information, refusing to restore");
Ed Tanousdc511aa2020-10-21 12:33:42 -0700121 return nullptr;
122 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700123
124 // For now, sessions that were persisted through a reboot get their idle
125 // timer reset. This could probably be overcome with a better
126 // understanding of wall clock time and steady timer time, possibly
127 // persisting values with wall clock time instead of steady timer, but
128 // the tradeoffs of all the corner cases involved are non-trivial, so
129 // this is done temporarily
130 userSession->lastUpdated = std::chrono::steady_clock::now();
131 userSession->persistence = PersistenceType::TIMEOUT;
132
133 return userSession;
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +0100134 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100135};
136
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100137struct AuthConfigMethods
138{
Ed Tanous25b54db2024-04-17 15:40:31 -0700139 bool basic = BMCWEB_BASIC_AUTH;
140 bool sessionToken = BMCWEB_SESSION_AUTH;
141 bool xtoken = BMCWEB_XTOKEN_AUTH;
142 bool cookie = BMCWEB_COOKIE_AUTH;
143 bool tls = BMCWEB_MUTUAL_TLS_AUTH;
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100144
Ed Tanous0bdda662023-08-03 17:27:34 -0700145 void fromJson(const nlohmann::json::object_t& j)
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100146 {
Ed Tanous0bdda662023-08-03 17:27:34 -0700147 for (const auto& element : j)
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100148 {
Ed Tanous0bdda662023-08-03 17:27:34 -0700149 const bool* value = element.second.get_ptr<const bool*>();
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100150 if (value == nullptr)
151 {
152 continue;
153 }
154
Ed Tanous0bdda662023-08-03 17:27:34 -0700155 if (element.first == "XToken")
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100156 {
157 xtoken = *value;
158 }
Ed Tanous0bdda662023-08-03 17:27:34 -0700159 else if (element.first == "Cookie")
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100160 {
161 cookie = *value;
162 }
Ed Tanous0bdda662023-08-03 17:27:34 -0700163 else if (element.first == "SessionToken")
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100164 {
165 sessionToken = *value;
166 }
Ed Tanous0bdda662023-08-03 17:27:34 -0700167 else if (element.first == "BasicAuth")
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100168 {
169 basic = *value;
170 }
Ed Tanous0bdda662023-08-03 17:27:34 -0700171 else if (element.first == "TLS")
Zbigniew Kurzynski501f1e52019-10-02 11:22:11 +0200172 {
173 tls = *value;
174 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100175 }
176 }
177};
178
Ed Tanous1abe55e2018-09-05 08:30:59 -0700179class SessionStore
180{
181 public:
182 std::shared_ptr<UserSession> generateUserSession(
Ed Tanous26ccae32023-02-16 10:28:44 -0800183 std::string_view username, const boost::asio::ip::address& clientIp,
Ed Tanousbb759e32022-08-02 17:07:54 -0700184 const std::optional<std::string>& clientId,
Joseph Reynolds3bf4e632020-02-06 14:44:32 -0600185 PersistenceType persistence = PersistenceType::TIMEOUT,
Sunitha Harishd3239222021-02-24 15:33:29 +0530186 bool isConfigureSelfOnly = false)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700187 {
Ed Tanous1abe55e2018-09-05 08:30:59 -0700188 // Only need csrf tokens for cookie based auth, token doesn't matter
Ed Tanousb7f3a822024-06-05 08:45:25 -0700189 std::string sessionToken =
190 bmcweb::getRandomIdOfLength(sessionTokenSize);
191 std::string csrfToken = bmcweb::getRandomIdOfLength(sessionTokenSize);
192 std::string uniqueId = bmcweb::getRandomIdOfLength(10);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700193
Ed Tanousb7f3a822024-06-05 08:45:25 -0700194 //
195 if (sessionToken.empty() || csrfToken.empty() || uniqueId.empty())
Ed Tanous1abe55e2018-09-05 08:30:59 -0700196 {
Ed Tanousb7f3a822024-06-05 08:45:25 -0700197 BMCWEB_LOG_ERROR("Failed to generate session tokens");
198 return nullptr;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700199 }
Jiaqing Zhao41d61c82021-12-07 13:21:47 +0800200
Ed Tanous47f29342024-03-19 12:18:06 -0700201 auto session = std::make_shared<UserSession>(
202 UserSession{uniqueId,
203 sessionToken,
204 std::string(username),
205 csrfToken,
206 clientId,
207 redfish::ip_util::toString(clientIp),
208 std::chrono::steady_clock::now(),
209 persistence,
210 false,
211 isConfigureSelfOnly,
212 "",
213 {}});
Patrick Williams41713dd2022-09-28 06:48:07 -0500214 auto it = authTokens.emplace(sessionToken, session);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700215 // Only need to write to disk if session isn't about to be destroyed.
216 needWrite = persistence == PersistenceType::TIMEOUT;
217 return it.first->second;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100218 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700219
Ed Tanous26ccae32023-02-16 10:28:44 -0800220 std::shared_ptr<UserSession> loginSessionByToken(std::string_view token)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700221 {
222 applySessionTimeouts();
Ed Tanous51dae672018-09-05 16:07:32 -0700223 if (token.size() != sessionTokenSize)
224 {
225 return nullptr;
226 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700227 auto sessionIt = authTokens.find(std::string(token));
228 if (sessionIt == authTokens.end())
229 {
230 return nullptr;
231 }
232 std::shared_ptr<UserSession> userSession = sessionIt->second;
233 userSession->lastUpdated = std::chrono::steady_clock::now();
234 return userSession;
235 }
236
Ed Tanous26ccae32023-02-16 10:28:44 -0800237 std::shared_ptr<UserSession> getSessionByUid(std::string_view uid)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700238 {
239 applySessionTimeouts();
240 // TODO(Ed) this is inefficient
241 auto sessionIt = authTokens.begin();
242 while (sessionIt != authTokens.end())
243 {
244 if (sessionIt->second->uniqueId == uid)
245 {
246 return sessionIt->second;
247 }
248 sessionIt++;
249 }
250 return nullptr;
251 }
252
Ed Tanousb5a76932020-09-29 16:16:58 -0700253 void removeSession(const std::shared_ptr<UserSession>& session)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700254 {
255 authTokens.erase(session->sessionToken);
256 needWrite = true;
257 }
258
259 std::vector<const std::string*> getUniqueIds(
260 bool getAll = true,
261 const PersistenceType& type = PersistenceType::SINGLE_REQUEST)
262 {
263 applySessionTimeouts();
264
265 std::vector<const std::string*> ret;
266 ret.reserve(authTokens.size());
267 for (auto& session : authTokens)
268 {
269 if (getAll || type == session.second->persistence)
270 {
271 ret.push_back(&session.second->uniqueId);
272 }
273 }
274 return ret;
275 }
276
Xie Ning9fa06f12022-06-29 18:27:47 +0800277 void removeSessionsByUsername(std::string_view username)
278 {
279 std::erase_if(authTokens, [username](const auto& value) {
280 if (value.second == nullptr)
281 {
282 return false;
283 }
284 return value.second->username == username;
285 });
286 }
287
Ravi Tejae518ef32024-05-16 10:33:08 -0500288 void removeSessionsByUsernameExceptSession(
289 std::string_view username, const std::shared_ptr<UserSession>& session)
290 {
291 std::erase_if(authTokens, [username, session](const auto& value) {
292 if (value.second == nullptr)
293 {
294 return false;
295 }
296
297 return value.second->username == username &&
298 value.second->uniqueId != session->uniqueId;
299 });
300 }
301
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100302 void updateAuthMethodsConfig(const AuthConfigMethods& config)
303 {
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100304 bool isTLSchanged = (authMethodsConfig.tls != config.tls);
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100305 authMethodsConfig = config;
306 needWrite = true;
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100307 if (isTLSchanged)
308 {
309 // recreate socket connections with new settings
310 std::raise(SIGHUP);
311 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100312 }
313
314 AuthConfigMethods& getAuthMethodsConfig()
315 {
316 return authMethodsConfig;
317 }
318
Ed Tanous9eb808c2022-01-25 10:19:23 -0800319 bool needsWrite() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700320 {
321 return needWrite;
322 }
Ed Tanous271584a2019-07-09 16:24:22 -0700323 int64_t getTimeoutInSeconds() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700324 {
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530325 return std::chrono::seconds(timeoutInSeconds).count();
326 }
327
328 void updateSessionTimeout(std::chrono::seconds newTimeoutInSeconds)
329 {
330 timeoutInSeconds = newTimeoutInSeconds;
331 needWrite = true;
Ed Tanous23a21a12020-07-25 04:45:05 +0000332 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700333
Ed Tanous1abe55e2018-09-05 08:30:59 -0700334 static SessionStore& getInstance()
335 {
336 static SessionStore sessionStore;
337 return sessionStore;
338 }
339
Ed Tanous1abe55e2018-09-05 08:30:59 -0700340 void applySessionTimeouts()
341 {
342 auto timeNow = std::chrono::steady_clock::now();
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530343 if (timeNow - lastTimeoutUpdate > std::chrono::seconds(1))
Ed Tanous1abe55e2018-09-05 08:30:59 -0700344 {
345 lastTimeoutUpdate = timeNow;
346 auto authTokensIt = authTokens.begin();
347 while (authTokensIt != authTokens.end())
348 {
349 if (timeNow - authTokensIt->second->lastUpdated >=
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530350 timeoutInSeconds)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700351 {
352 authTokensIt = authTokens.erase(authTokensIt);
Ratan Gupta07386c62019-12-14 14:06:09 +0530353
Ed Tanous1abe55e2018-09-05 08:30:59 -0700354 needWrite = true;
355 }
356 else
357 {
358 authTokensIt++;
359 }
360 }
361 }
362 }
Gunnar Mills83cf8182020-11-11 15:37:34 -0600363
364 SessionStore(const SessionStore&) = delete;
365 SessionStore& operator=(const SessionStore&) = delete;
Ed Tanousecd6a3a2022-01-07 09:18:40 -0800366 SessionStore(SessionStore&&) = delete;
367 SessionStore& operator=(const SessionStore&&) = delete;
368 ~SessionStore() = default;
Gunnar Mills83cf8182020-11-11 15:37:34 -0600369
370 std::unordered_map<std::string, std::shared_ptr<UserSession>,
371 std::hash<std::string>,
372 crow::utility::ConstantTimeCompare>
373 authTokens;
374
375 std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate;
376 bool needWrite{false};
377 std::chrono::seconds timeoutInSeconds;
378 AuthConfigMethods authMethodsConfig;
379
380 private:
Patrick Williams89492a12023-05-10 07:51:34 -0500381 SessionStore() : timeoutInSeconds(1800) {}
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100382};
383
Ed Tanous1abe55e2018-09-05 08:30:59 -0700384} // namespace persistent_data