blob: 41447057769ede461810a8c5ac626adce675cdbf [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>
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +01007#include <csignal>
Ratan Gupta12c04ef2019-04-03 10:08:11 +05308#include <dbus_singleton.hpp>
Ed Tanous1abe55e2018-09-05 08:30:59 -07009#include <nlohmann/json.hpp>
10#include <pam_authenticate.hpp>
11#include <random>
RAJESWARAN THILLAIGOVINDAN70525172019-07-09 13:15:05 -050012#include <sdbusplus/bus/match.hpp>
Ratan Gupta12c04ef2019-04-03 10:08:11 +053013
Ed Tanousc94ad492019-10-10 15:39:33 -070014#include "logging.h"
Ed Tanous51dae672018-09-05 16:07:32 -070015#include "utility.h"
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010016
Ed Tanous1abe55e2018-09-05 08:30:59 -070017namespace crow
18{
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010019
Ed Tanous1abe55e2018-09-05 08:30:59 -070020namespace persistent_data
21{
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010022
Ed Tanous51dae672018-09-05 16:07:32 -070023// entropy: 20 characters, 62 possibilities. log2(62^20) = 119 bits of
24// entropy. OWASP recommends at least 64
25// https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#session-id-entropy
26constexpr std::size_t sessionTokenSize = 20;
27
Ed Tanous1abe55e2018-09-05 08:30:59 -070028enum class PersistenceType
29{
30 TIMEOUT, // User session times out after a predetermined amount of time
31 SINGLE_REQUEST // User times out once this request is completed.
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010032};
33
Ed Tanous1abe55e2018-09-05 08:30:59 -070034struct UserSession
35{
36 std::string uniqueId;
37 std::string sessionToken;
38 std::string username;
39 std::string csrfToken;
40 std::chrono::time_point<std::chrono::steady_clock> lastUpdated;
41 PersistenceType persistence;
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +010042
Ed Tanous1abe55e2018-09-05 08:30:59 -070043 /**
44 * @brief Fills object with data from UserSession's JSON representation
45 *
46 * This replaces nlohmann's from_json to ensure no-throw approach
47 *
48 * @param[in] j JSON object from which data should be loaded
49 *
50 * @return a shared pointer if data has been loaded properly, nullptr
51 * otherwise
52 */
53 static std::shared_ptr<UserSession> fromJson(const nlohmann::json& j)
54 {
55 std::shared_ptr<UserSession> userSession =
56 std::make_shared<UserSession>();
57 for (const auto& element : j.items())
58 {
59 const std::string* thisValue =
60 element.value().get_ptr<const std::string*>();
61 if (thisValue == nullptr)
62 {
63 BMCWEB_LOG_ERROR << "Error reading persistent store. Property "
64 << element.key() << " was not of type string";
65 return nullptr;
66 }
67 if (element.key() == "unique_id")
68 {
69 userSession->uniqueId = *thisValue;
70 }
71 else if (element.key() == "session_token")
72 {
73 userSession->sessionToken = *thisValue;
74 }
75 else if (element.key() == "csrf_token")
76 {
77 userSession->csrfToken = *thisValue;
78 }
79 else if (element.key() == "username")
80 {
81 userSession->username = *thisValue;
82 }
83 else
84 {
85 BMCWEB_LOG_ERROR
86 << "Got unexpected property reading persistent file: "
87 << element.key();
88 return nullptr;
89 }
90 }
91
92 // For now, sessions that were persisted through a reboot get their idle
93 // timer reset. This could probably be overcome with a better
94 // understanding of wall clock time and steady timer time, possibly
95 // persisting values with wall clock time instead of steady timer, but
96 // the tradeoffs of all the corner cases involved are non-trivial, so
97 // this is done temporarily
98 userSession->lastUpdated = std::chrono::steady_clock::now();
99 userSession->persistence = PersistenceType::TIMEOUT;
100
101 return userSession;
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +0100102 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100103};
104
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100105struct AuthConfigMethods
106{
107 bool xtoken = true;
108 bool cookie = true;
109 bool sessionToken = true;
110 bool basic = true;
Zbigniew Kurzynskicac94c52019-11-07 12:55:04 +0100111 bool tls = false;
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100112
113 void fromJson(const nlohmann::json& j)
114 {
115 for (const auto& element : j.items())
116 {
117 const bool* value = element.value().get_ptr<const bool*>();
118 if (value == nullptr)
119 {
120 continue;
121 }
122
123 if (element.key() == "XToken")
124 {
125 xtoken = *value;
126 }
127 else if (element.key() == "Cookie")
128 {
129 cookie = *value;
130 }
131 else if (element.key() == "SessionToken")
132 {
133 sessionToken = *value;
134 }
135 else if (element.key() == "BasicAuth")
136 {
137 basic = *value;
138 }
Zbigniew Kurzynski501f1e52019-10-02 11:22:11 +0200139 else if (element.key() == "TLS")
140 {
141 tls = *value;
142 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100143 }
144 }
145};
146
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100147class Middleware;
148
Ed Tanous1abe55e2018-09-05 08:30:59 -0700149class SessionStore
150{
151 public:
152 std::shared_ptr<UserSession> generateUserSession(
Ed Tanous39e77502019-03-04 17:35:53 -0800153 const std::string_view username,
Ed Tanous1abe55e2018-09-05 08:30:59 -0700154 PersistenceType persistence = PersistenceType::TIMEOUT)
155 {
156 // TODO(ed) find a secure way to not generate session identifiers if
157 // persistence is set to SINGLE_REQUEST
158 static constexpr std::array<char, 62> alphanum = {
Joseph Reynolds368b1d42019-08-15 15:29:06 -0500159 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
160 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
161 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
Ed Tanous1abe55e2018-09-05 08:30:59 -0700162 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
163 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100164
Ed Tanous1abe55e2018-09-05 08:30:59 -0700165 std::string sessionToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700166 sessionToken.resize(sessionTokenSize, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700167 std::uniform_int_distribution<size_t> dist(0, alphanum.size() - 1);
168 for (size_t i = 0; i < sessionToken.size(); ++i)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700169 {
170 sessionToken[i] = alphanum[dist(rd)];
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100171 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700172 // Only need csrf tokens for cookie based auth, token doesn't matter
173 std::string csrfToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700174 csrfToken.resize(sessionTokenSize, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700175 for (size_t i = 0; i < csrfToken.size(); ++i)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700176 {
177 csrfToken[i] = alphanum[dist(rd)];
178 }
179
180 std::string uniqueId;
181 uniqueId.resize(10, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700182 for (size_t i = 0; i < uniqueId.size(); ++i)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700183 {
184 uniqueId[i] = alphanum[dist(rd)];
185 }
Ratan Gupta12c04ef2019-04-03 10:08:11 +0530186
Ed Tanous1abe55e2018-09-05 08:30:59 -0700187 auto session = std::make_shared<UserSession>(UserSession{
Ed Tanousca0c93b2019-09-19 11:53:50 -0700188 uniqueId, sessionToken, std::string(username), csrfToken,
Ed Tanous1abe55e2018-09-05 08:30:59 -0700189 std::chrono::steady_clock::now(), persistence});
190 auto it = authTokens.emplace(std::make_pair(sessionToken, session));
191 // Only need to write to disk if session isn't about to be destroyed.
192 needWrite = persistence == PersistenceType::TIMEOUT;
193 return it.first->second;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100194 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700195
196 std::shared_ptr<UserSession>
Ed Tanous39e77502019-03-04 17:35:53 -0800197 loginSessionByToken(const std::string_view token)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700198 {
199 applySessionTimeouts();
Ed Tanous51dae672018-09-05 16:07:32 -0700200 if (token.size() != sessionTokenSize)
201 {
202 return nullptr;
203 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700204 auto sessionIt = authTokens.find(std::string(token));
205 if (sessionIt == authTokens.end())
206 {
207 return nullptr;
208 }
209 std::shared_ptr<UserSession> userSession = sessionIt->second;
210 userSession->lastUpdated = std::chrono::steady_clock::now();
211 return userSession;
212 }
213
Ed Tanous39e77502019-03-04 17:35:53 -0800214 std::shared_ptr<UserSession> getSessionByUid(const std::string_view uid)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700215 {
216 applySessionTimeouts();
217 // TODO(Ed) this is inefficient
218 auto sessionIt = authTokens.begin();
219 while (sessionIt != authTokens.end())
220 {
221 if (sessionIt->second->uniqueId == uid)
222 {
223 return sessionIt->second;
224 }
225 sessionIt++;
226 }
227 return nullptr;
228 }
229
230 void removeSession(std::shared_ptr<UserSession> session)
231 {
232 authTokens.erase(session->sessionToken);
233 needWrite = true;
234 }
235
236 std::vector<const std::string*> getUniqueIds(
237 bool getAll = true,
238 const PersistenceType& type = PersistenceType::SINGLE_REQUEST)
239 {
240 applySessionTimeouts();
241
242 std::vector<const std::string*> ret;
243 ret.reserve(authTokens.size());
244 for (auto& session : authTokens)
245 {
246 if (getAll || type == session.second->persistence)
247 {
248 ret.push_back(&session.second->uniqueId);
249 }
250 }
251 return ret;
252 }
253
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100254 void updateAuthMethodsConfig(const AuthConfigMethods& config)
255 {
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100256 bool isTLSchanged = (authMethodsConfig.tls != config.tls);
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100257 authMethodsConfig = config;
258 needWrite = true;
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100259 if (isTLSchanged)
260 {
261 // recreate socket connections with new settings
262 std::raise(SIGHUP);
263 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100264 }
265
266 AuthConfigMethods& getAuthMethodsConfig()
267 {
268 return authMethodsConfig;
269 }
270
Ed Tanous1abe55e2018-09-05 08:30:59 -0700271 bool needsWrite()
272 {
273 return needWrite;
274 }
Ed Tanous271584a2019-07-09 16:24:22 -0700275 int64_t getTimeoutInSeconds() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700276 {
277 return std::chrono::seconds(timeoutInMinutes).count();
278 };
279
280 // Persistent data middleware needs to be able to serialize our authTokens
281 // structure, which is private
282 friend Middleware;
283
284 static SessionStore& getInstance()
285 {
286 static SessionStore sessionStore;
287 return sessionStore;
288 }
289
290 SessionStore(const SessionStore&) = delete;
291 SessionStore& operator=(const SessionStore&) = delete;
292
293 private:
294 SessionStore() : timeoutInMinutes(60)
295 {
296 }
297
298 void applySessionTimeouts()
299 {
300 auto timeNow = std::chrono::steady_clock::now();
301 if (timeNow - lastTimeoutUpdate > std::chrono::minutes(1))
302 {
303 lastTimeoutUpdate = timeNow;
304 auto authTokensIt = authTokens.begin();
305 while (authTokensIt != authTokens.end())
306 {
307 if (timeNow - authTokensIt->second->lastUpdated >=
308 timeoutInMinutes)
309 {
310 authTokensIt = authTokens.erase(authTokensIt);
311 needWrite = true;
312 }
313 else
314 {
315 authTokensIt++;
316 }
317 }
318 }
319 }
Ratan Gupta12c04ef2019-04-03 10:08:11 +0530320
Ed Tanous1abe55e2018-09-05 08:30:59 -0700321 std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate;
Ed Tanous51dae672018-09-05 16:07:32 -0700322 std::unordered_map<std::string, std::shared_ptr<UserSession>,
323 std::hash<std::string>,
324 crow::utility::ConstantTimeCompare>
Ed Tanous1abe55e2018-09-05 08:30:59 -0700325 authTokens;
326 std::random_device rd;
327 bool needWrite{false};
328 std::chrono::minutes timeoutInMinutes;
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100329 AuthConfigMethods authMethodsConfig;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100330};
331
Ed Tanous1abe55e2018-09-05 08:30:59 -0700332} // namespace persistent_data
333} // namespace crow
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200334
335// to_json(...) definition for objects of UserSession type
Ed Tanous1abe55e2018-09-05 08:30:59 -0700336namespace nlohmann
337{
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200338template <>
Ed Tanous1abe55e2018-09-05 08:30:59 -0700339struct adl_serializer<std::shared_ptr<crow::persistent_data::UserSession>>
340{
341 static void
342 to_json(nlohmann::json& j,
343 const std::shared_ptr<crow::persistent_data::UserSession>& p)
344 {
345 if (p->persistence !=
346 crow::persistent_data::PersistenceType::SINGLE_REQUEST)
347 {
348 j = nlohmann::json{{"unique_id", p->uniqueId},
349 {"session_token", p->sessionToken},
350 {"username", p->username},
351 {"csrf_token", p->csrfToken}};
352 }
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200353 }
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200354};
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100355
356template <> struct adl_serializer<crow::persistent_data::AuthConfigMethods>
357{
358 static void to_json(nlohmann::json& j,
359 const crow::persistent_data::AuthConfigMethods& c)
360 {
361 j = nlohmann::json{{"XToken", c.xtoken},
362 {"Cookie", c.cookie},
363 {"SessionToken", c.sessionToken},
Zbigniew Kurzynski501f1e52019-10-02 11:22:11 +0200364 {"BasicAuth", c.basic},
365 {"TLS", c.tls}};
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100366 }
367};
Ed Tanous1abe55e2018-09-05 08:30:59 -0700368} // namespace nlohmann