blob: a7ffe289219e0a9b8954fb870620451c06cb8f06 [file] [log] [blame]
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +01001#pragma once
2
James Feista68a8042020-04-15 15:46:44 -07003#include <openssl/rand.h>
4
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +01005#include <boost/container/flat_map.hpp>
6#include <boost/uuid/uuid.hpp>
7#include <boost/uuid/uuid_generators.hpp>
8#include <boost/uuid/uuid_io.hpp>
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +01009#include <csignal>
Ratan Gupta12c04ef2019-04-03 10:08:11 +053010#include <dbus_singleton.hpp>
Ed Tanous1abe55e2018-09-05 08:30:59 -070011#include <nlohmann/json.hpp>
12#include <pam_authenticate.hpp>
13#include <random>
RAJESWARAN THILLAIGOVINDAN70525172019-07-09 13:15:05 -050014#include <sdbusplus/bus/match.hpp>
Ratan Gupta12c04ef2019-04-03 10:08:11 +053015
Ed Tanousc94ad492019-10-10 15:39:33 -070016#include "logging.h"
Ed Tanous51dae672018-09-05 16:07:32 -070017#include "utility.h"
Ratan Gupta07386c62019-12-14 14:06:09 +053018#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
19#include <ibm/locks.hpp>
20#endif
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010021
Ed Tanous1abe55e2018-09-05 08:30:59 -070022namespace crow
23{
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;
45 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 }
98 else
99 {
100 BMCWEB_LOG_ERROR
101 << "Got unexpected property reading persistent file: "
102 << element.key();
103 return nullptr;
104 }
105 }
106
107 // For now, sessions that were persisted through a reboot get their idle
108 // timer reset. This could probably be overcome with a better
109 // understanding of wall clock time and steady timer time, possibly
110 // persisting values with wall clock time instead of steady timer, but
111 // the tradeoffs of all the corner cases involved are non-trivial, so
112 // this is done temporarily
113 userSession->lastUpdated = std::chrono::steady_clock::now();
114 userSession->persistence = PersistenceType::TIMEOUT;
115
116 return userSession;
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +0100117 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100118};
119
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100120struct AuthConfigMethods
121{
122 bool xtoken = true;
123 bool cookie = true;
124 bool sessionToken = true;
125 bool basic = true;
Zbigniew Kurzynskicac94c52019-11-07 12:55:04 +0100126 bool tls = false;
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100127
128 void fromJson(const nlohmann::json& j)
129 {
130 for (const auto& element : j.items())
131 {
132 const bool* value = element.value().get_ptr<const bool*>();
133 if (value == nullptr)
134 {
135 continue;
136 }
137
138 if (element.key() == "XToken")
139 {
140 xtoken = *value;
141 }
142 else if (element.key() == "Cookie")
143 {
144 cookie = *value;
145 }
146 else if (element.key() == "SessionToken")
147 {
148 sessionToken = *value;
149 }
150 else if (element.key() == "BasicAuth")
151 {
152 basic = *value;
153 }
Zbigniew Kurzynski501f1e52019-10-02 11:22:11 +0200154 else if (element.key() == "TLS")
155 {
156 tls = *value;
157 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100158 }
159 }
160};
161
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100162class Middleware;
163
James Feista68a8042020-04-15 15:46:44 -0700164struct OpenSSLGenerator
165{
166
167 uint8_t operator()(void)
168 {
169 uint8_t index = 0;
170 int rc = RAND_bytes(&index, sizeof(index));
171 if (rc != opensslSuccess)
172 {
173 std::cerr << "Cannot get random number\n";
174 err = true;
175 }
176
177 return index;
178 };
179
180 uint8_t max()
181 {
182 return std::numeric_limits<uint8_t>::max();
183 }
184 uint8_t min()
185 {
186 return std::numeric_limits<uint8_t>::min();
187 }
188
189 bool error()
190 {
191 return err;
192 }
193
194 // all generators require this variable
195 using result_type = uint8_t;
196
197 private:
198 // RAND_bytes() returns 1 on success, 0 otherwise. -1 if bad function
199 static constexpr int opensslSuccess = 1;
200 bool err = false;
201};
202
Ed Tanous1abe55e2018-09-05 08:30:59 -0700203class SessionStore
204{
205 public:
206 std::shared_ptr<UserSession> generateUserSession(
Ed Tanous39e77502019-03-04 17:35:53 -0800207 const std::string_view username,
Joseph Reynolds3bf4e632020-02-06 14:44:32 -0600208 PersistenceType persistence = PersistenceType::TIMEOUT,
209 bool isConfigureSelfOnly = false)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700210 {
211 // TODO(ed) find a secure way to not generate session identifiers if
212 // persistence is set to SINGLE_REQUEST
213 static constexpr std::array<char, 62> alphanum = {
Joseph Reynolds368b1d42019-08-15 15:29:06 -0500214 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
215 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
216 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
Ed Tanous1abe55e2018-09-05 08:30:59 -0700217 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
218 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100219
Ed Tanous1abe55e2018-09-05 08:30:59 -0700220 std::string sessionToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700221 sessionToken.resize(sessionTokenSize, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700222 std::uniform_int_distribution<size_t> dist(0, alphanum.size() - 1);
James Feista68a8042020-04-15 15:46:44 -0700223
224 OpenSSLGenerator gen;
225
Ed Tanous271584a2019-07-09 16:24:22 -0700226 for (size_t i = 0; i < sessionToken.size(); ++i)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700227 {
James Feista68a8042020-04-15 15:46:44 -0700228 sessionToken[i] = alphanum[dist(gen)];
229 if (gen.error())
230 {
231 return nullptr;
232 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100233 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700234 // Only need csrf tokens for cookie based auth, token doesn't matter
235 std::string csrfToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700236 csrfToken.resize(sessionTokenSize, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700237 for (size_t i = 0; i < csrfToken.size(); ++i)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700238 {
James Feista68a8042020-04-15 15:46:44 -0700239 csrfToken[i] = alphanum[dist(gen)];
240 if (gen.error())
241 {
242 return nullptr;
243 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700244 }
245
246 std::string uniqueId;
247 uniqueId.resize(10, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700248 for (size_t i = 0; i < uniqueId.size(); ++i)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700249 {
James Feista68a8042020-04-15 15:46:44 -0700250 uniqueId[i] = alphanum[dist(gen)];
251 if (gen.error())
252 {
253 return nullptr;
254 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700255 }
Ratan Gupta12c04ef2019-04-03 10:08:11 +0530256
Joseph Reynolds3bf4e632020-02-06 14:44:32 -0600257 auto session = std::make_shared<UserSession>(
258 UserSession{uniqueId, sessionToken, std::string(username),
259 csrfToken, std::chrono::steady_clock::now(),
260 persistence, false, isConfigureSelfOnly});
Ed Tanous1abe55e2018-09-05 08:30:59 -0700261 auto it = authTokens.emplace(std::make_pair(sessionToken, session));
262 // Only need to write to disk if session isn't about to be destroyed.
263 needWrite = persistence == PersistenceType::TIMEOUT;
264 return it.first->second;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100265 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700266
267 std::shared_ptr<UserSession>
Ed Tanous39e77502019-03-04 17:35:53 -0800268 loginSessionByToken(const std::string_view token)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700269 {
270 applySessionTimeouts();
Ed Tanous51dae672018-09-05 16:07:32 -0700271 if (token.size() != sessionTokenSize)
272 {
273 return nullptr;
274 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700275 auto sessionIt = authTokens.find(std::string(token));
276 if (sessionIt == authTokens.end())
277 {
278 return nullptr;
279 }
280 std::shared_ptr<UserSession> userSession = sessionIt->second;
281 userSession->lastUpdated = std::chrono::steady_clock::now();
282 return userSession;
283 }
284
Ed Tanous39e77502019-03-04 17:35:53 -0800285 std::shared_ptr<UserSession> getSessionByUid(const std::string_view uid)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700286 {
287 applySessionTimeouts();
288 // TODO(Ed) this is inefficient
289 auto sessionIt = authTokens.begin();
290 while (sessionIt != authTokens.end())
291 {
292 if (sessionIt->second->uniqueId == uid)
293 {
294 return sessionIt->second;
295 }
296 sessionIt++;
297 }
298 return nullptr;
299 }
300
301 void removeSession(std::shared_ptr<UserSession> session)
302 {
Ratan Gupta07386c62019-12-14 14:06:09 +0530303#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
304 crow::ibm_mc_lock::Lock::getInstance().releaseLock(session->uniqueId);
305#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700306 authTokens.erase(session->sessionToken);
307 needWrite = true;
308 }
309
310 std::vector<const std::string*> getUniqueIds(
311 bool getAll = true,
312 const PersistenceType& type = PersistenceType::SINGLE_REQUEST)
313 {
314 applySessionTimeouts();
315
316 std::vector<const std::string*> ret;
317 ret.reserve(authTokens.size());
318 for (auto& session : authTokens)
319 {
320 if (getAll || type == session.second->persistence)
321 {
322 ret.push_back(&session.second->uniqueId);
323 }
324 }
325 return ret;
326 }
327
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100328 void updateAuthMethodsConfig(const AuthConfigMethods& config)
329 {
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100330 bool isTLSchanged = (authMethodsConfig.tls != config.tls);
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100331 authMethodsConfig = config;
332 needWrite = true;
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100333 if (isTLSchanged)
334 {
335 // recreate socket connections with new settings
336 std::raise(SIGHUP);
337 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100338 }
339
340 AuthConfigMethods& getAuthMethodsConfig()
341 {
342 return authMethodsConfig;
343 }
344
Ed Tanous1abe55e2018-09-05 08:30:59 -0700345 bool needsWrite()
346 {
347 return needWrite;
348 }
Ed Tanous271584a2019-07-09 16:24:22 -0700349 int64_t getTimeoutInSeconds() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700350 {
351 return std::chrono::seconds(timeoutInMinutes).count();
352 };
353
354 // Persistent data middleware needs to be able to serialize our authTokens
355 // structure, which is private
356 friend Middleware;
357
358 static SessionStore& getInstance()
359 {
360 static SessionStore sessionStore;
361 return sessionStore;
362 }
363
364 SessionStore(const SessionStore&) = delete;
365 SessionStore& operator=(const SessionStore&) = delete;
366
367 private:
368 SessionStore() : timeoutInMinutes(60)
369 {
370 }
371
372 void applySessionTimeouts()
373 {
374 auto timeNow = std::chrono::steady_clock::now();
375 if (timeNow - lastTimeoutUpdate > std::chrono::minutes(1))
376 {
377 lastTimeoutUpdate = timeNow;
378 auto authTokensIt = authTokens.begin();
379 while (authTokensIt != authTokens.end())
380 {
381 if (timeNow - authTokensIt->second->lastUpdated >=
382 timeoutInMinutes)
383 {
Ratan Gupta07386c62019-12-14 14:06:09 +0530384#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
385 crow::ibm_mc_lock::Lock::getInstance().releaseLock(
386 authTokensIt->second->uniqueId);
387#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700388 authTokensIt = authTokens.erase(authTokensIt);
Ratan Gupta07386c62019-12-14 14:06:09 +0530389
Ed Tanous1abe55e2018-09-05 08:30:59 -0700390 needWrite = true;
391 }
392 else
393 {
394 authTokensIt++;
395 }
396 }
397 }
398 }
Ratan Gupta12c04ef2019-04-03 10:08:11 +0530399
Ed Tanous1abe55e2018-09-05 08:30:59 -0700400 std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate;
Ed Tanous51dae672018-09-05 16:07:32 -0700401 std::unordered_map<std::string, std::shared_ptr<UserSession>,
402 std::hash<std::string>,
403 crow::utility::ConstantTimeCompare>
Ed Tanous1abe55e2018-09-05 08:30:59 -0700404 authTokens;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700405 bool needWrite{false};
406 std::chrono::minutes timeoutInMinutes;
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100407 AuthConfigMethods authMethodsConfig;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100408};
409
Ed Tanous1abe55e2018-09-05 08:30:59 -0700410} // namespace persistent_data
411} // namespace crow
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200412
413// to_json(...) definition for objects of UserSession type
Ed Tanous1abe55e2018-09-05 08:30:59 -0700414namespace nlohmann
415{
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200416template <>
Ed Tanous1abe55e2018-09-05 08:30:59 -0700417struct adl_serializer<std::shared_ptr<crow::persistent_data::UserSession>>
418{
419 static void
420 to_json(nlohmann::json& j,
421 const std::shared_ptr<crow::persistent_data::UserSession>& p)
422 {
423 if (p->persistence !=
424 crow::persistent_data::PersistenceType::SINGLE_REQUEST)
425 {
426 j = nlohmann::json{{"unique_id", p->uniqueId},
427 {"session_token", p->sessionToken},
428 {"username", p->username},
429 {"csrf_token", p->csrfToken}};
430 }
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200431 }
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200432};
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100433
434template <> struct adl_serializer<crow::persistent_data::AuthConfigMethods>
435{
436 static void to_json(nlohmann::json& j,
437 const crow::persistent_data::AuthConfigMethods& c)
438 {
439 j = nlohmann::json{{"XToken", c.xtoken},
440 {"Cookie", c.cookie},
441 {"SessionToken", c.sessionToken},
Zbigniew Kurzynski501f1e52019-10-02 11:22:11 +0200442 {"BasicAuth", c.basic},
443 {"TLS", c.tls}};
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100444 }
445};
Ed Tanous1abe55e2018-09-05 08:30:59 -0700446} // namespace nlohmann