blob: 23de570f2d725d390636000d34acbe1a6bcef731 [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
James Feista68a8042020-04-15 15:46:44 -07006#include <openssl/rand.h>
7
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +01008#include <boost/container/flat_map.hpp>
9#include <boost/uuid/uuid.hpp>
10#include <boost/uuid/uuid_generators.hpp>
11#include <boost/uuid/uuid_io.hpp>
Ratan Gupta12c04ef2019-04-03 10:08:11 +053012#include <dbus_singleton.hpp>
Ed Tanous1abe55e2018-09-05 08:30:59 -070013#include <nlohmann/json.hpp>
14#include <pam_authenticate.hpp>
RAJESWARAN THILLAIGOVINDAN70525172019-07-09 13:15:05 -050015#include <sdbusplus/bus/match.hpp>
Ratan Gupta12c04ef2019-04-03 10:08:11 +053016
Gunnar Mills1214b7e2020-06-04 10:11:30 -050017#include <csignal>
18#include <random>
Ratan Gupta07386c62019-12-14 14:06:09 +053019#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
20#include <ibm/locks.hpp>
21#endif
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010022
Ed Tanous1abe55e2018-09-05 08:30:59 -070023namespace persistent_data
24{
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010025
Ed Tanous51dae672018-09-05 16:07:32 -070026// entropy: 20 characters, 62 possibilities. log2(62^20) = 119 bits of
27// entropy. OWASP recommends at least 64
28// https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#session-id-entropy
29constexpr std::size_t sessionTokenSize = 20;
30
Ed Tanous1abe55e2018-09-05 08:30:59 -070031enum class PersistenceType
32{
33 TIMEOUT, // User session times out after a predetermined amount of time
34 SINGLE_REQUEST // User times out once this request is completed.
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010035};
36
Ed Tanous1abe55e2018-09-05 08:30:59 -070037struct UserSession
38{
39 std::string uniqueId;
40 std::string sessionToken;
41 std::string username;
42 std::string csrfToken;
Sunitha Harish08bdcc72020-05-12 05:17:57 -050043 std::string clientId;
Sunitha Harish92f68222020-05-28 05:09:09 -050044 std::string clientIp;
Ed Tanous1abe55e2018-09-05 08:30:59 -070045 std::chrono::time_point<std::chrono::steady_clock> lastUpdated;
46 PersistenceType persistence;
James Feistf8aa3d22020-04-08 18:32:33 -070047 bool cookieAuth = false;
Joseph Reynolds3bf4e632020-02-06 14:44:32 -060048 bool isConfigureSelfOnly = false;
49
50 // There are two sources of truth for isConfigureSelfOnly:
51 // 1. When pamAuthenticateUser() returns PAM_NEW_AUTHTOK_REQD.
52 // 2. D-Bus User.Manager.GetUserInfo property UserPasswordExpired.
53 // These should be in sync, but the underlying condition can change at any
54 // time. For example, a password can expire or be changed outside of
55 // bmcweb. The value stored here is updated at the start of each
56 // operation and used as the truth within bmcweb.
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +010057
Ed Tanous1abe55e2018-09-05 08:30:59 -070058 /**
59 * @brief Fills object with data from UserSession's JSON representation
60 *
61 * This replaces nlohmann's from_json to ensure no-throw approach
62 *
63 * @param[in] j JSON object from which data should be loaded
64 *
65 * @return a shared pointer if data has been loaded properly, nullptr
66 * otherwise
67 */
68 static std::shared_ptr<UserSession> fromJson(const nlohmann::json& j)
69 {
70 std::shared_ptr<UserSession> userSession =
71 std::make_shared<UserSession>();
72 for (const auto& element : j.items())
73 {
74 const std::string* thisValue =
75 element.value().get_ptr<const std::string*>();
76 if (thisValue == nullptr)
77 {
78 BMCWEB_LOG_ERROR << "Error reading persistent store. Property "
79 << element.key() << " was not of type string";
80 return nullptr;
81 }
82 if (element.key() == "unique_id")
83 {
84 userSession->uniqueId = *thisValue;
85 }
86 else if (element.key() == "session_token")
87 {
88 userSession->sessionToken = *thisValue;
89 }
90 else if (element.key() == "csrf_token")
91 {
92 userSession->csrfToken = *thisValue;
93 }
94 else if (element.key() == "username")
95 {
96 userSession->username = *thisValue;
97 }
Sunitha Harish08bdcc72020-05-12 05:17:57 -050098 else if (element.key() == "client_id")
99 {
100 userSession->clientId = *thisValue;
101 }
Sunitha Harish92f68222020-05-28 05:09:09 -0500102 else if (element.key() == "client_ip")
103 {
104 userSession->clientIp = *thisValue;
105 }
106
Ed Tanous1abe55e2018-09-05 08:30:59 -0700107 else
108 {
109 BMCWEB_LOG_ERROR
110 << "Got unexpected property reading persistent file: "
111 << element.key();
112 return nullptr;
113 }
114 }
115
116 // For now, sessions that were persisted through a reboot get their idle
117 // timer reset. This could probably be overcome with a better
118 // understanding of wall clock time and steady timer time, possibly
119 // persisting values with wall clock time instead of steady timer, but
120 // the tradeoffs of all the corner cases involved are non-trivial, so
121 // this is done temporarily
122 userSession->lastUpdated = std::chrono::steady_clock::now();
123 userSession->persistence = PersistenceType::TIMEOUT;
124
125 return userSession;
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +0100126 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100127};
128
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100129struct AuthConfigMethods
130{
131 bool xtoken = true;
132 bool cookie = true;
133 bool sessionToken = true;
134 bool basic = true;
Zbigniew Kurzynskicac94c52019-11-07 12:55:04 +0100135 bool tls = false;
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100136
137 void fromJson(const nlohmann::json& j)
138 {
139 for (const auto& element : j.items())
140 {
141 const bool* value = element.value().get_ptr<const bool*>();
142 if (value == nullptr)
143 {
144 continue;
145 }
146
147 if (element.key() == "XToken")
148 {
149 xtoken = *value;
150 }
151 else if (element.key() == "Cookie")
152 {
153 cookie = *value;
154 }
155 else if (element.key() == "SessionToken")
156 {
157 sessionToken = *value;
158 }
159 else if (element.key() == "BasicAuth")
160 {
161 basic = *value;
162 }
Zbigniew Kurzynski501f1e52019-10-02 11:22:11 +0200163 else if (element.key() == "TLS")
164 {
165 tls = *value;
166 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100167 }
168 }
169};
170
James Feista68a8042020-04-15 15:46:44 -0700171struct OpenSSLGenerator
172{
James Feista68a8042020-04-15 15:46:44 -0700173 uint8_t operator()(void)
174 {
175 uint8_t index = 0;
176 int rc = RAND_bytes(&index, sizeof(index));
177 if (rc != opensslSuccess)
178 {
179 std::cerr << "Cannot get random number\n";
180 err = true;
181 }
182
183 return index;
Ed Tanous23a21a12020-07-25 04:45:05 +0000184 }
James Feista68a8042020-04-15 15:46:44 -0700185
186 uint8_t max()
187 {
188 return std::numeric_limits<uint8_t>::max();
189 }
190 uint8_t min()
191 {
192 return std::numeric_limits<uint8_t>::min();
193 }
194
195 bool error()
196 {
197 return err;
198 }
199
200 // all generators require this variable
201 using result_type = uint8_t;
202
203 private:
204 // RAND_bytes() returns 1 on success, 0 otherwise. -1 if bad function
205 static constexpr int opensslSuccess = 1;
206 bool err = false;
207};
208
Ed Tanous1abe55e2018-09-05 08:30:59 -0700209class SessionStore
210{
211 public:
212 std::shared_ptr<UserSession> generateUserSession(
Ed Tanous39e77502019-03-04 17:35:53 -0800213 const std::string_view username,
Joseph Reynolds3bf4e632020-02-06 14:44:32 -0600214 PersistenceType persistence = PersistenceType::TIMEOUT,
Sunitha Harish92f68222020-05-28 05:09:09 -0500215 bool isConfigureSelfOnly = false, const std::string_view clientId = "",
216 const std::string_view clientIp = "")
Ed Tanous1abe55e2018-09-05 08:30:59 -0700217 {
218 // TODO(ed) find a secure way to not generate session identifiers if
219 // persistence is set to SINGLE_REQUEST
220 static constexpr std::array<char, 62> alphanum = {
Joseph Reynolds368b1d42019-08-15 15:29:06 -0500221 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
222 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
223 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
Ed Tanous1abe55e2018-09-05 08:30:59 -0700224 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
225 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100226
Ed Tanous1abe55e2018-09-05 08:30:59 -0700227 std::string sessionToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700228 sessionToken.resize(sessionTokenSize, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700229 std::uniform_int_distribution<size_t> dist(0, alphanum.size() - 1);
James Feista68a8042020-04-15 15:46:44 -0700230
231 OpenSSLGenerator gen;
232
Ed Tanous0dfeda62019-10-24 11:21:38 -0700233 for (char& sessionChar : sessionToken)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700234 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700235 sessionChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700236 if (gen.error())
237 {
238 return nullptr;
239 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100240 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700241 // Only need csrf tokens for cookie based auth, token doesn't matter
242 std::string csrfToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700243 csrfToken.resize(sessionTokenSize, '0');
Ed Tanous0dfeda62019-10-24 11:21:38 -0700244 for (char& csrfChar : csrfToken)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700245 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700246 csrfChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700247 if (gen.error())
248 {
249 return nullptr;
250 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700251 }
252
253 std::string uniqueId;
254 uniqueId.resize(10, '0');
Ed Tanous0dfeda62019-10-24 11:21:38 -0700255 for (char& uidChar : uniqueId)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700256 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700257 uidChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700258 if (gen.error())
259 {
260 return nullptr;
261 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700262 }
Sunitha Harish92f68222020-05-28 05:09:09 -0500263 auto session = std::make_shared<UserSession>(
264 UserSession{uniqueId, sessionToken, std::string(username),
265 csrfToken, std::string(clientId), std::string(clientIp),
266 std::chrono::steady_clock::now(), persistence, false,
267 isConfigureSelfOnly});
Ed Tanous1abe55e2018-09-05 08:30:59 -0700268 auto it = authTokens.emplace(std::make_pair(sessionToken, session));
269 // Only need to write to disk if session isn't about to be destroyed.
270 needWrite = persistence == PersistenceType::TIMEOUT;
271 return it.first->second;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100272 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700273
274 std::shared_ptr<UserSession>
Ed Tanous39e77502019-03-04 17:35:53 -0800275 loginSessionByToken(const std::string_view token)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700276 {
277 applySessionTimeouts();
Ed Tanous51dae672018-09-05 16:07:32 -0700278 if (token.size() != sessionTokenSize)
279 {
280 return nullptr;
281 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700282 auto sessionIt = authTokens.find(std::string(token));
283 if (sessionIt == authTokens.end())
284 {
285 return nullptr;
286 }
287 std::shared_ptr<UserSession> userSession = sessionIt->second;
288 userSession->lastUpdated = std::chrono::steady_clock::now();
289 return userSession;
290 }
291
Ed Tanous39e77502019-03-04 17:35:53 -0800292 std::shared_ptr<UserSession> getSessionByUid(const std::string_view uid)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700293 {
294 applySessionTimeouts();
295 // TODO(Ed) this is inefficient
296 auto sessionIt = authTokens.begin();
297 while (sessionIt != authTokens.end())
298 {
299 if (sessionIt->second->uniqueId == uid)
300 {
301 return sessionIt->second;
302 }
303 sessionIt++;
304 }
305 return nullptr;
306 }
307
308 void removeSession(std::shared_ptr<UserSession> session)
309 {
Ratan Gupta07386c62019-12-14 14:06:09 +0530310#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
311 crow::ibm_mc_lock::Lock::getInstance().releaseLock(session->uniqueId);
312#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700313 authTokens.erase(session->sessionToken);
314 needWrite = true;
315 }
316
317 std::vector<const std::string*> getUniqueIds(
318 bool getAll = true,
319 const PersistenceType& type = PersistenceType::SINGLE_REQUEST)
320 {
321 applySessionTimeouts();
322
323 std::vector<const std::string*> ret;
324 ret.reserve(authTokens.size());
325 for (auto& session : authTokens)
326 {
327 if (getAll || type == session.second->persistence)
328 {
329 ret.push_back(&session.second->uniqueId);
330 }
331 }
332 return ret;
333 }
334
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100335 void updateAuthMethodsConfig(const AuthConfigMethods& config)
336 {
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100337 bool isTLSchanged = (authMethodsConfig.tls != config.tls);
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100338 authMethodsConfig = config;
339 needWrite = true;
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100340 if (isTLSchanged)
341 {
342 // recreate socket connections with new settings
343 std::raise(SIGHUP);
344 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100345 }
346
347 AuthConfigMethods& getAuthMethodsConfig()
348 {
349 return authMethodsConfig;
350 }
351
Ed Tanous1abe55e2018-09-05 08:30:59 -0700352 bool needsWrite()
353 {
354 return needWrite;
355 }
Ed Tanous271584a2019-07-09 16:24:22 -0700356 int64_t getTimeoutInSeconds() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700357 {
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530358 return std::chrono::seconds(timeoutInSeconds).count();
359 }
360
361 void updateSessionTimeout(std::chrono::seconds newTimeoutInSeconds)
362 {
363 timeoutInSeconds = newTimeoutInSeconds;
364 needWrite = true;
Ed Tanous23a21a12020-07-25 04:45:05 +0000365 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700366
Ed Tanous1abe55e2018-09-05 08:30:59 -0700367 static SessionStore& getInstance()
368 {
369 static SessionStore sessionStore;
370 return sessionStore;
371 }
372
373 SessionStore(const SessionStore&) = delete;
374 SessionStore& operator=(const SessionStore&) = delete;
375
Ed Tanous52cc1122020-07-18 13:51:21 -0700376 std::unordered_map<std::string, std::shared_ptr<UserSession>,
377 std::hash<std::string>,
378 crow::utility::ConstantTimeCompare>
379 authTokens;
380
381 std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate;
382 bool needWrite{false};
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530383 std::chrono::seconds timeoutInSeconds;
Ed Tanous52cc1122020-07-18 13:51:21 -0700384 AuthConfigMethods authMethodsConfig;
385
Ed Tanous1abe55e2018-09-05 08:30:59 -0700386 private:
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530387 SessionStore() : timeoutInSeconds(3600)
Gunnar Mills1214b7e2020-06-04 10:11:30 -0500388 {}
Ed Tanous1abe55e2018-09-05 08:30:59 -0700389
390 void applySessionTimeouts()
391 {
392 auto timeNow = std::chrono::steady_clock::now();
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530393 if (timeNow - lastTimeoutUpdate > std::chrono::seconds(1))
Ed Tanous1abe55e2018-09-05 08:30:59 -0700394 {
395 lastTimeoutUpdate = timeNow;
396 auto authTokensIt = authTokens.begin();
397 while (authTokensIt != authTokens.end())
398 {
399 if (timeNow - authTokensIt->second->lastUpdated >=
Manojkiran Edaf2a4a602020-08-27 16:04:26 +0530400 timeoutInSeconds)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700401 {
Ratan Gupta07386c62019-12-14 14:06:09 +0530402#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
403 crow::ibm_mc_lock::Lock::getInstance().releaseLock(
404 authTokensIt->second->uniqueId);
405#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700406 authTokensIt = authTokens.erase(authTokensIt);
Ratan Gupta07386c62019-12-14 14:06:09 +0530407
Ed Tanous1abe55e2018-09-05 08:30:59 -0700408 needWrite = true;
409 }
410 else
411 {
412 authTokensIt++;
413 }
414 }
415 }
416 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100417};
418
Ed Tanous1abe55e2018-09-05 08:30:59 -0700419} // namespace persistent_data
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200420
421// to_json(...) definition for objects of UserSession type
Ed Tanous1abe55e2018-09-05 08:30:59 -0700422namespace nlohmann
423{
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200424template <>
Ed Tanous52cc1122020-07-18 13:51:21 -0700425struct adl_serializer<std::shared_ptr<persistent_data::UserSession>>
Ed Tanous1abe55e2018-09-05 08:30:59 -0700426{
Ed Tanous52cc1122020-07-18 13:51:21 -0700427 static void to_json(nlohmann::json& j,
428 const std::shared_ptr<persistent_data::UserSession>& p)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700429 {
Ed Tanous52cc1122020-07-18 13:51:21 -0700430 if (p->persistence != persistent_data::PersistenceType::SINGLE_REQUEST)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700431 {
Sunitha Harish08bdcc72020-05-12 05:17:57 -0500432#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
Sunitha Harish92f68222020-05-28 05:09:09 -0500433 j = nlohmann::json{
434 {"unique_id", p->uniqueId}, {"session_token", p->sessionToken},
435 {"username", p->username}, {"csrf_token", p->csrfToken},
436 {"client_id", p->clientId}, { "client_ip", p->clientIp }};
Sunitha Harish08bdcc72020-05-12 05:17:57 -0500437#else
Ed Tanous1abe55e2018-09-05 08:30:59 -0700438 j = nlohmann::json{{"unique_id", p->uniqueId},
439 {"session_token", p->sessionToken},
440 {"username", p->username},
Sunitha Harish92f68222020-05-28 05:09:09 -0500441 {"csrf_token", p->csrfToken},
442 {"client_ip", p->clientIp}};
Sunitha Harish08bdcc72020-05-12 05:17:57 -0500443#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700444 }
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200445 }
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200446};
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100447
Gunnar Mills1214b7e2020-06-04 10:11:30 -0500448template <>
Ed Tanous52cc1122020-07-18 13:51:21 -0700449struct adl_serializer<persistent_data::AuthConfigMethods>
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100450{
451 static void to_json(nlohmann::json& j,
Ed Tanous52cc1122020-07-18 13:51:21 -0700452 const persistent_data::AuthConfigMethods& c)
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100453 {
454 j = nlohmann::json{{"XToken", c.xtoken},
455 {"Cookie", c.cookie},
456 {"SessionToken", c.sessionToken},
Zbigniew Kurzynski501f1e52019-10-02 11:22:11 +0200457 {"BasicAuth", c.basic},
458 {"TLS", c.tls}};
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100459 }
460};
Ed Tanous1abe55e2018-09-05 08:30:59 -0700461} // namespace nlohmann