blob: 1eace0ddad75540a01faa75bf28e7b85519f79e1 [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 Tanousfc76b8a2020-09-28 17:21:52 -07006
James Feista68a8042020-04-15 15:46:44 -07007#include <openssl/rand.h>
8
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +01009#include <boost/container/flat_map.hpp>
10#include <boost/uuid/uuid.hpp>
11#include <boost/uuid/uuid_generators.hpp>
12#include <boost/uuid/uuid_io.hpp>
Ratan Gupta12c04ef2019-04-03 10:08:11 +053013#include <dbus_singleton.hpp>
Ed Tanous1abe55e2018-09-05 08:30:59 -070014#include <nlohmann/json.hpp>
15#include <pam_authenticate.hpp>
RAJESWARAN THILLAIGOVINDAN70525172019-07-09 13:15:05 -050016#include <sdbusplus/bus/match.hpp>
Ratan Gupta12c04ef2019-04-03 10:08:11 +053017
Gunnar Mills1214b7e2020-06-04 10:11:30 -050018#include <csignal>
19#include <random>
Ratan Gupta07386c62019-12-14 14:06:09 +053020#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
21#include <ibm/locks.hpp>
22#endif
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010023
Ed Tanous1abe55e2018-09-05 08:30:59 -070024namespace persistent_data
25{
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010026
Ed Tanous51dae672018-09-05 16:07:32 -070027// entropy: 20 characters, 62 possibilities. log2(62^20) = 119 bits of
28// entropy. OWASP recommends at least 64
29// https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#session-id-entropy
30constexpr std::size_t sessionTokenSize = 20;
31
Ed Tanous1abe55e2018-09-05 08:30:59 -070032enum class PersistenceType
33{
34 TIMEOUT, // User session times out after a predetermined amount of time
35 SINGLE_REQUEST // User times out once this request is completed.
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010036};
37
Ed Tanous1abe55e2018-09-05 08:30:59 -070038struct UserSession
39{
40 std::string uniqueId;
41 std::string sessionToken;
42 std::string username;
43 std::string csrfToken;
Sunitha Harish08bdcc72020-05-12 05:17:57 -050044 std::string clientId;
Sunitha Harish92f68222020-05-28 05:09:09 -050045 std::string clientIp;
Ed Tanous1abe55e2018-09-05 08:30:59 -070046 std::chrono::time_point<std::chrono::steady_clock> lastUpdated;
47 PersistenceType persistence;
James Feistf8aa3d22020-04-08 18:32:33 -070048 bool cookieAuth = false;
Joseph Reynolds3bf4e632020-02-06 14:44:32 -060049 bool isConfigureSelfOnly = false;
50
51 // There are two sources of truth for isConfigureSelfOnly:
52 // 1. When pamAuthenticateUser() returns PAM_NEW_AUTHTOK_REQD.
53 // 2. D-Bus User.Manager.GetUserInfo property UserPasswordExpired.
54 // These should be in sync, but the underlying condition can change at any
55 // time. For example, a password can expire or be changed outside of
56 // bmcweb. The value stored here is updated at the start of each
57 // operation and used as the truth within bmcweb.
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +010058
Ed Tanous1abe55e2018-09-05 08:30:59 -070059 /**
60 * @brief Fills object with data from UserSession's JSON representation
61 *
62 * This replaces nlohmann's from_json to ensure no-throw approach
63 *
64 * @param[in] j JSON object from which data should be loaded
65 *
66 * @return a shared pointer if data has been loaded properly, nullptr
67 * otherwise
68 */
69 static std::shared_ptr<UserSession> fromJson(const nlohmann::json& j)
70 {
71 std::shared_ptr<UserSession> userSession =
72 std::make_shared<UserSession>();
73 for (const auto& element : j.items())
74 {
75 const std::string* thisValue =
76 element.value().get_ptr<const std::string*>();
77 if (thisValue == nullptr)
78 {
79 BMCWEB_LOG_ERROR << "Error reading persistent store. Property "
80 << element.key() << " was not of type string";
Ed Tanousdc511aa2020-10-21 12:33:42 -070081 continue;
Ed Tanous1abe55e2018-09-05 08:30:59 -070082 }
83 if (element.key() == "unique_id")
84 {
85 userSession->uniqueId = *thisValue;
86 }
87 else if (element.key() == "session_token")
88 {
89 userSession->sessionToken = *thisValue;
90 }
91 else if (element.key() == "csrf_token")
92 {
93 userSession->csrfToken = *thisValue;
94 }
95 else if (element.key() == "username")
96 {
97 userSession->username = *thisValue;
98 }
Ed Tanousdc511aa2020-10-21 12:33:42 -070099#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
Sunitha Harish08bdcc72020-05-12 05:17:57 -0500100 else if (element.key() == "client_id")
101 {
102 userSession->clientId = *thisValue;
103 }
Ed Tanousdc511aa2020-10-21 12:33:42 -0700104#endif
Sunitha Harish92f68222020-05-28 05:09:09 -0500105 else if (element.key() == "client_ip")
106 {
107 userSession->clientIp = *thisValue;
108 }
109
Ed Tanous1abe55e2018-09-05 08:30:59 -0700110 else
111 {
112 BMCWEB_LOG_ERROR
113 << "Got unexpected property reading persistent file: "
114 << element.key();
Ed Tanousdc511aa2020-10-21 12:33:42 -0700115 continue;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700116 }
117 }
Ed Tanousdc511aa2020-10-21 12:33:42 -0700118 // If any of these fields are missing, we can't restore the session, as
119 // we don't have enough information. These 4 fields have been present
120 // in every version of this file in bmcwebs history, so any file, even
121 // on upgrade, should have these present
122 if (userSession->uniqueId.empty() || userSession->username.empty() ||
123 userSession->sessionToken.empty() || userSession->csrfToken.empty())
124 {
125 BMCWEB_LOG_DEBUG << "Session missing required security "
126 "information, refusing to restore";
127 return nullptr;
128 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700129
130 // For now, sessions that were persisted through a reboot get their idle
131 // timer reset. This could probably be overcome with a better
132 // understanding of wall clock time and steady timer time, possibly
133 // persisting values with wall clock time instead of steady timer, but
134 // the tradeoffs of all the corner cases involved are non-trivial, so
135 // this is done temporarily
136 userSession->lastUpdated = std::chrono::steady_clock::now();
137 userSession->persistence = PersistenceType::TIMEOUT;
138
139 return userSession;
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +0100140 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100141};
142
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100143struct AuthConfigMethods
144{
145 bool xtoken = true;
146 bool cookie = true;
147 bool sessionToken = true;
148 bool basic = true;
Zbigniew Kurzynskicac94c52019-11-07 12:55:04 +0100149 bool tls = false;
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100150
151 void fromJson(const nlohmann::json& j)
152 {
153 for (const auto& element : j.items())
154 {
155 const bool* value = element.value().get_ptr<const bool*>();
156 if (value == nullptr)
157 {
158 continue;
159 }
160
161 if (element.key() == "XToken")
162 {
163 xtoken = *value;
164 }
165 else if (element.key() == "Cookie")
166 {
167 cookie = *value;
168 }
169 else if (element.key() == "SessionToken")
170 {
171 sessionToken = *value;
172 }
173 else if (element.key() == "BasicAuth")
174 {
175 basic = *value;
176 }
Zbigniew Kurzynski501f1e52019-10-02 11:22:11 +0200177 else if (element.key() == "TLS")
178 {
179 tls = *value;
180 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100181 }
182 }
183};
184
Ed Tanous1abe55e2018-09-05 08:30:59 -0700185class SessionStore
186{
187 public:
188 std::shared_ptr<UserSession> generateUserSession(
Ed Tanous39e77502019-03-04 17:35:53 -0800189 const std::string_view username,
Joseph Reynolds3bf4e632020-02-06 14:44:32 -0600190 PersistenceType persistence = PersistenceType::TIMEOUT,
Sunitha Harish92f68222020-05-28 05:09:09 -0500191 bool isConfigureSelfOnly = false, const std::string_view clientId = "",
192 const std::string_view clientIp = "")
Ed Tanous1abe55e2018-09-05 08:30:59 -0700193 {
194 // TODO(ed) find a secure way to not generate session identifiers if
195 // persistence is set to SINGLE_REQUEST
196 static constexpr std::array<char, 62> alphanum = {
Joseph Reynolds368b1d42019-08-15 15:29:06 -0500197 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
198 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
199 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
Ed Tanous1abe55e2018-09-05 08:30:59 -0700200 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
201 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100202
Ed Tanous1abe55e2018-09-05 08:30:59 -0700203 std::string sessionToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700204 sessionToken.resize(sessionTokenSize, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700205 std::uniform_int_distribution<size_t> dist(0, alphanum.size() - 1);
James Feista68a8042020-04-15 15:46:44 -0700206
Ed Tanousfc76b8a2020-09-28 17:21:52 -0700207 bmcweb::OpenSSLGenerator gen;
James Feista68a8042020-04-15 15:46:44 -0700208
Ed Tanous0dfeda62019-10-24 11:21:38 -0700209 for (char& sessionChar : sessionToken)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700210 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700211 sessionChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700212 if (gen.error())
213 {
214 return nullptr;
215 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100216 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700217 // Only need csrf tokens for cookie based auth, token doesn't matter
218 std::string csrfToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700219 csrfToken.resize(sessionTokenSize, '0');
Ed Tanous0dfeda62019-10-24 11:21:38 -0700220 for (char& csrfChar : csrfToken)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700221 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700222 csrfChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700223 if (gen.error())
224 {
225 return nullptr;
226 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700227 }
228
229 std::string uniqueId;
230 uniqueId.resize(10, '0');
Ed Tanous0dfeda62019-10-24 11:21:38 -0700231 for (char& uidChar : uniqueId)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700232 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700233 uidChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700234 if (gen.error())
235 {
236 return nullptr;
237 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700238 }
Sunitha Harish92f68222020-05-28 05:09:09 -0500239 auto session = std::make_shared<UserSession>(
240 UserSession{uniqueId, sessionToken, std::string(username),
241 csrfToken, std::string(clientId), std::string(clientIp),
242 std::chrono::steady_clock::now(), persistence, false,
243 isConfigureSelfOnly});
Ed Tanous1abe55e2018-09-05 08:30:59 -0700244 auto it = authTokens.emplace(std::make_pair(sessionToken, session));
245 // Only need to write to disk if session isn't about to be destroyed.
246 needWrite = persistence == PersistenceType::TIMEOUT;
247 return it.first->second;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100248 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700249
250 std::shared_ptr<UserSession>
Ed Tanous39e77502019-03-04 17:35:53 -0800251 loginSessionByToken(const 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 Tanous39e77502019-03-04 17:35:53 -0800268 std::shared_ptr<UserSession> getSessionByUid(const 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 {
Ratan Gupta07386c62019-12-14 14:06:09 +0530286#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
287 crow::ibm_mc_lock::Lock::getInstance().releaseLock(session->uniqueId);
288#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700289 authTokens.erase(session->sessionToken);
290 needWrite = true;
291 }
292
293 std::vector<const std::string*> getUniqueIds(
294 bool getAll = true,
295 const PersistenceType& type = PersistenceType::SINGLE_REQUEST)
296 {
297 applySessionTimeouts();
298
299 std::vector<const std::string*> ret;
300 ret.reserve(authTokens.size());
301 for (auto& session : authTokens)
302 {
303 if (getAll || type == session.second->persistence)
304 {
305 ret.push_back(&session.second->uniqueId);
306 }
307 }
308 return ret;
309 }
310
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100311 void updateAuthMethodsConfig(const AuthConfigMethods& config)
312 {
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100313 bool isTLSchanged = (authMethodsConfig.tls != config.tls);
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100314 authMethodsConfig = config;
315 needWrite = true;
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100316 if (isTLSchanged)
317 {
318 // recreate socket connections with new settings
319 std::raise(SIGHUP);
320 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100321 }
322
323 AuthConfigMethods& getAuthMethodsConfig()
324 {
325 return authMethodsConfig;
326 }
327
Ed Tanous1abe55e2018-09-05 08:30:59 -0700328 bool needsWrite()
329 {
330 return needWrite;
331 }
Ed Tanous271584a2019-07-09 16:24:22 -0700332 int64_t getTimeoutInSeconds() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700333 {
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530334 return std::chrono::seconds(timeoutInSeconds).count();
335 }
336
337 void updateSessionTimeout(std::chrono::seconds newTimeoutInSeconds)
338 {
339 timeoutInSeconds = newTimeoutInSeconds;
340 needWrite = true;
Ed Tanous23a21a12020-07-25 04:45:05 +0000341 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700342
Ed Tanous1abe55e2018-09-05 08:30:59 -0700343 static SessionStore& getInstance()
344 {
345 static SessionStore sessionStore;
346 return sessionStore;
347 }
348
Ed Tanous1abe55e2018-09-05 08:30:59 -0700349 void applySessionTimeouts()
350 {
351 auto timeNow = std::chrono::steady_clock::now();
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530352 if (timeNow - lastTimeoutUpdate > std::chrono::seconds(1))
Ed Tanous1abe55e2018-09-05 08:30:59 -0700353 {
354 lastTimeoutUpdate = timeNow;
355 auto authTokensIt = authTokens.begin();
356 while (authTokensIt != authTokens.end())
357 {
358 if (timeNow - authTokensIt->second->lastUpdated >=
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530359 timeoutInSeconds)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700360 {
Ratan Gupta07386c62019-12-14 14:06:09 +0530361#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
362 crow::ibm_mc_lock::Lock::getInstance().releaseLock(
363 authTokensIt->second->uniqueId);
364#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700365 authTokensIt = authTokens.erase(authTokensIt);
Ratan Gupta07386c62019-12-14 14:06:09 +0530366
Ed Tanous1abe55e2018-09-05 08:30:59 -0700367 needWrite = true;
368 }
369 else
370 {
371 authTokensIt++;
372 }
373 }
374 }
375 }
Gunnar Mills83cf8182020-11-11 15:37:34 -0600376
377 SessionStore(const SessionStore&) = delete;
378 SessionStore& operator=(const SessionStore&) = delete;
379
380 std::unordered_map<std::string, std::shared_ptr<UserSession>,
381 std::hash<std::string>,
382 crow::utility::ConstantTimeCompare>
383 authTokens;
384
385 std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate;
386 bool needWrite{false};
387 std::chrono::seconds timeoutInSeconds;
388 AuthConfigMethods authMethodsConfig;
389
390 private:
391 SessionStore() : timeoutInSeconds(3600)
392 {}
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100393};
394
Ed Tanous1abe55e2018-09-05 08:30:59 -0700395} // namespace persistent_data