blob: 10e29c830f035619f8383f831c70f180288445a5 [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 */
63 static std::shared_ptr<UserSession> fromJson(const nlohmann::json& j)
64 {
65 std::shared_ptr<UserSession> userSession =
66 std::make_shared<UserSession>();
67 for (const auto& element : j.items())
68 {
69 const std::string* thisValue =
70 element.value().get_ptr<const std::string*>();
71 if (thisValue == nullptr)
72 {
Ed Tanous62598e32023-07-17 17:06:25 -070073 BMCWEB_LOG_ERROR(
74 "Error reading persistent store. Property {} was not of type string",
75 element.key());
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 {
Ed Tanous62598e32023-07-17 17:06:25 -0700105 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 {
Ed Tanous62598e32023-07-17 17:06:25 -0700118 BMCWEB_LOG_DEBUG("Session missing required security "
119 "information, refusing to restore");
Ed Tanousdc511aa2020-10-21 12:33:42 -0700120 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{
Ed Tanous25b54db2024-04-17 15:40:31 -0700138 bool basic = BMCWEB_BASIC_AUTH;
139 bool sessionToken = BMCWEB_SESSION_AUTH;
140 bool xtoken = BMCWEB_XTOKEN_AUTH;
141 bool cookie = BMCWEB_COOKIE_AUTH;
142 bool tls = BMCWEB_MUTUAL_TLS_AUTH;
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100143
144 void fromJson(const nlohmann::json& j)
145 {
146 for (const auto& element : j.items())
147 {
148 const bool* value = element.value().get_ptr<const bool*>();
149 if (value == nullptr)
150 {
151 continue;
152 }
153
154 if (element.key() == "XToken")
155 {
156 xtoken = *value;
157 }
158 else if (element.key() == "Cookie")
159 {
160 cookie = *value;
161 }
162 else if (element.key() == "SessionToken")
163 {
164 sessionToken = *value;
165 }
166 else if (element.key() == "BasicAuth")
167 {
168 basic = *value;
169 }
Zbigniew Kurzynski501f1e52019-10-02 11:22:11 +0200170 else if (element.key() == "TLS")
171 {
172 tls = *value;
173 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100174 }
175 }
176};
177
Ed Tanous1abe55e2018-09-05 08:30:59 -0700178class SessionStore
179{
180 public:
181 std::shared_ptr<UserSession> generateUserSession(
Ed Tanous26ccae32023-02-16 10:28:44 -0800182 std::string_view username, const boost::asio::ip::address& clientIp,
Ed Tanousbb759e32022-08-02 17:07:54 -0700183 const std::optional<std::string>& clientId,
Joseph Reynolds3bf4e632020-02-06 14:44:32 -0600184 PersistenceType persistence = PersistenceType::TIMEOUT,
Sunitha Harishd3239222021-02-24 15:33:29 +0530185 bool isConfigureSelfOnly = false)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700186 {
Ed Tanous1abe55e2018-09-05 08:30:59 -0700187 // Only need csrf tokens for cookie based auth, token doesn't matter
Ed Tanousb7f3a822024-06-05 08:45:25 -0700188 std::string sessionToken =
189 bmcweb::getRandomIdOfLength(sessionTokenSize);
190 std::string csrfToken = bmcweb::getRandomIdOfLength(sessionTokenSize);
191 std::string uniqueId = bmcweb::getRandomIdOfLength(10);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700192
Ed Tanousb7f3a822024-06-05 08:45:25 -0700193 //
194 if (sessionToken.empty() || csrfToken.empty() || uniqueId.empty())
Ed Tanous1abe55e2018-09-05 08:30:59 -0700195 {
Ed Tanousb7f3a822024-06-05 08:45:25 -0700196 BMCWEB_LOG_ERROR("Failed to generate session tokens");
197 return nullptr;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700198 }
Jiaqing Zhao41d61c82021-12-07 13:21:47 +0800199
Ed Tanous47f29342024-03-19 12:18:06 -0700200 auto session = std::make_shared<UserSession>(
201 UserSession{uniqueId,
202 sessionToken,
203 std::string(username),
204 csrfToken,
205 clientId,
206 redfish::ip_util::toString(clientIp),
207 std::chrono::steady_clock::now(),
208 persistence,
209 false,
210 isConfigureSelfOnly,
211 "",
212 {}});
Patrick Williams41713dd2022-09-28 06:48:07 -0500213 auto it = authTokens.emplace(sessionToken, session);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700214 // Only need to write to disk if session isn't about to be destroyed.
215 needWrite = persistence == PersistenceType::TIMEOUT;
216 return it.first->second;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100217 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700218
Ed Tanous26ccae32023-02-16 10:28:44 -0800219 std::shared_ptr<UserSession> loginSessionByToken(std::string_view token)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700220 {
221 applySessionTimeouts();
Ed Tanous51dae672018-09-05 16:07:32 -0700222 if (token.size() != sessionTokenSize)
223 {
224 return nullptr;
225 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700226 auto sessionIt = authTokens.find(std::string(token));
227 if (sessionIt == authTokens.end())
228 {
229 return nullptr;
230 }
231 std::shared_ptr<UserSession> userSession = sessionIt->second;
232 userSession->lastUpdated = std::chrono::steady_clock::now();
233 return userSession;
234 }
235
Ed Tanous26ccae32023-02-16 10:28:44 -0800236 std::shared_ptr<UserSession> getSessionByUid(std::string_view uid)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700237 {
238 applySessionTimeouts();
239 // TODO(Ed) this is inefficient
240 auto sessionIt = authTokens.begin();
241 while (sessionIt != authTokens.end())
242 {
243 if (sessionIt->second->uniqueId == uid)
244 {
245 return sessionIt->second;
246 }
247 sessionIt++;
248 }
249 return nullptr;
250 }
251
Ed Tanousb5a76932020-09-29 16:16:58 -0700252 void removeSession(const std::shared_ptr<UserSession>& session)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700253 {
254 authTokens.erase(session->sessionToken);
255 needWrite = true;
256 }
257
258 std::vector<const std::string*> getUniqueIds(
259 bool getAll = true,
260 const PersistenceType& type = PersistenceType::SINGLE_REQUEST)
261 {
262 applySessionTimeouts();
263
264 std::vector<const std::string*> ret;
265 ret.reserve(authTokens.size());
266 for (auto& session : authTokens)
267 {
268 if (getAll || type == session.second->persistence)
269 {
270 ret.push_back(&session.second->uniqueId);
271 }
272 }
273 return ret;
274 }
275
Xie Ning9fa06f12022-06-29 18:27:47 +0800276 void removeSessionsByUsername(std::string_view username)
277 {
278 std::erase_if(authTokens, [username](const auto& value) {
279 if (value.second == nullptr)
280 {
281 return false;
282 }
283 return value.second->username == username;
284 });
285 }
286
Ravi Tejae518ef32024-05-16 10:33:08 -0500287 void removeSessionsByUsernameExceptSession(
288 std::string_view username, const std::shared_ptr<UserSession>& session)
289 {
290 std::erase_if(authTokens, [username, session](const auto& value) {
291 if (value.second == nullptr)
292 {
293 return false;
294 }
295
296 return value.second->username == username &&
297 value.second->uniqueId != session->uniqueId;
298 });
299 }
300
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100301 void updateAuthMethodsConfig(const AuthConfigMethods& config)
302 {
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100303 bool isTLSchanged = (authMethodsConfig.tls != config.tls);
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100304 authMethodsConfig = config;
305 needWrite = true;
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100306 if (isTLSchanged)
307 {
308 // recreate socket connections with new settings
309 std::raise(SIGHUP);
310 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100311 }
312
313 AuthConfigMethods& getAuthMethodsConfig()
314 {
315 return authMethodsConfig;
316 }
317
Ed Tanous9eb808c2022-01-25 10:19:23 -0800318 bool needsWrite() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700319 {
320 return needWrite;
321 }
Ed Tanous271584a2019-07-09 16:24:22 -0700322 int64_t getTimeoutInSeconds() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700323 {
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530324 return std::chrono::seconds(timeoutInSeconds).count();
325 }
326
327 void updateSessionTimeout(std::chrono::seconds newTimeoutInSeconds)
328 {
329 timeoutInSeconds = newTimeoutInSeconds;
330 needWrite = true;
Ed Tanous23a21a12020-07-25 04:45:05 +0000331 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700332
Ed Tanous1abe55e2018-09-05 08:30:59 -0700333 static SessionStore& getInstance()
334 {
335 static SessionStore sessionStore;
336 return sessionStore;
337 }
338
Ed Tanous1abe55e2018-09-05 08:30:59 -0700339 void applySessionTimeouts()
340 {
341 auto timeNow = std::chrono::steady_clock::now();
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530342 if (timeNow - lastTimeoutUpdate > std::chrono::seconds(1))
Ed Tanous1abe55e2018-09-05 08:30:59 -0700343 {
344 lastTimeoutUpdate = timeNow;
345 auto authTokensIt = authTokens.begin();
346 while (authTokensIt != authTokens.end())
347 {
348 if (timeNow - authTokensIt->second->lastUpdated >=
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530349 timeoutInSeconds)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700350 {
351 authTokensIt = authTokens.erase(authTokensIt);
Ratan Gupta07386c62019-12-14 14:06:09 +0530352
Ed Tanous1abe55e2018-09-05 08:30:59 -0700353 needWrite = true;
354 }
355 else
356 {
357 authTokensIt++;
358 }
359 }
360 }
361 }
Gunnar Mills83cf8182020-11-11 15:37:34 -0600362
363 SessionStore(const SessionStore&) = delete;
364 SessionStore& operator=(const SessionStore&) = delete;
Ed Tanousecd6a3a2022-01-07 09:18:40 -0800365 SessionStore(SessionStore&&) = delete;
366 SessionStore& operator=(const SessionStore&&) = delete;
367 ~SessionStore() = default;
Gunnar Mills83cf8182020-11-11 15:37:34 -0600368
369 std::unordered_map<std::string, std::shared_ptr<UserSession>,
370 std::hash<std::string>,
371 crow::utility::ConstantTimeCompare>
372 authTokens;
373
374 std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate;
375 bool needWrite{false};
376 std::chrono::seconds timeoutInSeconds;
377 AuthConfigMethods authMethodsConfig;
378
379 private:
Patrick Williams89492a12023-05-10 07:51:34 -0500380 SessionStore() : timeoutInSeconds(1800) {}
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100381};
382
Ed Tanous1abe55e2018-09-05 08:30:59 -0700383} // namespace persistent_data