blob: e4558094a84462d010fb2510258dbead049cc110 [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 crow
24{
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010025
Ed Tanous1abe55e2018-09-05 08:30:59 -070026namespace persistent_data
27{
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010028
Ed Tanous51dae672018-09-05 16:07:32 -070029// entropy: 20 characters, 62 possibilities. log2(62^20) = 119 bits of
30// entropy. OWASP recommends at least 64
31// https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#session-id-entropy
32constexpr std::size_t sessionTokenSize = 20;
33
Ed Tanous1abe55e2018-09-05 08:30:59 -070034enum class PersistenceType
35{
36 TIMEOUT, // User session times out after a predetermined amount of time
37 SINGLE_REQUEST // User times out once this request is completed.
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010038};
39
Ed Tanous1abe55e2018-09-05 08:30:59 -070040struct UserSession
41{
42 std::string uniqueId;
43 std::string sessionToken;
44 std::string username;
45 std::string csrfToken;
Sunitha Harish08bdcc72020-05-12 05:17:57 -050046 std::string clientId;
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 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700104 else
105 {
106 BMCWEB_LOG_ERROR
107 << "Got unexpected property reading persistent file: "
108 << element.key();
109 return nullptr;
110 }
111 }
112
113 // For now, sessions that were persisted through a reboot get their idle
114 // timer reset. This could probably be overcome with a better
115 // understanding of wall clock time and steady timer time, possibly
116 // persisting values with wall clock time instead of steady timer, but
117 // the tradeoffs of all the corner cases involved are non-trivial, so
118 // this is done temporarily
119 userSession->lastUpdated = std::chrono::steady_clock::now();
120 userSession->persistence = PersistenceType::TIMEOUT;
121
122 return userSession;
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +0100123 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100124};
125
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100126struct AuthConfigMethods
127{
128 bool xtoken = true;
129 bool cookie = true;
130 bool sessionToken = true;
131 bool basic = true;
Zbigniew Kurzynskicac94c52019-11-07 12:55:04 +0100132 bool tls = false;
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100133
134 void fromJson(const nlohmann::json& j)
135 {
136 for (const auto& element : j.items())
137 {
138 const bool* value = element.value().get_ptr<const bool*>();
139 if (value == nullptr)
140 {
141 continue;
142 }
143
144 if (element.key() == "XToken")
145 {
146 xtoken = *value;
147 }
148 else if (element.key() == "Cookie")
149 {
150 cookie = *value;
151 }
152 else if (element.key() == "SessionToken")
153 {
154 sessionToken = *value;
155 }
156 else if (element.key() == "BasicAuth")
157 {
158 basic = *value;
159 }
Zbigniew Kurzynski501f1e52019-10-02 11:22:11 +0200160 else if (element.key() == "TLS")
161 {
162 tls = *value;
163 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100164 }
165 }
166};
167
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100168class Middleware;
169
James Feista68a8042020-04-15 15:46:44 -0700170struct OpenSSLGenerator
171{
172
173 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;
184 };
185
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 Harish08bdcc72020-05-12 05:17:57 -0500215 bool isConfigureSelfOnly = false, const std::string_view clientId = "")
Ed Tanous1abe55e2018-09-05 08:30:59 -0700216 {
217 // TODO(ed) find a secure way to not generate session identifiers if
218 // persistence is set to SINGLE_REQUEST
219 static constexpr std::array<char, 62> alphanum = {
Joseph Reynolds368b1d42019-08-15 15:29:06 -0500220 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
221 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
222 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
Ed Tanous1abe55e2018-09-05 08:30:59 -0700223 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
224 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100225
Ed Tanous1abe55e2018-09-05 08:30:59 -0700226 std::string sessionToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700227 sessionToken.resize(sessionTokenSize, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700228 std::uniform_int_distribution<size_t> dist(0, alphanum.size() - 1);
James Feista68a8042020-04-15 15:46:44 -0700229
230 OpenSSLGenerator gen;
231
Ed Tanous271584a2019-07-09 16:24:22 -0700232 for (size_t i = 0; i < sessionToken.size(); ++i)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700233 {
James Feista68a8042020-04-15 15:46:44 -0700234 sessionToken[i] = alphanum[dist(gen)];
235 if (gen.error())
236 {
237 return nullptr;
238 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100239 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700240 // Only need csrf tokens for cookie based auth, token doesn't matter
241 std::string csrfToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700242 csrfToken.resize(sessionTokenSize, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700243 for (size_t i = 0; i < csrfToken.size(); ++i)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700244 {
James Feista68a8042020-04-15 15:46:44 -0700245 csrfToken[i] = alphanum[dist(gen)];
246 if (gen.error())
247 {
248 return nullptr;
249 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700250 }
251
252 std::string uniqueId;
253 uniqueId.resize(10, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700254 for (size_t i = 0; i < uniqueId.size(); ++i)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700255 {
James Feista68a8042020-04-15 15:46:44 -0700256 uniqueId[i] = alphanum[dist(gen)];
257 if (gen.error())
258 {
259 return nullptr;
260 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700261 }
Sunitha Harish08bdcc72020-05-12 05:17:57 -0500262 auto session = std::make_shared<UserSession>(UserSession{
263 uniqueId, sessionToken, std::string(username), csrfToken,
264 std::string(clientId), std::chrono::steady_clock::now(),
265 persistence, false, isConfigureSelfOnly});
Ed Tanous1abe55e2018-09-05 08:30:59 -0700266 auto it = authTokens.emplace(std::make_pair(sessionToken, session));
267 // Only need to write to disk if session isn't about to be destroyed.
268 needWrite = persistence == PersistenceType::TIMEOUT;
269 return it.first->second;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100270 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700271
272 std::shared_ptr<UserSession>
Ed Tanous39e77502019-03-04 17:35:53 -0800273 loginSessionByToken(const std::string_view token)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700274 {
275 applySessionTimeouts();
Ed Tanous51dae672018-09-05 16:07:32 -0700276 if (token.size() != sessionTokenSize)
277 {
278 return nullptr;
279 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700280 auto sessionIt = authTokens.find(std::string(token));
281 if (sessionIt == authTokens.end())
282 {
283 return nullptr;
284 }
285 std::shared_ptr<UserSession> userSession = sessionIt->second;
286 userSession->lastUpdated = std::chrono::steady_clock::now();
287 return userSession;
288 }
289
Ed Tanous39e77502019-03-04 17:35:53 -0800290 std::shared_ptr<UserSession> getSessionByUid(const std::string_view uid)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700291 {
292 applySessionTimeouts();
293 // TODO(Ed) this is inefficient
294 auto sessionIt = authTokens.begin();
295 while (sessionIt != authTokens.end())
296 {
297 if (sessionIt->second->uniqueId == uid)
298 {
299 return sessionIt->second;
300 }
301 sessionIt++;
302 }
303 return nullptr;
304 }
305
306 void removeSession(std::shared_ptr<UserSession> session)
307 {
Ratan Gupta07386c62019-12-14 14:06:09 +0530308#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
309 crow::ibm_mc_lock::Lock::getInstance().releaseLock(session->uniqueId);
310#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700311 authTokens.erase(session->sessionToken);
312 needWrite = true;
313 }
314
315 std::vector<const std::string*> getUniqueIds(
316 bool getAll = true,
317 const PersistenceType& type = PersistenceType::SINGLE_REQUEST)
318 {
319 applySessionTimeouts();
320
321 std::vector<const std::string*> ret;
322 ret.reserve(authTokens.size());
323 for (auto& session : authTokens)
324 {
325 if (getAll || type == session.second->persistence)
326 {
327 ret.push_back(&session.second->uniqueId);
328 }
329 }
330 return ret;
331 }
332
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100333 void updateAuthMethodsConfig(const AuthConfigMethods& config)
334 {
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100335 bool isTLSchanged = (authMethodsConfig.tls != config.tls);
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100336 authMethodsConfig = config;
337 needWrite = true;
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100338 if (isTLSchanged)
339 {
340 // recreate socket connections with new settings
341 std::raise(SIGHUP);
342 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100343 }
344
345 AuthConfigMethods& getAuthMethodsConfig()
346 {
347 return authMethodsConfig;
348 }
349
Ed Tanous1abe55e2018-09-05 08:30:59 -0700350 bool needsWrite()
351 {
352 return needWrite;
353 }
Ed Tanous271584a2019-07-09 16:24:22 -0700354 int64_t getTimeoutInSeconds() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700355 {
356 return std::chrono::seconds(timeoutInMinutes).count();
357 };
358
359 // Persistent data middleware needs to be able to serialize our authTokens
360 // structure, which is private
361 friend Middleware;
362
363 static SessionStore& getInstance()
364 {
365 static SessionStore sessionStore;
366 return sessionStore;
367 }
368
369 SessionStore(const SessionStore&) = delete;
370 SessionStore& operator=(const SessionStore&) = delete;
371
372 private:
373 SessionStore() : timeoutInMinutes(60)
Gunnar Mills1214b7e2020-06-04 10:11:30 -0500374 {}
Ed Tanous1abe55e2018-09-05 08:30:59 -0700375
376 void applySessionTimeouts()
377 {
378 auto timeNow = std::chrono::steady_clock::now();
379 if (timeNow - lastTimeoutUpdate > std::chrono::minutes(1))
380 {
381 lastTimeoutUpdate = timeNow;
382 auto authTokensIt = authTokens.begin();
383 while (authTokensIt != authTokens.end())
384 {
385 if (timeNow - authTokensIt->second->lastUpdated >=
386 timeoutInMinutes)
387 {
Ratan Gupta07386c62019-12-14 14:06:09 +0530388#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
389 crow::ibm_mc_lock::Lock::getInstance().releaseLock(
390 authTokensIt->second->uniqueId);
391#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700392 authTokensIt = authTokens.erase(authTokensIt);
Ratan Gupta07386c62019-12-14 14:06:09 +0530393
Ed Tanous1abe55e2018-09-05 08:30:59 -0700394 needWrite = true;
395 }
396 else
397 {
398 authTokensIt++;
399 }
400 }
401 }
402 }
Ratan Gupta12c04ef2019-04-03 10:08:11 +0530403
Ed Tanous1abe55e2018-09-05 08:30:59 -0700404 std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate;
Ed Tanous51dae672018-09-05 16:07:32 -0700405 std::unordered_map<std::string, std::shared_ptr<UserSession>,
406 std::hash<std::string>,
407 crow::utility::ConstantTimeCompare>
Ed Tanous1abe55e2018-09-05 08:30:59 -0700408 authTokens;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700409 bool needWrite{false};
410 std::chrono::minutes timeoutInMinutes;
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100411 AuthConfigMethods authMethodsConfig;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100412};
413
Ed Tanous1abe55e2018-09-05 08:30:59 -0700414} // namespace persistent_data
415} // namespace crow
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200416
417// to_json(...) definition for objects of UserSession type
Ed Tanous1abe55e2018-09-05 08:30:59 -0700418namespace nlohmann
419{
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200420template <>
Ed Tanous1abe55e2018-09-05 08:30:59 -0700421struct adl_serializer<std::shared_ptr<crow::persistent_data::UserSession>>
422{
423 static void
424 to_json(nlohmann::json& j,
425 const std::shared_ptr<crow::persistent_data::UserSession>& p)
426 {
427 if (p->persistence !=
428 crow::persistent_data::PersistenceType::SINGLE_REQUEST)
429 {
Sunitha Harish08bdcc72020-05-12 05:17:57 -0500430#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
431 j = nlohmann::json{{"unique_id", p->uniqueId},
432 {"session_token", p->sessionToken},
433 {"username", p->username},
434 {"csrf_token", p->csrfToken},
435 { "client_id",
436 p->clientId }};
437#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},
441 {"csrf_token", p->csrfToken}};
Sunitha Harish08bdcc72020-05-12 05:17:57 -0500442#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700443 }
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200444 }
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200445};
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100446
Gunnar Mills1214b7e2020-06-04 10:11:30 -0500447template <>
448struct adl_serializer<crow::persistent_data::AuthConfigMethods>
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100449{
450 static void to_json(nlohmann::json& j,
451 const crow::persistent_data::AuthConfigMethods& c)
452 {
453 j = nlohmann::json{{"XToken", c.xtoken},
454 {"Cookie", c.cookie},
455 {"SessionToken", c.sessionToken},
Zbigniew Kurzynski501f1e52019-10-02 11:22:11 +0200456 {"BasicAuth", c.basic},
457 {"TLS", c.tls}};
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100458 }
459};
Ed Tanous1abe55e2018-09-05 08:30:59 -0700460} // namespace nlohmann