blob: 95459b32d0736061cc0949e018b11f7c08916d28 [file] [log] [blame]
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +01001#pragma once
2
Gunnar Mills1214b7e2020-06-04 10:11:30 -05003#include "logging.h"
4#include "utility.h"
5
Ed Tanousfc76b8a2020-09-28 17:21:52 -07006#include "random.hpp"
7
James Feista68a8042020-04-15 15:46:44 -07008#include <openssl/rand.h>
9
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010010#include <boost/container/flat_map.hpp>
11#include <boost/uuid/uuid.hpp>
12#include <boost/uuid/uuid_generators.hpp>
13#include <boost/uuid/uuid_io.hpp>
Ratan Gupta12c04ef2019-04-03 10:08:11 +053014#include <dbus_singleton.hpp>
Ed Tanous1abe55e2018-09-05 08:30:59 -070015#include <nlohmann/json.hpp>
16#include <pam_authenticate.hpp>
RAJESWARAN THILLAIGOVINDAN70525172019-07-09 13:15:05 -050017#include <sdbusplus/bus/match.hpp>
Ratan Gupta12c04ef2019-04-03 10:08:11 +053018
Gunnar Mills1214b7e2020-06-04 10:11:30 -050019#include <csignal>
20#include <random>
Ratan Gupta07386c62019-12-14 14:06:09 +053021#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
22#include <ibm/locks.hpp>
23#endif
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010024
Ed Tanous1abe55e2018-09-05 08:30:59 -070025namespace persistent_data
26{
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010027
Ed Tanous51dae672018-09-05 16:07:32 -070028// entropy: 20 characters, 62 possibilities. log2(62^20) = 119 bits of
29// entropy. OWASP recommends at least 64
30// https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#session-id-entropy
31constexpr std::size_t sessionTokenSize = 20;
32
Ed Tanous1abe55e2018-09-05 08:30:59 -070033enum class PersistenceType
34{
35 TIMEOUT, // User session times out after a predetermined amount of time
36 SINGLE_REQUEST // User times out once this request is completed.
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010037};
38
Ed Tanous1abe55e2018-09-05 08:30:59 -070039struct UserSession
40{
41 std::string uniqueId;
42 std::string sessionToken;
43 std::string username;
44 std::string csrfToken;
Sunitha Harish08bdcc72020-05-12 05:17:57 -050045 std::string clientId;
Sunitha Harish92f68222020-05-28 05:09:09 -050046 std::string clientIp;
Ed Tanous1abe55e2018-09-05 08:30:59 -070047 std::chrono::time_point<std::chrono::steady_clock> lastUpdated;
48 PersistenceType persistence;
James Feistf8aa3d22020-04-08 18:32:33 -070049 bool cookieAuth = false;
Joseph Reynolds3bf4e632020-02-06 14:44:32 -060050 bool isConfigureSelfOnly = false;
51
52 // There are two sources of truth for isConfigureSelfOnly:
53 // 1. When pamAuthenticateUser() returns PAM_NEW_AUTHTOK_REQD.
54 // 2. D-Bus User.Manager.GetUserInfo property UserPasswordExpired.
55 // These should be in sync, but the underlying condition can change at any
56 // time. For example, a password can expire or be changed outside of
57 // bmcweb. The value stored here is updated at the start of each
58 // operation and used as the truth within bmcweb.
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +010059
Ed Tanous1abe55e2018-09-05 08:30:59 -070060 /**
61 * @brief Fills object with data from UserSession's JSON representation
62 *
63 * This replaces nlohmann's from_json to ensure no-throw approach
64 *
65 * @param[in] j JSON object from which data should be loaded
66 *
67 * @return a shared pointer if data has been loaded properly, nullptr
68 * otherwise
69 */
70 static std::shared_ptr<UserSession> fromJson(const nlohmann::json& j)
71 {
72 std::shared_ptr<UserSession> userSession =
73 std::make_shared<UserSession>();
74 for (const auto& element : j.items())
75 {
76 const std::string* thisValue =
77 element.value().get_ptr<const std::string*>();
78 if (thisValue == nullptr)
79 {
80 BMCWEB_LOG_ERROR << "Error reading persistent store. Property "
81 << element.key() << " was not of type string";
82 return nullptr;
83 }
84 if (element.key() == "unique_id")
85 {
86 userSession->uniqueId = *thisValue;
87 }
88 else if (element.key() == "session_token")
89 {
90 userSession->sessionToken = *thisValue;
91 }
92 else if (element.key() == "csrf_token")
93 {
94 userSession->csrfToken = *thisValue;
95 }
96 else if (element.key() == "username")
97 {
98 userSession->username = *thisValue;
99 }
Sunitha Harish08bdcc72020-05-12 05:17:57 -0500100 else if (element.key() == "client_id")
101 {
102 userSession->clientId = *thisValue;
103 }
Sunitha Harish92f68222020-05-28 05:09:09 -0500104 else if (element.key() == "client_ip")
105 {
106 userSession->clientIp = *thisValue;
107 }
108
Ed Tanous1abe55e2018-09-05 08:30:59 -0700109 else
110 {
111 BMCWEB_LOG_ERROR
112 << "Got unexpected property reading persistent file: "
113 << element.key();
114 return nullptr;
115 }
116 }
117
118 // For now, sessions that were persisted through a reboot get their idle
119 // timer reset. This could probably be overcome with a better
120 // understanding of wall clock time and steady timer time, possibly
121 // persisting values with wall clock time instead of steady timer, but
122 // the tradeoffs of all the corner cases involved are non-trivial, so
123 // this is done temporarily
124 userSession->lastUpdated = std::chrono::steady_clock::now();
125 userSession->persistence = PersistenceType::TIMEOUT;
126
127 return userSession;
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +0100128 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100129};
130
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100131struct AuthConfigMethods
132{
133 bool xtoken = true;
134 bool cookie = true;
135 bool sessionToken = true;
136 bool basic = true;
Zbigniew Kurzynskicac94c52019-11-07 12:55:04 +0100137 bool tls = false;
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100138
139 void fromJson(const nlohmann::json& j)
140 {
141 for (const auto& element : j.items())
142 {
143 const bool* value = element.value().get_ptr<const bool*>();
144 if (value == nullptr)
145 {
146 continue;
147 }
148
149 if (element.key() == "XToken")
150 {
151 xtoken = *value;
152 }
153 else if (element.key() == "Cookie")
154 {
155 cookie = *value;
156 }
157 else if (element.key() == "SessionToken")
158 {
159 sessionToken = *value;
160 }
161 else if (element.key() == "BasicAuth")
162 {
163 basic = *value;
164 }
Zbigniew Kurzynski501f1e52019-10-02 11:22:11 +0200165 else if (element.key() == "TLS")
166 {
167 tls = *value;
168 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100169 }
170 }
171};
172
Ed Tanous1abe55e2018-09-05 08:30:59 -0700173class SessionStore
174{
175 public:
176 std::shared_ptr<UserSession> generateUserSession(
Ed Tanous39e77502019-03-04 17:35:53 -0800177 const std::string_view username,
Joseph Reynolds3bf4e632020-02-06 14:44:32 -0600178 PersistenceType persistence = PersistenceType::TIMEOUT,
Sunitha Harish92f68222020-05-28 05:09:09 -0500179 bool isConfigureSelfOnly = false, const std::string_view clientId = "",
180 const std::string_view clientIp = "")
Ed Tanous1abe55e2018-09-05 08:30:59 -0700181 {
182 // TODO(ed) find a secure way to not generate session identifiers if
183 // persistence is set to SINGLE_REQUEST
184 static constexpr std::array<char, 62> alphanum = {
Joseph Reynolds368b1d42019-08-15 15:29:06 -0500185 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
186 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
187 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
Ed Tanous1abe55e2018-09-05 08:30:59 -0700188 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
189 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100190
Ed Tanous1abe55e2018-09-05 08:30:59 -0700191 std::string sessionToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700192 sessionToken.resize(sessionTokenSize, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700193 std::uniform_int_distribution<size_t> dist(0, alphanum.size() - 1);
James Feista68a8042020-04-15 15:46:44 -0700194
Ed Tanousfc76b8a2020-09-28 17:21:52 -0700195 bmcweb::OpenSSLGenerator gen;
James Feista68a8042020-04-15 15:46:44 -0700196
Ed Tanous0dfeda62019-10-24 11:21:38 -0700197 for (char& sessionChar : sessionToken)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700198 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700199 sessionChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700200 if (gen.error())
201 {
202 return nullptr;
203 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100204 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700205 // Only need csrf tokens for cookie based auth, token doesn't matter
206 std::string csrfToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700207 csrfToken.resize(sessionTokenSize, '0');
Ed Tanous0dfeda62019-10-24 11:21:38 -0700208 for (char& csrfChar : csrfToken)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700209 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700210 csrfChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700211 if (gen.error())
212 {
213 return nullptr;
214 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700215 }
216
217 std::string uniqueId;
218 uniqueId.resize(10, '0');
Ed Tanous0dfeda62019-10-24 11:21:38 -0700219 for (char& uidChar : uniqueId)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700220 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700221 uidChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700222 if (gen.error())
223 {
224 return nullptr;
225 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700226 }
Sunitha Harish92f68222020-05-28 05:09:09 -0500227 auto session = std::make_shared<UserSession>(
228 UserSession{uniqueId, sessionToken, std::string(username),
229 csrfToken, std::string(clientId), std::string(clientIp),
230 std::chrono::steady_clock::now(), persistence, false,
231 isConfigureSelfOnly});
Ed Tanous1abe55e2018-09-05 08:30:59 -0700232 auto it = authTokens.emplace(std::make_pair(sessionToken, session));
233 // Only need to write to disk if session isn't about to be destroyed.
234 needWrite = persistence == PersistenceType::TIMEOUT;
235 return it.first->second;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100236 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700237
238 std::shared_ptr<UserSession>
Ed Tanous39e77502019-03-04 17:35:53 -0800239 loginSessionByToken(const std::string_view token)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700240 {
241 applySessionTimeouts();
Ed Tanous51dae672018-09-05 16:07:32 -0700242 if (token.size() != sessionTokenSize)
243 {
244 return nullptr;
245 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700246 auto sessionIt = authTokens.find(std::string(token));
247 if (sessionIt == authTokens.end())
248 {
249 return nullptr;
250 }
251 std::shared_ptr<UserSession> userSession = sessionIt->second;
252 userSession->lastUpdated = std::chrono::steady_clock::now();
253 return userSession;
254 }
255
Ed Tanous39e77502019-03-04 17:35:53 -0800256 std::shared_ptr<UserSession> getSessionByUid(const std::string_view uid)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700257 {
258 applySessionTimeouts();
259 // TODO(Ed) this is inefficient
260 auto sessionIt = authTokens.begin();
261 while (sessionIt != authTokens.end())
262 {
263 if (sessionIt->second->uniqueId == uid)
264 {
265 return sessionIt->second;
266 }
267 sessionIt++;
268 }
269 return nullptr;
270 }
271
272 void removeSession(std::shared_ptr<UserSession> session)
273 {
Ratan Gupta07386c62019-12-14 14:06:09 +0530274#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
275 crow::ibm_mc_lock::Lock::getInstance().releaseLock(session->uniqueId);
276#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700277 authTokens.erase(session->sessionToken);
278 needWrite = true;
279 }
280
281 std::vector<const std::string*> getUniqueIds(
282 bool getAll = true,
283 const PersistenceType& type = PersistenceType::SINGLE_REQUEST)
284 {
285 applySessionTimeouts();
286
287 std::vector<const std::string*> ret;
288 ret.reserve(authTokens.size());
289 for (auto& session : authTokens)
290 {
291 if (getAll || type == session.second->persistence)
292 {
293 ret.push_back(&session.second->uniqueId);
294 }
295 }
296 return ret;
297 }
298
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100299 void updateAuthMethodsConfig(const AuthConfigMethods& config)
300 {
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100301 bool isTLSchanged = (authMethodsConfig.tls != config.tls);
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100302 authMethodsConfig = config;
303 needWrite = true;
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100304 if (isTLSchanged)
305 {
306 // recreate socket connections with new settings
307 std::raise(SIGHUP);
308 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100309 }
310
311 AuthConfigMethods& getAuthMethodsConfig()
312 {
313 return authMethodsConfig;
314 }
315
Ed Tanous1abe55e2018-09-05 08:30:59 -0700316 bool needsWrite()
317 {
318 return needWrite;
319 }
Ed Tanous271584a2019-07-09 16:24:22 -0700320 int64_t getTimeoutInSeconds() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700321 {
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530322 return std::chrono::seconds(timeoutInSeconds).count();
323 }
324
325 void updateSessionTimeout(std::chrono::seconds newTimeoutInSeconds)
326 {
327 timeoutInSeconds = newTimeoutInSeconds;
328 needWrite = true;
Ed Tanous23a21a12020-07-25 04:45:05 +0000329 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700330
Ed Tanous1abe55e2018-09-05 08:30:59 -0700331 static SessionStore& getInstance()
332 {
333 static SessionStore sessionStore;
334 return sessionStore;
335 }
336
337 SessionStore(const SessionStore&) = delete;
338 SessionStore& operator=(const SessionStore&) = delete;
339
Ed Tanous52cc1122020-07-18 13:51:21 -0700340 std::unordered_map<std::string, std::shared_ptr<UserSession>,
341 std::hash<std::string>,
342 crow::utility::ConstantTimeCompare>
343 authTokens;
344
345 std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate;
346 bool needWrite{false};
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530347 std::chrono::seconds timeoutInSeconds;
Ed Tanous52cc1122020-07-18 13:51:21 -0700348 AuthConfigMethods authMethodsConfig;
349
Ed Tanous1abe55e2018-09-05 08:30:59 -0700350 private:
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530351 SessionStore() : timeoutInSeconds(3600)
Gunnar Mills1214b7e2020-06-04 10:11:30 -0500352 {}
Ed Tanous1abe55e2018-09-05 08:30:59 -0700353
354 void applySessionTimeouts()
355 {
356 auto timeNow = std::chrono::steady_clock::now();
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530357 if (timeNow - lastTimeoutUpdate > std::chrono::seconds(1))
Ed Tanous1abe55e2018-09-05 08:30:59 -0700358 {
359 lastTimeoutUpdate = timeNow;
360 auto authTokensIt = authTokens.begin();
361 while (authTokensIt != authTokens.end())
362 {
363 if (timeNow - authTokensIt->second->lastUpdated >=
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530364 timeoutInSeconds)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700365 {
Ratan Gupta07386c62019-12-14 14:06:09 +0530366#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
367 crow::ibm_mc_lock::Lock::getInstance().releaseLock(
368 authTokensIt->second->uniqueId);
369#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700370 authTokensIt = authTokens.erase(authTokensIt);
Ratan Gupta07386c62019-12-14 14:06:09 +0530371
Ed Tanous1abe55e2018-09-05 08:30:59 -0700372 needWrite = true;
373 }
374 else
375 {
376 authTokensIt++;
377 }
378 }
379 }
380 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100381};
382
Ed Tanous1abe55e2018-09-05 08:30:59 -0700383} // namespace persistent_data