blob: 8ff903a43936ebb76b81f011ab57403e42e6d4fe [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;
James Feistf8aa3d22020-04-08 18:32:33 -070042 bool cookieAuth = false;
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +010043
Ed Tanous1abe55e2018-09-05 08:30:59 -070044 /**
45 * @brief Fills object with data from UserSession's JSON representation
46 *
47 * This replaces nlohmann's from_json to ensure no-throw approach
48 *
49 * @param[in] j JSON object from which data should be loaded
50 *
51 * @return a shared pointer if data has been loaded properly, nullptr
52 * otherwise
53 */
54 static std::shared_ptr<UserSession> fromJson(const nlohmann::json& j)
55 {
56 std::shared_ptr<UserSession> userSession =
57 std::make_shared<UserSession>();
58 for (const auto& element : j.items())
59 {
60 const std::string* thisValue =
61 element.value().get_ptr<const std::string*>();
62 if (thisValue == nullptr)
63 {
64 BMCWEB_LOG_ERROR << "Error reading persistent store. Property "
65 << element.key() << " was not of type string";
66 return nullptr;
67 }
68 if (element.key() == "unique_id")
69 {
70 userSession->uniqueId = *thisValue;
71 }
72 else if (element.key() == "session_token")
73 {
74 userSession->sessionToken = *thisValue;
75 }
76 else if (element.key() == "csrf_token")
77 {
78 userSession->csrfToken = *thisValue;
79 }
80 else if (element.key() == "username")
81 {
82 userSession->username = *thisValue;
83 }
84 else
85 {
86 BMCWEB_LOG_ERROR
87 << "Got unexpected property reading persistent file: "
88 << element.key();
89 return nullptr;
90 }
91 }
92
93 // For now, sessions that were persisted through a reboot get their idle
94 // timer reset. This could probably be overcome with a better
95 // understanding of wall clock time and steady timer time, possibly
96 // persisting values with wall clock time instead of steady timer, but
97 // the tradeoffs of all the corner cases involved are non-trivial, so
98 // this is done temporarily
99 userSession->lastUpdated = std::chrono::steady_clock::now();
100 userSession->persistence = PersistenceType::TIMEOUT;
101
102 return userSession;
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +0100103 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100104};
105
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100106struct AuthConfigMethods
107{
108 bool xtoken = true;
109 bool cookie = true;
110 bool sessionToken = true;
111 bool basic = true;
Zbigniew Kurzynskicac94c52019-11-07 12:55:04 +0100112 bool tls = false;
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100113
114 void fromJson(const nlohmann::json& j)
115 {
116 for (const auto& element : j.items())
117 {
118 const bool* value = element.value().get_ptr<const bool*>();
119 if (value == nullptr)
120 {
121 continue;
122 }
123
124 if (element.key() == "XToken")
125 {
126 xtoken = *value;
127 }
128 else if (element.key() == "Cookie")
129 {
130 cookie = *value;
131 }
132 else if (element.key() == "SessionToken")
133 {
134 sessionToken = *value;
135 }
136 else if (element.key() == "BasicAuth")
137 {
138 basic = *value;
139 }
Zbigniew Kurzynski501f1e52019-10-02 11:22:11 +0200140 else if (element.key() == "TLS")
141 {
142 tls = *value;
143 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100144 }
145 }
146};
147
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100148class Middleware;
149
Ed Tanous1abe55e2018-09-05 08:30:59 -0700150class SessionStore
151{
152 public:
153 std::shared_ptr<UserSession> generateUserSession(
Ed Tanous39e77502019-03-04 17:35:53 -0800154 const std::string_view username,
Ed Tanous1abe55e2018-09-05 08:30:59 -0700155 PersistenceType persistence = PersistenceType::TIMEOUT)
156 {
157 // TODO(ed) find a secure way to not generate session identifiers if
158 // persistence is set to SINGLE_REQUEST
159 static constexpr std::array<char, 62> alphanum = {
Joseph Reynolds368b1d42019-08-15 15:29:06 -0500160 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
161 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
162 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
Ed Tanous1abe55e2018-09-05 08:30:59 -0700163 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
164 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100165
Ed Tanous1abe55e2018-09-05 08:30:59 -0700166 std::string sessionToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700167 sessionToken.resize(sessionTokenSize, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700168 std::uniform_int_distribution<size_t> dist(0, alphanum.size() - 1);
169 for (size_t i = 0; i < sessionToken.size(); ++i)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700170 {
171 sessionToken[i] = alphanum[dist(rd)];
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100172 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700173 // Only need csrf tokens for cookie based auth, token doesn't matter
174 std::string csrfToken;
Ed Tanous51dae672018-09-05 16:07:32 -0700175 csrfToken.resize(sessionTokenSize, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700176 for (size_t i = 0; i < csrfToken.size(); ++i)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700177 {
178 csrfToken[i] = alphanum[dist(rd)];
179 }
180
181 std::string uniqueId;
182 uniqueId.resize(10, '0');
Ed Tanous271584a2019-07-09 16:24:22 -0700183 for (size_t i = 0; i < uniqueId.size(); ++i)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700184 {
185 uniqueId[i] = alphanum[dist(rd)];
186 }
Ratan Gupta12c04ef2019-04-03 10:08:11 +0530187
Ed Tanous1abe55e2018-09-05 08:30:59 -0700188 auto session = std::make_shared<UserSession>(UserSession{
Ed Tanousca0c93b2019-09-19 11:53:50 -0700189 uniqueId, sessionToken, std::string(username), csrfToken,
Ed Tanous1abe55e2018-09-05 08:30:59 -0700190 std::chrono::steady_clock::now(), persistence});
191 auto it = authTokens.emplace(std::make_pair(sessionToken, session));
192 // Only need to write to disk if session isn't about to be destroyed.
193 needWrite = persistence == PersistenceType::TIMEOUT;
194 return it.first->second;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100195 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700196
197 std::shared_ptr<UserSession>
Ed Tanous39e77502019-03-04 17:35:53 -0800198 loginSessionByToken(const std::string_view token)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700199 {
200 applySessionTimeouts();
Ed Tanous51dae672018-09-05 16:07:32 -0700201 if (token.size() != sessionTokenSize)
202 {
203 return nullptr;
204 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700205 auto sessionIt = authTokens.find(std::string(token));
206 if (sessionIt == authTokens.end())
207 {
208 return nullptr;
209 }
210 std::shared_ptr<UserSession> userSession = sessionIt->second;
211 userSession->lastUpdated = std::chrono::steady_clock::now();
212 return userSession;
213 }
214
Ed Tanous39e77502019-03-04 17:35:53 -0800215 std::shared_ptr<UserSession> getSessionByUid(const std::string_view uid)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700216 {
217 applySessionTimeouts();
218 // TODO(Ed) this is inefficient
219 auto sessionIt = authTokens.begin();
220 while (sessionIt != authTokens.end())
221 {
222 if (sessionIt->second->uniqueId == uid)
223 {
224 return sessionIt->second;
225 }
226 sessionIt++;
227 }
228 return nullptr;
229 }
230
231 void removeSession(std::shared_ptr<UserSession> session)
232 {
233 authTokens.erase(session->sessionToken);
234 needWrite = true;
235 }
236
237 std::vector<const std::string*> getUniqueIds(
238 bool getAll = true,
239 const PersistenceType& type = PersistenceType::SINGLE_REQUEST)
240 {
241 applySessionTimeouts();
242
243 std::vector<const std::string*> ret;
244 ret.reserve(authTokens.size());
245 for (auto& session : authTokens)
246 {
247 if (getAll || type == session.second->persistence)
248 {
249 ret.push_back(&session.second->uniqueId);
250 }
251 }
252 return ret;
253 }
254
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100255 void updateAuthMethodsConfig(const AuthConfigMethods& config)
256 {
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100257 bool isTLSchanged = (authMethodsConfig.tls != config.tls);
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100258 authMethodsConfig = config;
259 needWrite = true;
Zbigniew Kurzynski009c2a42019-11-14 13:37:15 +0100260 if (isTLSchanged)
261 {
262 // recreate socket connections with new settings
263 std::raise(SIGHUP);
264 }
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100265 }
266
267 AuthConfigMethods& getAuthMethodsConfig()
268 {
269 return authMethodsConfig;
270 }
271
Ed Tanous1abe55e2018-09-05 08:30:59 -0700272 bool needsWrite()
273 {
274 return needWrite;
275 }
Ed Tanous271584a2019-07-09 16:24:22 -0700276 int64_t getTimeoutInSeconds() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700277 {
278 return std::chrono::seconds(timeoutInMinutes).count();
279 };
280
281 // Persistent data middleware needs to be able to serialize our authTokens
282 // structure, which is private
283 friend Middleware;
284
285 static SessionStore& getInstance()
286 {
287 static SessionStore sessionStore;
288 return sessionStore;
289 }
290
291 SessionStore(const SessionStore&) = delete;
292 SessionStore& operator=(const SessionStore&) = delete;
293
294 private:
295 SessionStore() : timeoutInMinutes(60)
296 {
297 }
298
299 void applySessionTimeouts()
300 {
301 auto timeNow = std::chrono::steady_clock::now();
302 if (timeNow - lastTimeoutUpdate > std::chrono::minutes(1))
303 {
304 lastTimeoutUpdate = timeNow;
305 auto authTokensIt = authTokens.begin();
306 while (authTokensIt != authTokens.end())
307 {
308 if (timeNow - authTokensIt->second->lastUpdated >=
309 timeoutInMinutes)
310 {
311 authTokensIt = authTokens.erase(authTokensIt);
312 needWrite = true;
313 }
314 else
315 {
316 authTokensIt++;
317 }
318 }
319 }
320 }
Ratan Gupta12c04ef2019-04-03 10:08:11 +0530321
Ed Tanous1abe55e2018-09-05 08:30:59 -0700322 std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate;
Ed Tanous51dae672018-09-05 16:07:32 -0700323 std::unordered_map<std::string, std::shared_ptr<UserSession>,
324 std::hash<std::string>,
325 crow::utility::ConstantTimeCompare>
Ed Tanous1abe55e2018-09-05 08:30:59 -0700326 authTokens;
327 std::random_device rd;
328 bool needWrite{false};
329 std::chrono::minutes timeoutInMinutes;
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100330 AuthConfigMethods authMethodsConfig;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100331};
332
Ed Tanous1abe55e2018-09-05 08:30:59 -0700333} // namespace persistent_data
334} // namespace crow
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200335
336// to_json(...) definition for objects of UserSession type
Ed Tanous1abe55e2018-09-05 08:30:59 -0700337namespace nlohmann
338{
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200339template <>
Ed Tanous1abe55e2018-09-05 08:30:59 -0700340struct adl_serializer<std::shared_ptr<crow::persistent_data::UserSession>>
341{
342 static void
343 to_json(nlohmann::json& j,
344 const std::shared_ptr<crow::persistent_data::UserSession>& p)
345 {
346 if (p->persistence !=
347 crow::persistent_data::PersistenceType::SINGLE_REQUEST)
348 {
349 j = nlohmann::json{{"unique_id", p->uniqueId},
350 {"session_token", p->sessionToken},
351 {"username", p->username},
352 {"csrf_token", p->csrfToken}};
353 }
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200354 }
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200355};
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100356
357template <> struct adl_serializer<crow::persistent_data::AuthConfigMethods>
358{
359 static void to_json(nlohmann::json& j,
360 const crow::persistent_data::AuthConfigMethods& c)
361 {
362 j = nlohmann::json{{"XToken", c.xtoken},
363 {"Cookie", c.cookie},
364 {"SessionToken", c.sessionToken},
Zbigniew Kurzynski501f1e52019-10-02 11:22:11 +0200365 {"BasicAuth", c.basic},
366 {"TLS", c.tls}};
Zbigniew Kurzynski78158632019-11-05 12:57:37 +0100367 }
368};
Ed Tanous1abe55e2018-09-05 08:30:59 -0700369} // namespace nlohmann