blob: e745845d1bc4d825f8be04a54d52b83e20966d73 [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
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100171class Middleware;
172
James Feista68a8042020-04-15 15:46:44 -0700173struct OpenSSLGenerator
174{
175
176 uint8_t operator()(void)
177 {
178 uint8_t index = 0;
179 int rc = RAND_bytes(&index, sizeof(index));
180 if (rc != opensslSuccess)
181 {
182 std::cerr << "Cannot get random number\n";
183 err = true;
184 }
185
186 return index;
Ed Tanous23a21a12020-07-25 04:45:05 +0000187 }
James Feista68a8042020-04-15 15:46:44 -0700188
189 uint8_t max()
190 {
191 return std::numeric_limits<uint8_t>::max();
192 }
193 uint8_t min()
194 {
195 return std::numeric_limits<uint8_t>::min();
196 }
197
198 bool error()
199 {
200 return err;
201 }
202
203 // all generators require this variable
204 using result_type = uint8_t;
205
206 private:
207 // RAND_bytes() returns 1 on success, 0 otherwise. -1 if bad function
208 static constexpr int opensslSuccess = 1;
209 bool err = false;
210};
211
Ed Tanous1abe55e2018-09-05 08:30:59 -0700212class SessionStore
213{
214 public:
215 std::shared_ptr<UserSession> generateUserSession(
Ed Tanous39e77502019-03-04 17:35:53 -0800216 const std::string_view username,
Joseph Reynolds3bf4e632020-02-06 14:44:32 -0600217 PersistenceType persistence = PersistenceType::TIMEOUT,
Sunitha Harish92f68222020-05-28 05:09:09 -0500218 bool isConfigureSelfOnly = false, const std::string_view clientId = "",
219 const std::string_view clientIp = "")
Ed Tanous1abe55e2018-09-05 08:30:59 -0700220 {
221 // TODO(ed) find a secure way to not generate session identifiers if
222 // persistence is set to SINGLE_REQUEST
223 static constexpr std::array<char, 62> alphanum = {
Joseph Reynolds368b1d42019-08-15 15:29:06 -0500224 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
225 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
226 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
Ed Tanous1abe55e2018-09-05 08:30:59 -0700227 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
228 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100229
Ed Tanous1abe55e2018-09-05 08:30:59 -0700230 std::string sessionToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700231 sessionToken.resize(sessionTokenSize, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700232 std::uniform_int_distribution<size_t> dist(0, alphanum.size() - 1);
James Feista68a8042020-04-15 15:46:44 -0700233
234 OpenSSLGenerator gen;
235
Ed Tanous0dfeda62019-10-24 11:21:38 -0700236 for (char& sessionChar : sessionToken)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700237 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700238 sessionChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700239 if (gen.error())
240 {
241 return nullptr;
242 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100243 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700244 // Only need csrf tokens for cookie based auth, token doesn't matter
245 std::string csrfToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700246 csrfToken.resize(sessionTokenSize, '0');
Ed Tanous0dfeda62019-10-24 11:21:38 -0700247 for (char& csrfChar : csrfToken)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700248 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700249 csrfChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700250 if (gen.error())
251 {
252 return nullptr;
253 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700254 }
255
256 std::string uniqueId;
257 uniqueId.resize(10, '0');
Ed Tanous0dfeda62019-10-24 11:21:38 -0700258 for (char& uidChar : uniqueId)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700259 {
Ed Tanous0dfeda62019-10-24 11:21:38 -0700260 uidChar = alphanum[dist(gen)];
James Feista68a8042020-04-15 15:46:44 -0700261 if (gen.error())
262 {
263 return nullptr;
264 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700265 }
Sunitha Harish92f68222020-05-28 05:09:09 -0500266 auto session = std::make_shared<UserSession>(
267 UserSession{uniqueId, sessionToken, std::string(username),
268 csrfToken, std::string(clientId), std::string(clientIp),
269 std::chrono::steady_clock::now(), persistence, false,
270 isConfigureSelfOnly});
Ed Tanous1abe55e2018-09-05 08:30:59 -0700271 auto it = authTokens.emplace(std::make_pair(sessionToken, session));
272 // Only need to write to disk if session isn't about to be destroyed.
273 needWrite = persistence == PersistenceType::TIMEOUT;
274 return it.first->second;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100275 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700276
277 std::shared_ptr<UserSession>
Ed Tanous39e77502019-03-04 17:35:53 -0800278 loginSessionByToken(const std::string_view token)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700279 {
280 applySessionTimeouts();
Ed Tanous51dae672018-09-05 16:07:32 -0700281 if (token.size() != sessionTokenSize)
282 {
283 return nullptr;
284 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700285 auto sessionIt = authTokens.find(std::string(token));
286 if (sessionIt == authTokens.end())
287 {
288 return nullptr;
289 }
290 std::shared_ptr<UserSession> userSession = sessionIt->second;
291 userSession->lastUpdated = std::chrono::steady_clock::now();
292 return userSession;
293 }
294
Ed Tanous39e77502019-03-04 17:35:53 -0800295 std::shared_ptr<UserSession> getSessionByUid(const std::string_view uid)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700296 {
297 applySessionTimeouts();
298 // TODO(Ed) this is inefficient
299 auto sessionIt = authTokens.begin();
300 while (sessionIt != authTokens.end())
301 {
302 if (sessionIt->second->uniqueId == uid)
303 {
304 return sessionIt->second;
305 }
306 sessionIt++;
307 }
308 return nullptr;
309 }
310
311 void removeSession(std::shared_ptr<UserSession> session)
312 {
Ratan Gupta07386c62019-12-14 14:06:09 +0530313#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
314 crow::ibm_mc_lock::Lock::getInstance().releaseLock(session->uniqueId);
315#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700316 authTokens.erase(session->sessionToken);
317 needWrite = true;
318 }
319
320 std::vector<const std::string*> getUniqueIds(
321 bool getAll = true,
322 const PersistenceType& type = PersistenceType::SINGLE_REQUEST)
323 {
324 applySessionTimeouts();
325
326 std::vector<const std::string*> ret;
327 ret.reserve(authTokens.size());
328 for (auto& session : authTokens)
329 {
330 if (getAll || type == session.second->persistence)
331 {
332 ret.push_back(&session.second->uniqueId);
333 }
334 }
335 return ret;
336 }
337
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100338 void updateAuthMethodsConfig(const AuthConfigMethods& config)
339 {
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100340 bool isTLSchanged = (authMethodsConfig.tls != config.tls);
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100341 authMethodsConfig = config;
342 needWrite = true;
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100343 if (isTLSchanged)
344 {
345 // recreate socket connections with new settings
346 std::raise(SIGHUP);
347 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100348 }
349
350 AuthConfigMethods& getAuthMethodsConfig()
351 {
352 return authMethodsConfig;
353 }
354
Ed Tanous1abe55e2018-09-05 08:30:59 -0700355 bool needsWrite()
356 {
357 return needWrite;
358 }
Ed Tanous271584a2019-07-09 16:24:22 -0700359 int64_t getTimeoutInSeconds() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700360 {
361 return std::chrono::seconds(timeoutInMinutes).count();
Ed Tanous23a21a12020-07-25 04:45:05 +0000362 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700363
Ed Tanous1abe55e2018-09-05 08:30:59 -0700364 static SessionStore& getInstance()
365 {
366 static SessionStore sessionStore;
367 return sessionStore;
368 }
369
370 SessionStore(const SessionStore&) = delete;
371 SessionStore& operator=(const SessionStore&) = delete;
372
Ed Tanous52cc1122020-07-18 13:51:21 -0700373 std::unordered_map<std::string, std::shared_ptr<UserSession>,
374 std::hash<std::string>,
375 crow::utility::ConstantTimeCompare>
376 authTokens;
377
378 std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate;
379 bool needWrite{false};
380 std::chrono::minutes timeoutInMinutes;
381 AuthConfigMethods authMethodsConfig;
382
Ed Tanous1abe55e2018-09-05 08:30:59 -0700383 private:
384 SessionStore() : timeoutInMinutes(60)
Gunnar Mills1214b7e2020-06-04 10:11:30 -0500385 {}
Ed Tanous1abe55e2018-09-05 08:30:59 -0700386
387 void applySessionTimeouts()
388 {
389 auto timeNow = std::chrono::steady_clock::now();
390 if (timeNow - lastTimeoutUpdate > std::chrono::minutes(1))
391 {
392 lastTimeoutUpdate = timeNow;
393 auto authTokensIt = authTokens.begin();
394 while (authTokensIt != authTokens.end())
395 {
396 if (timeNow - authTokensIt->second->lastUpdated >=
397 timeoutInMinutes)
398 {
Ratan Gupta07386c62019-12-14 14:06:09 +0530399#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
400 crow::ibm_mc_lock::Lock::getInstance().releaseLock(
401 authTokensIt->second->uniqueId);
402#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700403 authTokensIt = authTokens.erase(authTokensIt);
Ratan Gupta07386c62019-12-14 14:06:09 +0530404
Ed Tanous1abe55e2018-09-05 08:30:59 -0700405 needWrite = true;
406 }
407 else
408 {
409 authTokensIt++;
410 }
411 }
412 }
413 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100414};
415
Ed Tanous1abe55e2018-09-05 08:30:59 -0700416} // namespace persistent_data
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200417
418// to_json(...) definition for objects of UserSession type
Ed Tanous1abe55e2018-09-05 08:30:59 -0700419namespace nlohmann
420{
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200421template <>
Ed Tanous52cc1122020-07-18 13:51:21 -0700422struct adl_serializer<std::shared_ptr<persistent_data::UserSession>>
Ed Tanous1abe55e2018-09-05 08:30:59 -0700423{
Ed Tanous52cc1122020-07-18 13:51:21 -0700424 static void to_json(nlohmann::json& j,
425 const std::shared_ptr<persistent_data::UserSession>& p)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700426 {
Ed Tanous52cc1122020-07-18 13:51:21 -0700427 if (p->persistence != persistent_data::PersistenceType::SINGLE_REQUEST)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700428 {
Sunitha Harish08bdcc72020-05-12 05:17:57 -0500429#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
Sunitha Harish92f68222020-05-28 05:09:09 -0500430 j = nlohmann::json{
431 {"unique_id", p->uniqueId}, {"session_token", p->sessionToken},
432 {"username", p->username}, {"csrf_token", p->csrfToken},
433 {"client_id", p->clientId}, { "client_ip", p->clientIp }};
Sunitha Harish08bdcc72020-05-12 05:17:57 -0500434#else
Ed Tanous1abe55e2018-09-05 08:30:59 -0700435 j = nlohmann::json{{"unique_id", p->uniqueId},
436 {"session_token", p->sessionToken},
437 {"username", p->username},
Sunitha Harish92f68222020-05-28 05:09:09 -0500438 {"csrf_token", p->csrfToken},
439 {"client_ip", p->clientIp}};
Sunitha Harish08bdcc72020-05-12 05:17:57 -0500440#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700441 }
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200442 }
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200443};
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100444
Gunnar Mills1214b7e2020-06-04 10:11:30 -0500445template <>
Ed Tanous52cc1122020-07-18 13:51:21 -0700446struct adl_serializer<persistent_data::AuthConfigMethods>
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100447{
448 static void to_json(nlohmann::json& j,
Ed Tanous52cc1122020-07-18 13:51:21 -0700449 const persistent_data::AuthConfigMethods& c)
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100450 {
451 j = nlohmann::json{{"XToken", c.xtoken},
452 {"Cookie", c.cookie},
453 {"SessionToken", c.sessionToken},
Zbigniew Kurzynski501f1e52019-10-02 11:22:11 +0200454 {"BasicAuth", c.basic},
455 {"TLS", c.tls}};
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100456 }
457};
Ed Tanous1abe55e2018-09-05 08:30:59 -0700458} // namespace nlohmann