blob: 7c2575d5d1fea5d719b8d18a8ff012475691ba02 [file] [log] [blame]
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +01001#pragma once
2
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +01003#include <boost/container/flat_map.hpp>
4#include <boost/uuid/uuid.hpp>
5#include <boost/uuid/uuid_generators.hpp>
6#include <boost/uuid/uuid_io.hpp>
Ratan Gupta12c04ef2019-04-03 10:08:11 +05307#include <dbus_singleton.hpp>
Ed Tanous1abe55e2018-09-05 08:30:59 -07008#include <nlohmann/json.hpp>
9#include <pam_authenticate.hpp>
10#include <random>
RAJESWARAN THILLAIGOVINDAN70525172019-07-09 13:15:05 -050011#include <sdbusplus/bus/match.hpp>
Ratan Gupta12c04ef2019-04-03 10:08:11 +053012
Ed Tanousc94ad492019-10-10 15:39:33 -070013#include "logging.h"
Ed Tanous51dae672018-09-05 16:07:32 -070014#include "utility.h"
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010015
Ed Tanous1abe55e2018-09-05 08:30:59 -070016namespace crow
17{
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010018
Ed Tanous1abe55e2018-09-05 08:30:59 -070019namespace persistent_data
20{
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010021
Ed Tanous51dae672018-09-05 16:07:32 -070022// entropy: 20 characters, 62 possibilities. log2(62^20) = 119 bits of
23// entropy. OWASP recommends at least 64
24// https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#session-id-entropy
25constexpr std::size_t sessionTokenSize = 20;
26
Ed Tanous1abe55e2018-09-05 08:30:59 -070027enum class PersistenceType
28{
29 TIMEOUT, // User session times out after a predetermined amount of time
30 SINGLE_REQUEST // User times out once this request is completed.
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010031};
32
Ed Tanous1abe55e2018-09-05 08:30:59 -070033struct UserSession
34{
35 std::string uniqueId;
36 std::string sessionToken;
37 std::string username;
38 std::string csrfToken;
39 std::chrono::time_point<std::chrono::steady_clock> lastUpdated;
40 PersistenceType persistence;
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +010041
Ed Tanous1abe55e2018-09-05 08:30:59 -070042 /**
43 * @brief Fills object with data from UserSession's JSON representation
44 *
45 * This replaces nlohmann's from_json to ensure no-throw approach
46 *
47 * @param[in] j JSON object from which data should be loaded
48 *
49 * @return a shared pointer if data has been loaded properly, nullptr
50 * otherwise
51 */
52 static std::shared_ptr<UserSession> fromJson(const nlohmann::json& j)
53 {
54 std::shared_ptr<UserSession> userSession =
55 std::make_shared<UserSession>();
56 for (const auto& element : j.items())
57 {
58 const std::string* thisValue =
59 element.value().get_ptr<const std::string*>();
60 if (thisValue == nullptr)
61 {
62 BMCWEB_LOG_ERROR << "Error reading persistent store. Property "
63 << element.key() << " was not of type string";
64 return nullptr;
65 }
66 if (element.key() == "unique_id")
67 {
68 userSession->uniqueId = *thisValue;
69 }
70 else if (element.key() == "session_token")
71 {
72 userSession->sessionToken = *thisValue;
73 }
74 else if (element.key() == "csrf_token")
75 {
76 userSession->csrfToken = *thisValue;
77 }
78 else if (element.key() == "username")
79 {
80 userSession->username = *thisValue;
81 }
82 else
83 {
84 BMCWEB_LOG_ERROR
85 << "Got unexpected property reading persistent file: "
86 << element.key();
87 return nullptr;
88 }
89 }
90
91 // For now, sessions that were persisted through a reboot get their idle
92 // timer reset. This could probably be overcome with a better
93 // understanding of wall clock time and steady timer time, possibly
94 // persisting values with wall clock time instead of steady timer, but
95 // the tradeoffs of all the corner cases involved are non-trivial, so
96 // this is done temporarily
97 userSession->lastUpdated = std::chrono::steady_clock::now();
98 userSession->persistence = PersistenceType::TIMEOUT;
99
100 return userSession;
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +0100101 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100102};
103
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100104struct AuthConfigMethods
105{
106 bool xtoken = true;
107 bool cookie = true;
108 bool sessionToken = true;
109 bool basic = true;
Zbigniew Kurzynski501f1e52019-10-02 11:22:11 +0200110 bool tls = true;
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100111
112 void fromJson(const nlohmann::json& j)
113 {
114 for (const auto& element : j.items())
115 {
116 const bool* value = element.value().get_ptr<const bool*>();
117 if (value == nullptr)
118 {
119 continue;
120 }
121
122 if (element.key() == "XToken")
123 {
124 xtoken = *value;
125 }
126 else if (element.key() == "Cookie")
127 {
128 cookie = *value;
129 }
130 else if (element.key() == "SessionToken")
131 {
132 sessionToken = *value;
133 }
134 else if (element.key() == "BasicAuth")
135 {
136 basic = *value;
137 }
Zbigniew Kurzynski501f1e52019-10-02 11:22:11 +0200138 else if (element.key() == "TLS")
139 {
140 tls = *value;
141 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100142 }
143 }
144};
145
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100146class Middleware;
147
Ed Tanous1abe55e2018-09-05 08:30:59 -0700148class SessionStore
149{
150 public:
151 std::shared_ptr<UserSession> generateUserSession(
Ed Tanous39e77502019-03-04 17:35:53 -0800152 const std::string_view username,
Ed Tanous1abe55e2018-09-05 08:30:59 -0700153 PersistenceType persistence = PersistenceType::TIMEOUT)
154 {
155 // TODO(ed) find a secure way to not generate session identifiers if
156 // persistence is set to SINGLE_REQUEST
157 static constexpr std::array<char, 62> alphanum = {
Joseph Reynolds368b1d42019-08-15 15:29:06 -0500158 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
159 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
160 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
Ed Tanous1abe55e2018-09-05 08:30:59 -0700161 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
162 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100163
Ed Tanous1abe55e2018-09-05 08:30:59 -0700164 std::string sessionToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700165 sessionToken.resize(sessionTokenSize, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700166 std::uniform_int_distribution<size_t> dist(0, alphanum.size() - 1);
167 for (size_t i = 0; i < sessionToken.size(); ++i)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700168 {
169 sessionToken[i] = alphanum[dist(rd)];
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100170 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700171 // Only need csrf tokens for cookie based auth, token doesn't matter
172 std::string csrfToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700173 csrfToken.resize(sessionTokenSize, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700174 for (size_t i = 0; i < csrfToken.size(); ++i)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700175 {
176 csrfToken[i] = alphanum[dist(rd)];
177 }
178
179 std::string uniqueId;
180 uniqueId.resize(10, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700181 for (size_t i = 0; i < uniqueId.size(); ++i)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700182 {
183 uniqueId[i] = alphanum[dist(rd)];
184 }
Ratan Gupta12c04ef2019-04-03 10:08:11 +0530185
Ed Tanous1abe55e2018-09-05 08:30:59 -0700186 auto session = std::make_shared<UserSession>(UserSession{
Ed Tanousca0c93b2019-09-19 11:53:50 -0700187 uniqueId, sessionToken, std::string(username), csrfToken,
Ed Tanous1abe55e2018-09-05 08:30:59 -0700188 std::chrono::steady_clock::now(), persistence});
189 auto it = authTokens.emplace(std::make_pair(sessionToken, session));
190 // Only need to write to disk if session isn't about to be destroyed.
191 needWrite = persistence == PersistenceType::TIMEOUT;
192 return it.first->second;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100193 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700194
195 std::shared_ptr<UserSession>
Ed Tanous39e77502019-03-04 17:35:53 -0800196 loginSessionByToken(const std::string_view token)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700197 {
198 applySessionTimeouts();
Ed Tanous51dae672018-09-05 16:07:32 -0700199 if (token.size() != sessionTokenSize)
200 {
201 return nullptr;
202 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700203 auto sessionIt = authTokens.find(std::string(token));
204 if (sessionIt == authTokens.end())
205 {
206 return nullptr;
207 }
208 std::shared_ptr<UserSession> userSession = sessionIt->second;
209 userSession->lastUpdated = std::chrono::steady_clock::now();
210 return userSession;
211 }
212
Ed Tanous39e77502019-03-04 17:35:53 -0800213 std::shared_ptr<UserSession> getSessionByUid(const std::string_view uid)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700214 {
215 applySessionTimeouts();
216 // TODO(Ed) this is inefficient
217 auto sessionIt = authTokens.begin();
218 while (sessionIt != authTokens.end())
219 {
220 if (sessionIt->second->uniqueId == uid)
221 {
222 return sessionIt->second;
223 }
224 sessionIt++;
225 }
226 return nullptr;
227 }
228
229 void removeSession(std::shared_ptr<UserSession> session)
230 {
231 authTokens.erase(session->sessionToken);
232 needWrite = true;
233 }
234
235 std::vector<const std::string*> getUniqueIds(
236 bool getAll = true,
237 const PersistenceType& type = PersistenceType::SINGLE_REQUEST)
238 {
239 applySessionTimeouts();
240
241 std::vector<const std::string*> ret;
242 ret.reserve(authTokens.size());
243 for (auto& session : authTokens)
244 {
245 if (getAll || type == session.second->persistence)
246 {
247 ret.push_back(&session.second->uniqueId);
248 }
249 }
250 return ret;
251 }
252
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100253 void updateAuthMethodsConfig(const AuthConfigMethods& config)
254 {
255 authMethodsConfig = config;
256 needWrite = true;
257 }
258
259 AuthConfigMethods& getAuthMethodsConfig()
260 {
261 return authMethodsConfig;
262 }
263
Ed Tanous1abe55e2018-09-05 08:30:59 -0700264 bool needsWrite()
265 {
266 return needWrite;
267 }
Ed Tanous271584a2019-07-09 16:24:22 -0700268 int64_t getTimeoutInSeconds() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700269 {
270 return std::chrono::seconds(timeoutInMinutes).count();
271 };
272
273 // Persistent data middleware needs to be able to serialize our authTokens
274 // structure, which is private
275 friend Middleware;
276
277 static SessionStore& getInstance()
278 {
279 static SessionStore sessionStore;
280 return sessionStore;
281 }
282
283 SessionStore(const SessionStore&) = delete;
284 SessionStore& operator=(const SessionStore&) = delete;
285
286 private:
287 SessionStore() : timeoutInMinutes(60)
288 {
289 }
290
291 void applySessionTimeouts()
292 {
293 auto timeNow = std::chrono::steady_clock::now();
294 if (timeNow - lastTimeoutUpdate > std::chrono::minutes(1))
295 {
296 lastTimeoutUpdate = timeNow;
297 auto authTokensIt = authTokens.begin();
298 while (authTokensIt != authTokens.end())
299 {
300 if (timeNow - authTokensIt->second->lastUpdated >=
301 timeoutInMinutes)
302 {
303 authTokensIt = authTokens.erase(authTokensIt);
304 needWrite = true;
305 }
306 else
307 {
308 authTokensIt++;
309 }
310 }
311 }
312 }
Ratan Gupta12c04ef2019-04-03 10:08:11 +0530313
Ed Tanous1abe55e2018-09-05 08:30:59 -0700314 std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate;
Ed Tanous51dae672018-09-05 16:07:32 -0700315 std::unordered_map<std::string, std::shared_ptr<UserSession>,
316 std::hash<std::string>,
317 crow::utility::ConstantTimeCompare>
Ed Tanous1abe55e2018-09-05 08:30:59 -0700318 authTokens;
319 std::random_device rd;
320 bool needWrite{false};
321 std::chrono::minutes timeoutInMinutes;
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100322 AuthConfigMethods authMethodsConfig;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100323};
324
Ed Tanous1abe55e2018-09-05 08:30:59 -0700325} // namespace persistent_data
326} // namespace crow
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200327
328// to_json(...) definition for objects of UserSession type
Ed Tanous1abe55e2018-09-05 08:30:59 -0700329namespace nlohmann
330{
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200331template <>
Ed Tanous1abe55e2018-09-05 08:30:59 -0700332struct adl_serializer<std::shared_ptr<crow::persistent_data::UserSession>>
333{
334 static void
335 to_json(nlohmann::json& j,
336 const std::shared_ptr<crow::persistent_data::UserSession>& p)
337 {
338 if (p->persistence !=
339 crow::persistent_data::PersistenceType::SINGLE_REQUEST)
340 {
341 j = nlohmann::json{{"unique_id", p->uniqueId},
342 {"session_token", p->sessionToken},
343 {"username", p->username},
344 {"csrf_token", p->csrfToken}};
345 }
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200346 }
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200347};
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100348
349template <> struct adl_serializer<crow::persistent_data::AuthConfigMethods>
350{
351 static void to_json(nlohmann::json& j,
352 const crow::persistent_data::AuthConfigMethods& c)
353 {
354 j = nlohmann::json{{"XToken", c.xtoken},
355 {"Cookie", c.cookie},
356 {"SessionToken", c.sessionToken},
Zbigniew Kurzynski501f1e52019-10-02 11:22:11 +0200357 {"BasicAuth", c.basic},
358 {"TLS", c.tls}};
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100359 }
360};
Ed Tanous1abe55e2018-09-05 08:30:59 -0700361} // namespace nlohmann