blob: c2fcf319d74d80d97901cea5359ff4eb1406ee51 [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"
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010018
Ed Tanous1abe55e2018-09-05 08:30:59 -070019namespace crow
20{
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010021
Ed Tanous1abe55e2018-09-05 08:30:59 -070022namespace persistent_data
23{
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010024
Ed Tanous51dae672018-09-05 16:07:32 -070025// entropy: 20 characters, 62 possibilities. log2(62^20) = 119 bits of
26// entropy. OWASP recommends at least 64
27// https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#session-id-entropy
28constexpr std::size_t sessionTokenSize = 20;
29
Ed Tanous1abe55e2018-09-05 08:30:59 -070030enum class PersistenceType
31{
32 TIMEOUT, // User session times out after a predetermined amount of time
33 SINGLE_REQUEST // User times out once this request is completed.
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010034};
35
Ed Tanous1abe55e2018-09-05 08:30:59 -070036struct UserSession
37{
38 std::string uniqueId;
39 std::string sessionToken;
40 std::string username;
41 std::string csrfToken;
42 std::chrono::time_point<std::chrono::steady_clock> lastUpdated;
43 PersistenceType persistence;
James Feistf8aa3d22020-04-08 18:32:33 -070044 bool cookieAuth = false;
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +010045
Ed Tanous1abe55e2018-09-05 08:30:59 -070046 /**
47 * @brief Fills object with data from UserSession's JSON representation
48 *
49 * This replaces nlohmann's from_json to ensure no-throw approach
50 *
51 * @param[in] j JSON object from which data should be loaded
52 *
53 * @return a shared pointer if data has been loaded properly, nullptr
54 * otherwise
55 */
56 static std::shared_ptr<UserSession> fromJson(const nlohmann::json& j)
57 {
58 std::shared_ptr<UserSession> userSession =
59 std::make_shared<UserSession>();
60 for (const auto& element : j.items())
61 {
62 const std::string* thisValue =
63 element.value().get_ptr<const std::string*>();
64 if (thisValue == nullptr)
65 {
66 BMCWEB_LOG_ERROR << "Error reading persistent store. Property "
67 << element.key() << " was not of type string";
68 return nullptr;
69 }
70 if (element.key() == "unique_id")
71 {
72 userSession->uniqueId = *thisValue;
73 }
74 else if (element.key() == "session_token")
75 {
76 userSession->sessionToken = *thisValue;
77 }
78 else if (element.key() == "csrf_token")
79 {
80 userSession->csrfToken = *thisValue;
81 }
82 else if (element.key() == "username")
83 {
84 userSession->username = *thisValue;
85 }
86 else
87 {
88 BMCWEB_LOG_ERROR
89 << "Got unexpected property reading persistent file: "
90 << element.key();
91 return nullptr;
92 }
93 }
94
95 // For now, sessions that were persisted through a reboot get their idle
96 // timer reset. This could probably be overcome with a better
97 // understanding of wall clock time and steady timer time, possibly
98 // persisting values with wall clock time instead of steady timer, but
99 // the tradeoffs of all the corner cases involved are non-trivial, so
100 // this is done temporarily
101 userSession->lastUpdated = std::chrono::steady_clock::now();
102 userSession->persistence = PersistenceType::TIMEOUT;
103
104 return userSession;
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +0100105 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100106};
107
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100108struct AuthConfigMethods
109{
110 bool xtoken = true;
111 bool cookie = true;
112 bool sessionToken = true;
113 bool basic = true;
Zbigniew Kurzynskicac94c52019-11-07 12:55:04 +0100114 bool tls = false;
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100115
116 void fromJson(const nlohmann::json& j)
117 {
118 for (const auto& element : j.items())
119 {
120 const bool* value = element.value().get_ptr<const bool*>();
121 if (value == nullptr)
122 {
123 continue;
124 }
125
126 if (element.key() == "XToken")
127 {
128 xtoken = *value;
129 }
130 else if (element.key() == "Cookie")
131 {
132 cookie = *value;
133 }
134 else if (element.key() == "SessionToken")
135 {
136 sessionToken = *value;
137 }
138 else if (element.key() == "BasicAuth")
139 {
140 basic = *value;
141 }
Zbigniew Kurzynski501f1e52019-10-02 11:22:11 +0200142 else if (element.key() == "TLS")
143 {
144 tls = *value;
145 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100146 }
147 }
148};
149
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100150class Middleware;
151
James Feista68a8042020-04-15 15:46:44 -0700152struct OpenSSLGenerator
153{
154
155 uint8_t operator()(void)
156 {
157 uint8_t index = 0;
158 int rc = RAND_bytes(&index, sizeof(index));
159 if (rc != opensslSuccess)
160 {
161 std::cerr << "Cannot get random number\n";
162 err = true;
163 }
164
165 return index;
166 };
167
168 uint8_t max()
169 {
170 return std::numeric_limits<uint8_t>::max();
171 }
172 uint8_t min()
173 {
174 return std::numeric_limits<uint8_t>::min();
175 }
176
177 bool error()
178 {
179 return err;
180 }
181
182 // all generators require this variable
183 using result_type = uint8_t;
184
185 private:
186 // RAND_bytes() returns 1 on success, 0 otherwise. -1 if bad function
187 static constexpr int opensslSuccess = 1;
188 bool err = false;
189};
190
Ed Tanous1abe55e2018-09-05 08:30:59 -0700191class SessionStore
192{
193 public:
194 std::shared_ptr<UserSession> generateUserSession(
Ed Tanous39e77502019-03-04 17:35:53 -0800195 const std::string_view username,
Ed Tanous1abe55e2018-09-05 08:30:59 -0700196 PersistenceType persistence = PersistenceType::TIMEOUT)
197 {
198 // TODO(ed) find a secure way to not generate session identifiers if
199 // persistence is set to SINGLE_REQUEST
200 static constexpr std::array<char, 62> alphanum = {
Joseph Reynolds368b1d42019-08-15 15:29:06 -0500201 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
202 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
203 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
Ed Tanous1abe55e2018-09-05 08:30:59 -0700204 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
205 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100206
Ed Tanous1abe55e2018-09-05 08:30:59 -0700207 std::string sessionToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700208 sessionToken.resize(sessionTokenSize, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700209 std::uniform_int_distribution<size_t> dist(0, alphanum.size() - 1);
James Feista68a8042020-04-15 15:46:44 -0700210
211 OpenSSLGenerator gen;
212
Ed Tanous271584a2019-07-09 16:24:22 -0700213 for (size_t i = 0; i < sessionToken.size(); ++i)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700214 {
James Feista68a8042020-04-15 15:46:44 -0700215 sessionToken[i] = alphanum[dist(gen)];
216 if (gen.error())
217 {
218 return nullptr;
219 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100220 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700221 // Only need csrf tokens for cookie based auth, token doesn't matter
222 std::string csrfToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700223 csrfToken.resize(sessionTokenSize, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700224 for (size_t i = 0; i < csrfToken.size(); ++i)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700225 {
James Feista68a8042020-04-15 15:46:44 -0700226 csrfToken[i] = alphanum[dist(gen)];
227 if (gen.error())
228 {
229 return nullptr;
230 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700231 }
232
233 std::string uniqueId;
234 uniqueId.resize(10, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700235 for (size_t i = 0; i < uniqueId.size(); ++i)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700236 {
James Feista68a8042020-04-15 15:46:44 -0700237 uniqueId[i] = alphanum[dist(gen)];
238 if (gen.error())
239 {
240 return nullptr;
241 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700242 }
Ratan Gupta12c04ef2019-04-03 10:08:11 +0530243
Ed Tanous1abe55e2018-09-05 08:30:59 -0700244 auto session = std::make_shared<UserSession>(UserSession{
Ed Tanousca0c93b2019-09-19 11:53:50 -0700245 uniqueId, sessionToken, std::string(username), csrfToken,
Ed Tanous1abe55e2018-09-05 08:30:59 -0700246 std::chrono::steady_clock::now(), persistence});
247 auto it = authTokens.emplace(std::make_pair(sessionToken, session));
248 // Only need to write to disk if session isn't about to be destroyed.
249 needWrite = persistence == PersistenceType::TIMEOUT;
250 return it.first->second;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100251 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700252
253 std::shared_ptr<UserSession>
Ed Tanous39e77502019-03-04 17:35:53 -0800254 loginSessionByToken(const std::string_view token)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700255 {
256 applySessionTimeouts();
Ed Tanous51dae672018-09-05 16:07:32 -0700257 if (token.size() != sessionTokenSize)
258 {
259 return nullptr;
260 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700261 auto sessionIt = authTokens.find(std::string(token));
262 if (sessionIt == authTokens.end())
263 {
264 return nullptr;
265 }
266 std::shared_ptr<UserSession> userSession = sessionIt->second;
267 userSession->lastUpdated = std::chrono::steady_clock::now();
268 return userSession;
269 }
270
Ed Tanous39e77502019-03-04 17:35:53 -0800271 std::shared_ptr<UserSession> getSessionByUid(const std::string_view uid)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700272 {
273 applySessionTimeouts();
274 // TODO(Ed) this is inefficient
275 auto sessionIt = authTokens.begin();
276 while (sessionIt != authTokens.end())
277 {
278 if (sessionIt->second->uniqueId == uid)
279 {
280 return sessionIt->second;
281 }
282 sessionIt++;
283 }
284 return nullptr;
285 }
286
287 void removeSession(std::shared_ptr<UserSession> session)
288 {
289 authTokens.erase(session->sessionToken);
290 needWrite = true;
291 }
292
293 std::vector<const std::string*> getUniqueIds(
294 bool getAll = true,
295 const PersistenceType& type = PersistenceType::SINGLE_REQUEST)
296 {
297 applySessionTimeouts();
298
299 std::vector<const std::string*> ret;
300 ret.reserve(authTokens.size());
301 for (auto& session : authTokens)
302 {
303 if (getAll || type == session.second->persistence)
304 {
305 ret.push_back(&session.second->uniqueId);
306 }
307 }
308 return ret;
309 }
310
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100311 void updateAuthMethodsConfig(const AuthConfigMethods& config)
312 {
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100313 bool isTLSchanged = (authMethodsConfig.tls != config.tls);
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100314 authMethodsConfig = config;
315 needWrite = true;
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100316 if (isTLSchanged)
317 {
318 // recreate socket connections with new settings
319 std::raise(SIGHUP);
320 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100321 }
322
323 AuthConfigMethods& getAuthMethodsConfig()
324 {
325 return authMethodsConfig;
326 }
327
Ed Tanous1abe55e2018-09-05 08:30:59 -0700328 bool needsWrite()
329 {
330 return needWrite;
331 }
Ed Tanous271584a2019-07-09 16:24:22 -0700332 int64_t getTimeoutInSeconds() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700333 {
334 return std::chrono::seconds(timeoutInMinutes).count();
335 };
336
337 // Persistent data middleware needs to be able to serialize our authTokens
338 // structure, which is private
339 friend Middleware;
340
341 static SessionStore& getInstance()
342 {
343 static SessionStore sessionStore;
344 return sessionStore;
345 }
346
347 SessionStore(const SessionStore&) = delete;
348 SessionStore& operator=(const SessionStore&) = delete;
349
350 private:
351 SessionStore() : timeoutInMinutes(60)
352 {
353 }
354
355 void applySessionTimeouts()
356 {
357 auto timeNow = std::chrono::steady_clock::now();
358 if (timeNow - lastTimeoutUpdate > std::chrono::minutes(1))
359 {
360 lastTimeoutUpdate = timeNow;
361 auto authTokensIt = authTokens.begin();
362 while (authTokensIt != authTokens.end())
363 {
364 if (timeNow - authTokensIt->second->lastUpdated >=
365 timeoutInMinutes)
366 {
367 authTokensIt = authTokens.erase(authTokensIt);
368 needWrite = true;
369 }
370 else
371 {
372 authTokensIt++;
373 }
374 }
375 }
376 }
Ratan Gupta12c04ef2019-04-03 10:08:11 +0530377
Ed Tanous1abe55e2018-09-05 08:30:59 -0700378 std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate;
Ed Tanous51dae672018-09-05 16:07:32 -0700379 std::unordered_map<std::string, std::shared_ptr<UserSession>,
380 std::hash<std::string>,
381 crow::utility::ConstantTimeCompare>
Ed Tanous1abe55e2018-09-05 08:30:59 -0700382 authTokens;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700383 bool needWrite{false};
384 std::chrono::minutes timeoutInMinutes;
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100385 AuthConfigMethods authMethodsConfig;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100386};
387
Ed Tanous1abe55e2018-09-05 08:30:59 -0700388} // namespace persistent_data
389} // namespace crow
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200390
391// to_json(...) definition for objects of UserSession type
Ed Tanous1abe55e2018-09-05 08:30:59 -0700392namespace nlohmann
393{
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200394template <>
Ed Tanous1abe55e2018-09-05 08:30:59 -0700395struct adl_serializer<std::shared_ptr<crow::persistent_data::UserSession>>
396{
397 static void
398 to_json(nlohmann::json& j,
399 const std::shared_ptr<crow::persistent_data::UserSession>& p)
400 {
401 if (p->persistence !=
402 crow::persistent_data::PersistenceType::SINGLE_REQUEST)
403 {
404 j = nlohmann::json{{"unique_id", p->uniqueId},
405 {"session_token", p->sessionToken},
406 {"username", p->username},
407 {"csrf_token", p->csrfToken}};
408 }
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200409 }
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200410};
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100411
412template <> struct adl_serializer<crow::persistent_data::AuthConfigMethods>
413{
414 static void to_json(nlohmann::json& j,
415 const crow::persistent_data::AuthConfigMethods& c)
416 {
417 j = nlohmann::json{{"XToken", c.xtoken},
418 {"Cookie", c.cookie},
419 {"SessionToken", c.sessionToken},
Zbigniew Kurzynski501f1e52019-10-02 11:22:11 +0200420 {"BasicAuth", c.basic},
421 {"TLS", c.tls}};
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100422 }
423};
Ed Tanous1abe55e2018-09-05 08:30:59 -0700424} // namespace nlohmann