blob: 217ce95e737766237ad0f2abf6b1646e2195706d [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;
Sunitha Harish92f68222020-05-28 05:09:09 -050047 std::string clientIp;
Ed Tanous1abe55e2018-09-05 08:30:59 -070048 std::chrono::time_point<std::chrono::steady_clock> lastUpdated;
49 PersistenceType persistence;
James Feistf8aa3d22020-04-08 18:32:33 -070050 bool cookieAuth = false;
Joseph Reynolds3bf4e632020-02-06 14:44:32 -060051 bool isConfigureSelfOnly = false;
52
53 // There are two sources of truth for isConfigureSelfOnly:
54 // 1. When pamAuthenticateUser() returns PAM_NEW_AUTHTOK_REQD.
55 // 2. D-Bus User.Manager.GetUserInfo property UserPasswordExpired.
56 // These should be in sync, but the underlying condition can change at any
57 // time. For example, a password can expire or be changed outside of
58 // bmcweb. The value stored here is updated at the start of each
59 // operation and used as the truth within bmcweb.
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +010060
Ed Tanous1abe55e2018-09-05 08:30:59 -070061 /**
62 * @brief Fills object with data from UserSession's JSON representation
63 *
64 * This replaces nlohmann's from_json to ensure no-throw approach
65 *
66 * @param[in] j JSON object from which data should be loaded
67 *
68 * @return a shared pointer if data has been loaded properly, nullptr
69 * otherwise
70 */
71 static std::shared_ptr<UserSession> fromJson(const nlohmann::json& j)
72 {
73 std::shared_ptr<UserSession> userSession =
74 std::make_shared<UserSession>();
75 for (const auto& element : j.items())
76 {
77 const std::string* thisValue =
78 element.value().get_ptr<const std::string*>();
79 if (thisValue == nullptr)
80 {
81 BMCWEB_LOG_ERROR << "Error reading persistent store. Property "
82 << element.key() << " was not of type string";
83 return nullptr;
84 }
85 if (element.key() == "unique_id")
86 {
87 userSession->uniqueId = *thisValue;
88 }
89 else if (element.key() == "session_token")
90 {
91 userSession->sessionToken = *thisValue;
92 }
93 else if (element.key() == "csrf_token")
94 {
95 userSession->csrfToken = *thisValue;
96 }
97 else if (element.key() == "username")
98 {
99 userSession->username = *thisValue;
100 }
Sunitha Harish08bdcc72020-05-12 05:17:57 -0500101 else if (element.key() == "client_id")
102 {
103 userSession->clientId = *thisValue;
104 }
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();
115 return nullptr;
116 }
117 }
118
119 // For now, sessions that were persisted through a reboot get their idle
120 // timer reset. This could probably be overcome with a better
121 // understanding of wall clock time and steady timer time, possibly
122 // persisting values with wall clock time instead of steady timer, but
123 // the tradeoffs of all the corner cases involved are non-trivial, so
124 // this is done temporarily
125 userSession->lastUpdated = std::chrono::steady_clock::now();
126 userSession->persistence = PersistenceType::TIMEOUT;
127
128 return userSession;
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +0100129 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100130};
131
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100132struct AuthConfigMethods
133{
134 bool xtoken = true;
135 bool cookie = true;
136 bool sessionToken = true;
137 bool basic = true;
Zbigniew Kurzynskicac94c52019-11-07 12:55:04 +0100138 bool tls = false;
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100139
140 void fromJson(const nlohmann::json& j)
141 {
142 for (const auto& element : j.items())
143 {
144 const bool* value = element.value().get_ptr<const bool*>();
145 if (value == nullptr)
146 {
147 continue;
148 }
149
150 if (element.key() == "XToken")
151 {
152 xtoken = *value;
153 }
154 else if (element.key() == "Cookie")
155 {
156 cookie = *value;
157 }
158 else if (element.key() == "SessionToken")
159 {
160 sessionToken = *value;
161 }
162 else if (element.key() == "BasicAuth")
163 {
164 basic = *value;
165 }
Zbigniew Kurzynski501f1e52019-10-02 11:22:11 +0200166 else if (element.key() == "TLS")
167 {
168 tls = *value;
169 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100170 }
171 }
172};
173
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100174class Middleware;
175
James Feista68a8042020-04-15 15:46:44 -0700176struct OpenSSLGenerator
177{
178
179 uint8_t operator()(void)
180 {
181 uint8_t index = 0;
182 int rc = RAND_bytes(&index, sizeof(index));
183 if (rc != opensslSuccess)
184 {
185 std::cerr << "Cannot get random number\n";
186 err = true;
187 }
188
189 return index;
190 };
191
192 uint8_t max()
193 {
194 return std::numeric_limits<uint8_t>::max();
195 }
196 uint8_t min()
197 {
198 return std::numeric_limits<uint8_t>::min();
199 }
200
201 bool error()
202 {
203 return err;
204 }
205
206 // all generators require this variable
207 using result_type = uint8_t;
208
209 private:
210 // RAND_bytes() returns 1 on success, 0 otherwise. -1 if bad function
211 static constexpr int opensslSuccess = 1;
212 bool err = false;
213};
214
Ed Tanous1abe55e2018-09-05 08:30:59 -0700215class SessionStore
216{
217 public:
218 std::shared_ptr<UserSession> generateUserSession(
Ed Tanous39e77502019-03-04 17:35:53 -0800219 const std::string_view username,
Joseph Reynolds3bf4e632020-02-06 14:44:32 -0600220 PersistenceType persistence = PersistenceType::TIMEOUT,
Sunitha Harish92f68222020-05-28 05:09:09 -0500221 bool isConfigureSelfOnly = false, const std::string_view clientId = "",
222 const std::string_view clientIp = "")
Ed Tanous1abe55e2018-09-05 08:30:59 -0700223 {
224 // TODO(ed) find a secure way to not generate session identifiers if
225 // persistence is set to SINGLE_REQUEST
226 static constexpr std::array<char, 62> alphanum = {
Joseph Reynolds368b1d42019-08-15 15:29:06 -0500227 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
228 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
229 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
Ed Tanous1abe55e2018-09-05 08:30:59 -0700230 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
231 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100232
Ed Tanous1abe55e2018-09-05 08:30:59 -0700233 std::string sessionToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700234 sessionToken.resize(sessionTokenSize, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700235 std::uniform_int_distribution<size_t> dist(0, alphanum.size() - 1);
James Feista68a8042020-04-15 15:46:44 -0700236
237 OpenSSLGenerator gen;
238
Ed Tanous271584a2019-07-09 16:24:22 -0700239 for (size_t i = 0; i < sessionToken.size(); ++i)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700240 {
James Feista68a8042020-04-15 15:46:44 -0700241 sessionToken[i] = alphanum[dist(gen)];
242 if (gen.error())
243 {
244 return nullptr;
245 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100246 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700247 // Only need csrf tokens for cookie based auth, token doesn't matter
248 std::string csrfToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700249 csrfToken.resize(sessionTokenSize, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700250 for (size_t i = 0; i < csrfToken.size(); ++i)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700251 {
James Feista68a8042020-04-15 15:46:44 -0700252 csrfToken[i] = alphanum[dist(gen)];
253 if (gen.error())
254 {
255 return nullptr;
256 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700257 }
258
259 std::string uniqueId;
260 uniqueId.resize(10, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700261 for (size_t i = 0; i < uniqueId.size(); ++i)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700262 {
James Feista68a8042020-04-15 15:46:44 -0700263 uniqueId[i] = alphanum[dist(gen)];
264 if (gen.error())
265 {
266 return nullptr;
267 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700268 }
Sunitha Harish92f68222020-05-28 05:09:09 -0500269 auto session = std::make_shared<UserSession>(
270 UserSession{uniqueId, sessionToken, std::string(username),
271 csrfToken, std::string(clientId), std::string(clientIp),
272 std::chrono::steady_clock::now(), persistence, false,
273 isConfigureSelfOnly});
Ed Tanous1abe55e2018-09-05 08:30:59 -0700274 auto it = authTokens.emplace(std::make_pair(sessionToken, session));
275 // Only need to write to disk if session isn't about to be destroyed.
276 needWrite = persistence == PersistenceType::TIMEOUT;
277 return it.first->second;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100278 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700279
280 std::shared_ptr<UserSession>
Ed Tanous39e77502019-03-04 17:35:53 -0800281 loginSessionByToken(const std::string_view token)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700282 {
283 applySessionTimeouts();
Ed Tanous51dae672018-09-05 16:07:32 -0700284 if (token.size() != sessionTokenSize)
285 {
286 return nullptr;
287 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700288 auto sessionIt = authTokens.find(std::string(token));
289 if (sessionIt == authTokens.end())
290 {
291 return nullptr;
292 }
293 std::shared_ptr<UserSession> userSession = sessionIt->second;
294 userSession->lastUpdated = std::chrono::steady_clock::now();
295 return userSession;
296 }
297
Ed Tanous39e77502019-03-04 17:35:53 -0800298 std::shared_ptr<UserSession> getSessionByUid(const std::string_view uid)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700299 {
300 applySessionTimeouts();
301 // TODO(Ed) this is inefficient
302 auto sessionIt = authTokens.begin();
303 while (sessionIt != authTokens.end())
304 {
305 if (sessionIt->second->uniqueId == uid)
306 {
307 return sessionIt->second;
308 }
309 sessionIt++;
310 }
311 return nullptr;
312 }
313
314 void removeSession(std::shared_ptr<UserSession> session)
315 {
Ratan Gupta07386c62019-12-14 14:06:09 +0530316#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
317 crow::ibm_mc_lock::Lock::getInstance().releaseLock(session->uniqueId);
318#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700319 authTokens.erase(session->sessionToken);
320 needWrite = true;
321 }
322
323 std::vector<const std::string*> getUniqueIds(
324 bool getAll = true,
325 const PersistenceType& type = PersistenceType::SINGLE_REQUEST)
326 {
327 applySessionTimeouts();
328
329 std::vector<const std::string*> ret;
330 ret.reserve(authTokens.size());
331 for (auto& session : authTokens)
332 {
333 if (getAll || type == session.second->persistence)
334 {
335 ret.push_back(&session.second->uniqueId);
336 }
337 }
338 return ret;
339 }
340
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100341 void updateAuthMethodsConfig(const AuthConfigMethods& config)
342 {
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100343 bool isTLSchanged = (authMethodsConfig.tls != config.tls);
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100344 authMethodsConfig = config;
345 needWrite = true;
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100346 if (isTLSchanged)
347 {
348 // recreate socket connections with new settings
349 std::raise(SIGHUP);
350 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100351 }
352
353 AuthConfigMethods& getAuthMethodsConfig()
354 {
355 return authMethodsConfig;
356 }
357
Ed Tanous1abe55e2018-09-05 08:30:59 -0700358 bool needsWrite()
359 {
360 return needWrite;
361 }
Ed Tanous271584a2019-07-09 16:24:22 -0700362 int64_t getTimeoutInSeconds() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700363 {
364 return std::chrono::seconds(timeoutInMinutes).count();
365 };
366
367 // Persistent data middleware needs to be able to serialize our authTokens
368 // structure, which is private
369 friend Middleware;
370
371 static SessionStore& getInstance()
372 {
373 static SessionStore sessionStore;
374 return sessionStore;
375 }
376
377 SessionStore(const SessionStore&) = delete;
378 SessionStore& operator=(const SessionStore&) = delete;
379
380 private:
381 SessionStore() : timeoutInMinutes(60)
Gunnar Mills1214b7e2020-06-04 10:11:30 -0500382 {}
Ed Tanous1abe55e2018-09-05 08:30:59 -0700383
384 void applySessionTimeouts()
385 {
386 auto timeNow = std::chrono::steady_clock::now();
387 if (timeNow - lastTimeoutUpdate > std::chrono::minutes(1))
388 {
389 lastTimeoutUpdate = timeNow;
390 auto authTokensIt = authTokens.begin();
391 while (authTokensIt != authTokens.end())
392 {
393 if (timeNow - authTokensIt->second->lastUpdated >=
394 timeoutInMinutes)
395 {
Ratan Gupta07386c62019-12-14 14:06:09 +0530396#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
397 crow::ibm_mc_lock::Lock::getInstance().releaseLock(
398 authTokensIt->second->uniqueId);
399#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700400 authTokensIt = authTokens.erase(authTokensIt);
Ratan Gupta07386c62019-12-14 14:06:09 +0530401
Ed Tanous1abe55e2018-09-05 08:30:59 -0700402 needWrite = true;
403 }
404 else
405 {
406 authTokensIt++;
407 }
408 }
409 }
410 }
Ratan Gupta12c04ef2019-04-03 10:08:11 +0530411
Ed Tanous1abe55e2018-09-05 08:30:59 -0700412 std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate;
Ed Tanous51dae672018-09-05 16:07:32 -0700413 std::unordered_map<std::string, std::shared_ptr<UserSession>,
414 std::hash<std::string>,
415 crow::utility::ConstantTimeCompare>
Ed Tanous1abe55e2018-09-05 08:30:59 -0700416 authTokens;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700417 bool needWrite{false};
418 std::chrono::minutes timeoutInMinutes;
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100419 AuthConfigMethods authMethodsConfig;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100420};
421
Ed Tanous1abe55e2018-09-05 08:30:59 -0700422} // namespace persistent_data
423} // namespace crow
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200424
425// to_json(...) definition for objects of UserSession type
Ed Tanous1abe55e2018-09-05 08:30:59 -0700426namespace nlohmann
427{
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200428template <>
Ed Tanous1abe55e2018-09-05 08:30:59 -0700429struct adl_serializer<std::shared_ptr<crow::persistent_data::UserSession>>
430{
431 static void
432 to_json(nlohmann::json& j,
433 const std::shared_ptr<crow::persistent_data::UserSession>& p)
434 {
435 if (p->persistence !=
436 crow::persistent_data::PersistenceType::SINGLE_REQUEST)
437 {
Sunitha Harish08bdcc72020-05-12 05:17:57 -0500438#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
Sunitha Harish92f68222020-05-28 05:09:09 -0500439 j = nlohmann::json{
440 {"unique_id", p->uniqueId}, {"session_token", p->sessionToken},
441 {"username", p->username}, {"csrf_token", p->csrfToken},
442 {"client_id", p->clientId}, { "client_ip", p->clientIp }};
Sunitha Harish08bdcc72020-05-12 05:17:57 -0500443#else
Ed Tanous1abe55e2018-09-05 08:30:59 -0700444 j = nlohmann::json{{"unique_id", p->uniqueId},
445 {"session_token", p->sessionToken},
446 {"username", p->username},
Sunitha Harish92f68222020-05-28 05:09:09 -0500447 {"csrf_token", p->csrfToken},
448 {"client_ip", p->clientIp}};
Sunitha Harish08bdcc72020-05-12 05:17:57 -0500449#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700450 }
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200451 }
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200452};
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100453
Gunnar Mills1214b7e2020-06-04 10:11:30 -0500454template <>
455struct adl_serializer<crow::persistent_data::AuthConfigMethods>
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100456{
457 static void to_json(nlohmann::json& j,
458 const crow::persistent_data::AuthConfigMethods& c)
459 {
460 j = nlohmann::json{{"XToken", c.xtoken},
461 {"Cookie", c.cookie},
462 {"SessionToken", c.sessionToken},
Zbigniew Kurzynski501f1e52019-10-02 11:22:11 +0200463 {"BasicAuth", c.basic},
464 {"TLS", c.tls}};
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100465 }
466};
Ed Tanous1abe55e2018-09-05 08:30:59 -0700467} // namespace nlohmann