blob: 9d24327eab4d338d45e2788ea53dffe8d252954a [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;
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +010048
Ed Tanous1abe55e2018-09-05 08:30:59 -070049 /**
50 * @brief Fills object with data from UserSession's JSON representation
51 *
52 * This replaces nlohmann's from_json to ensure no-throw approach
53 *
54 * @param[in] j JSON object from which data should be loaded
55 *
56 * @return a shared pointer if data has been loaded properly, nullptr
57 * otherwise
58 */
59 static std::shared_ptr<UserSession> fromJson(const nlohmann::json& j)
60 {
61 std::shared_ptr<UserSession> userSession =
62 std::make_shared<UserSession>();
63 for (const auto& element : j.items())
64 {
65 const std::string* thisValue =
66 element.value().get_ptr<const std::string*>();
67 if (thisValue == nullptr)
68 {
69 BMCWEB_LOG_ERROR << "Error reading persistent store. Property "
70 << element.key() << " was not of type string";
71 return nullptr;
72 }
73 if (element.key() == "unique_id")
74 {
75 userSession->uniqueId = *thisValue;
76 }
77 else if (element.key() == "session_token")
78 {
79 userSession->sessionToken = *thisValue;
80 }
81 else if (element.key() == "csrf_token")
82 {
83 userSession->csrfToken = *thisValue;
84 }
85 else if (element.key() == "username")
86 {
87 userSession->username = *thisValue;
88 }
89 else
90 {
91 BMCWEB_LOG_ERROR
92 << "Got unexpected property reading persistent file: "
93 << element.key();
94 return nullptr;
95 }
96 }
97
98 // For now, sessions that were persisted through a reboot get their idle
99 // timer reset. This could probably be overcome with a better
100 // understanding of wall clock time and steady timer time, possibly
101 // persisting values with wall clock time instead of steady timer, but
102 // the tradeoffs of all the corner cases involved are non-trivial, so
103 // this is done temporarily
104 userSession->lastUpdated = std::chrono::steady_clock::now();
105 userSession->persistence = PersistenceType::TIMEOUT;
106
107 return userSession;
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +0100108 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100109};
110
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100111struct AuthConfigMethods
112{
113 bool xtoken = true;
114 bool cookie = true;
115 bool sessionToken = true;
116 bool basic = true;
Zbigniew Kurzynskicac94c52019-11-07 12:55:04 +0100117 bool tls = false;
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100118
119 void fromJson(const nlohmann::json& j)
120 {
121 for (const auto& element : j.items())
122 {
123 const bool* value = element.value().get_ptr<const bool*>();
124 if (value == nullptr)
125 {
126 continue;
127 }
128
129 if (element.key() == "XToken")
130 {
131 xtoken = *value;
132 }
133 else if (element.key() == "Cookie")
134 {
135 cookie = *value;
136 }
137 else if (element.key() == "SessionToken")
138 {
139 sessionToken = *value;
140 }
141 else if (element.key() == "BasicAuth")
142 {
143 basic = *value;
144 }
Zbigniew Kurzynski501f1e52019-10-02 11:22:11 +0200145 else if (element.key() == "TLS")
146 {
147 tls = *value;
148 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100149 }
150 }
151};
152
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100153class Middleware;
154
James Feista68a8042020-04-15 15:46:44 -0700155struct OpenSSLGenerator
156{
157
158 uint8_t operator()(void)
159 {
160 uint8_t index = 0;
161 int rc = RAND_bytes(&index, sizeof(index));
162 if (rc != opensslSuccess)
163 {
164 std::cerr << "Cannot get random number\n";
165 err = true;
166 }
167
168 return index;
169 };
170
171 uint8_t max()
172 {
173 return std::numeric_limits<uint8_t>::max();
174 }
175 uint8_t min()
176 {
177 return std::numeric_limits<uint8_t>::min();
178 }
179
180 bool error()
181 {
182 return err;
183 }
184
185 // all generators require this variable
186 using result_type = uint8_t;
187
188 private:
189 // RAND_bytes() returns 1 on success, 0 otherwise. -1 if bad function
190 static constexpr int opensslSuccess = 1;
191 bool err = false;
192};
193
Ed Tanous1abe55e2018-09-05 08:30:59 -0700194class SessionStore
195{
196 public:
197 std::shared_ptr<UserSession> generateUserSession(
Ed Tanous39e77502019-03-04 17:35:53 -0800198 const std::string_view username,
Ed Tanous1abe55e2018-09-05 08:30:59 -0700199 PersistenceType persistence = PersistenceType::TIMEOUT)
200 {
201 // TODO(ed) find a secure way to not generate session identifiers if
202 // persistence is set to SINGLE_REQUEST
203 static constexpr std::array<char, 62> alphanum = {
Joseph Reynolds368b1d42019-08-15 15:29:06 -0500204 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
205 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
206 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
Ed Tanous1abe55e2018-09-05 08:30:59 -0700207 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
208 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100209
Ed Tanous1abe55e2018-09-05 08:30:59 -0700210 std::string sessionToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700211 sessionToken.resize(sessionTokenSize, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700212 std::uniform_int_distribution<size_t> dist(0, alphanum.size() - 1);
James Feista68a8042020-04-15 15:46:44 -0700213
214 OpenSSLGenerator gen;
215
Ed Tanous271584a2019-07-09 16:24:22 -0700216 for (size_t i = 0; i < sessionToken.size(); ++i)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700217 {
James Feista68a8042020-04-15 15:46:44 -0700218 sessionToken[i] = alphanum[dist(gen)];
219 if (gen.error())
220 {
221 return nullptr;
222 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100223 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700224 // Only need csrf tokens for cookie based auth, token doesn't matter
225 std::string csrfToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700226 csrfToken.resize(sessionTokenSize, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700227 for (size_t i = 0; i < csrfToken.size(); ++i)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700228 {
James Feista68a8042020-04-15 15:46:44 -0700229 csrfToken[i] = alphanum[dist(gen)];
230 if (gen.error())
231 {
232 return nullptr;
233 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700234 }
235
236 std::string uniqueId;
237 uniqueId.resize(10, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700238 for (size_t i = 0; i < uniqueId.size(); ++i)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700239 {
James Feista68a8042020-04-15 15:46:44 -0700240 uniqueId[i] = alphanum[dist(gen)];
241 if (gen.error())
242 {
243 return nullptr;
244 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700245 }
Ratan Gupta12c04ef2019-04-03 10:08:11 +0530246
Ed Tanous1abe55e2018-09-05 08:30:59 -0700247 auto session = std::make_shared<UserSession>(UserSession{
Ed Tanousca0c93b2019-09-19 11:53:50 -0700248 uniqueId, sessionToken, std::string(username), csrfToken,
Ed Tanous1abe55e2018-09-05 08:30:59 -0700249 std::chrono::steady_clock::now(), persistence});
250 auto it = authTokens.emplace(std::make_pair(sessionToken, session));
251 // Only need to write to disk if session isn't about to be destroyed.
252 needWrite = persistence == PersistenceType::TIMEOUT;
253 return it.first->second;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100254 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700255
256 std::shared_ptr<UserSession>
Ed Tanous39e77502019-03-04 17:35:53 -0800257 loginSessionByToken(const std::string_view token)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700258 {
259 applySessionTimeouts();
Ed Tanous51dae672018-09-05 16:07:32 -0700260 if (token.size() != sessionTokenSize)
261 {
262 return nullptr;
263 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700264 auto sessionIt = authTokens.find(std::string(token));
265 if (sessionIt == authTokens.end())
266 {
267 return nullptr;
268 }
269 std::shared_ptr<UserSession> userSession = sessionIt->second;
270 userSession->lastUpdated = std::chrono::steady_clock::now();
271 return userSession;
272 }
273
Ed Tanous39e77502019-03-04 17:35:53 -0800274 std::shared_ptr<UserSession> getSessionByUid(const std::string_view uid)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700275 {
276 applySessionTimeouts();
277 // TODO(Ed) this is inefficient
278 auto sessionIt = authTokens.begin();
279 while (sessionIt != authTokens.end())
280 {
281 if (sessionIt->second->uniqueId == uid)
282 {
283 return sessionIt->second;
284 }
285 sessionIt++;
286 }
287 return nullptr;
288 }
289
290 void removeSession(std::shared_ptr<UserSession> session)
291 {
Ratan Gupta07386c62019-12-14 14:06:09 +0530292#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
293 crow::ibm_mc_lock::Lock::getInstance().releaseLock(session->uniqueId);
294#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700295 authTokens.erase(session->sessionToken);
296 needWrite = true;
297 }
298
299 std::vector<const std::string*> getUniqueIds(
300 bool getAll = true,
301 const PersistenceType& type = PersistenceType::SINGLE_REQUEST)
302 {
303 applySessionTimeouts();
304
305 std::vector<const std::string*> ret;
306 ret.reserve(authTokens.size());
307 for (auto& session : authTokens)
308 {
309 if (getAll || type == session.second->persistence)
310 {
311 ret.push_back(&session.second->uniqueId);
312 }
313 }
314 return ret;
315 }
316
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100317 void updateAuthMethodsConfig(const AuthConfigMethods& config)
318 {
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100319 bool isTLSchanged = (authMethodsConfig.tls != config.tls);
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100320 authMethodsConfig = config;
321 needWrite = true;
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100322 if (isTLSchanged)
323 {
324 // recreate socket connections with new settings
325 std::raise(SIGHUP);
326 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100327 }
328
329 AuthConfigMethods& getAuthMethodsConfig()
330 {
331 return authMethodsConfig;
332 }
333
Ed Tanous1abe55e2018-09-05 08:30:59 -0700334 bool needsWrite()
335 {
336 return needWrite;
337 }
Ed Tanous271584a2019-07-09 16:24:22 -0700338 int64_t getTimeoutInSeconds() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700339 {
340 return std::chrono::seconds(timeoutInMinutes).count();
341 };
342
343 // Persistent data middleware needs to be able to serialize our authTokens
344 // structure, which is private
345 friend Middleware;
346
347 static SessionStore& getInstance()
348 {
349 static SessionStore sessionStore;
350 return sessionStore;
351 }
352
353 SessionStore(const SessionStore&) = delete;
354 SessionStore& operator=(const SessionStore&) = delete;
355
356 private:
357 SessionStore() : timeoutInMinutes(60)
358 {
359 }
360
361 void applySessionTimeouts()
362 {
363 auto timeNow = std::chrono::steady_clock::now();
364 if (timeNow - lastTimeoutUpdate > std::chrono::minutes(1))
365 {
366 lastTimeoutUpdate = timeNow;
367 auto authTokensIt = authTokens.begin();
368 while (authTokensIt != authTokens.end())
369 {
370 if (timeNow - authTokensIt->second->lastUpdated >=
371 timeoutInMinutes)
372 {
Ratan Gupta07386c62019-12-14 14:06:09 +0530373#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
374 crow::ibm_mc_lock::Lock::getInstance().releaseLock(
375 authTokensIt->second->uniqueId);
376#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700377 authTokensIt = authTokens.erase(authTokensIt);
Ratan Gupta07386c62019-12-14 14:06:09 +0530378
Ed Tanous1abe55e2018-09-05 08:30:59 -0700379 needWrite = true;
380 }
381 else
382 {
383 authTokensIt++;
384 }
385 }
386 }
387 }
Ratan Gupta12c04ef2019-04-03 10:08:11 +0530388
Ed Tanous1abe55e2018-09-05 08:30:59 -0700389 std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate;
Ed Tanous51dae672018-09-05 16:07:32 -0700390 std::unordered_map<std::string, std::shared_ptr<UserSession>,
391 std::hash<std::string>,
392 crow::utility::ConstantTimeCompare>
Ed Tanous1abe55e2018-09-05 08:30:59 -0700393 authTokens;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700394 bool needWrite{false};
395 std::chrono::minutes timeoutInMinutes;
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100396 AuthConfigMethods authMethodsConfig;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100397};
398
Ed Tanous1abe55e2018-09-05 08:30:59 -0700399} // namespace persistent_data
400} // namespace crow
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200401
402// to_json(...) definition for objects of UserSession type
Ed Tanous1abe55e2018-09-05 08:30:59 -0700403namespace nlohmann
404{
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200405template <>
Ed Tanous1abe55e2018-09-05 08:30:59 -0700406struct adl_serializer<std::shared_ptr<crow::persistent_data::UserSession>>
407{
408 static void
409 to_json(nlohmann::json& j,
410 const std::shared_ptr<crow::persistent_data::UserSession>& p)
411 {
412 if (p->persistence !=
413 crow::persistent_data::PersistenceType::SINGLE_REQUEST)
414 {
415 j = nlohmann::json{{"unique_id", p->uniqueId},
416 {"session_token", p->sessionToken},
417 {"username", p->username},
418 {"csrf_token", p->csrfToken}};
419 }
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200420 }
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200421};
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100422
423template <> struct adl_serializer<crow::persistent_data::AuthConfigMethods>
424{
425 static void to_json(nlohmann::json& j,
426 const crow::persistent_data::AuthConfigMethods& c)
427 {
428 j = nlohmann::json{{"XToken", c.xtoken},
429 {"Cookie", c.cookie},
430 {"SessionToken", c.sessionToken},
Zbigniew Kurzynski501f1e52019-10-02 11:22:11 +0200431 {"BasicAuth", c.basic},
432 {"TLS", c.tls}};
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100433 }
434};
Ed Tanous1abe55e2018-09-05 08:30:59 -0700435} // namespace nlohmann