blob: 510f566476f65d67fa7151509bce52cfd9915062 [file] [log] [blame]
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +01001#pragma once
2
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +01003#include <crow/app.h>
4#include <crow/http_request.h>
5#include <crow/http_response.h>
Ed Tanous1abe55e2018-09-05 08:30:59 -07006
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +01007#include <boost/container/flat_map.hpp>
8#include <boost/uuid/uuid.hpp>
9#include <boost/uuid/uuid_generators.hpp>
10#include <boost/uuid/uuid_io.hpp>
Ed Tanous1abe55e2018-09-05 08:30:59 -070011#include <nlohmann/json.hpp>
12#include <pam_authenticate.hpp>
13#include <random>
14#include <webassets.hpp>
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 Tanous1abe55e2018-09-05 08:30:59 -070022enum class PersistenceType
23{
24 TIMEOUT, // User session times out after a predetermined amount of time
25 SINGLE_REQUEST // User times out once this request is completed.
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010026};
27
Ed Tanous1abe55e2018-09-05 08:30:59 -070028struct UserSession
29{
30 std::string uniqueId;
31 std::string sessionToken;
32 std::string username;
33 std::string csrfToken;
34 std::chrono::time_point<std::chrono::steady_clock> lastUpdated;
35 PersistenceType persistence;
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +010036
Ed Tanous1abe55e2018-09-05 08:30:59 -070037 /**
38 * @brief Fills object with data from UserSession's JSON representation
39 *
40 * This replaces nlohmann's from_json to ensure no-throw approach
41 *
42 * @param[in] j JSON object from which data should be loaded
43 *
44 * @return a shared pointer if data has been loaded properly, nullptr
45 * otherwise
46 */
47 static std::shared_ptr<UserSession> fromJson(const nlohmann::json& j)
48 {
49 std::shared_ptr<UserSession> userSession =
50 std::make_shared<UserSession>();
51 for (const auto& element : j.items())
52 {
53 const std::string* thisValue =
54 element.value().get_ptr<const std::string*>();
55 if (thisValue == nullptr)
56 {
57 BMCWEB_LOG_ERROR << "Error reading persistent store. Property "
58 << element.key() << " was not of type string";
59 return nullptr;
60 }
61 if (element.key() == "unique_id")
62 {
63 userSession->uniqueId = *thisValue;
64 }
65 else if (element.key() == "session_token")
66 {
67 userSession->sessionToken = *thisValue;
68 }
69 else if (element.key() == "csrf_token")
70 {
71 userSession->csrfToken = *thisValue;
72 }
73 else if (element.key() == "username")
74 {
75 userSession->username = *thisValue;
76 }
77 else
78 {
79 BMCWEB_LOG_ERROR
80 << "Got unexpected property reading persistent file: "
81 << element.key();
82 return nullptr;
83 }
84 }
85
86 // For now, sessions that were persisted through a reboot get their idle
87 // timer reset. This could probably be overcome with a better
88 // understanding of wall clock time and steady timer time, possibly
89 // persisting values with wall clock time instead of steady timer, but
90 // the tradeoffs of all the corner cases involved are non-trivial, so
91 // this is done temporarily
92 userSession->lastUpdated = std::chrono::steady_clock::now();
93 userSession->persistence = PersistenceType::TIMEOUT;
94
95 return userSession;
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +010096 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010097};
98
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010099class Middleware;
100
Ed Tanous1abe55e2018-09-05 08:30:59 -0700101class SessionStore
102{
103 public:
104 std::shared_ptr<UserSession> generateUserSession(
105 const boost::string_view username,
106 PersistenceType persistence = PersistenceType::TIMEOUT)
107 {
108 // TODO(ed) find a secure way to not generate session identifiers if
109 // persistence is set to SINGLE_REQUEST
110 static constexpr std::array<char, 62> alphanum = {
111 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'b', 'C',
112 'D', 'E', 'F', 'g', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
113 'Q', 'r', 'S', 'T', 'U', 'v', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
114 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
115 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100116
Ed Tanous1abe55e2018-09-05 08:30:59 -0700117 // entropy: 30 characters, 62 possibilities. log2(62^30) = 178 bits of
118 // entropy. OWASP recommends at least 60
119 // https://www.owasp.org/index.php/Session_Management_Cheat_Sheet#Session_ID_Entropy
120 std::string sessionToken;
121 sessionToken.resize(20, '0');
122 std::uniform_int_distribution<int> dist(0, alphanum.size() - 1);
123 for (int i = 0; i < sessionToken.size(); ++i)
124 {
125 sessionToken[i] = alphanum[dist(rd)];
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100126 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700127 // Only need csrf tokens for cookie based auth, token doesn't matter
128 std::string csrfToken;
129 csrfToken.resize(20, '0');
130 for (int i = 0; i < csrfToken.size(); ++i)
131 {
132 csrfToken[i] = alphanum[dist(rd)];
133 }
134
135 std::string uniqueId;
136 uniqueId.resize(10, '0');
137 for (int i = 0; i < uniqueId.size(); ++i)
138 {
139 uniqueId[i] = alphanum[dist(rd)];
140 }
141 auto session = std::make_shared<UserSession>(UserSession{
142 uniqueId, sessionToken, std::string(username), csrfToken,
143 std::chrono::steady_clock::now(), persistence});
144 auto it = authTokens.emplace(std::make_pair(sessionToken, session));
145 // Only need to write to disk if session isn't about to be destroyed.
146 needWrite = persistence == PersistenceType::TIMEOUT;
147 return it.first->second;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100148 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700149
150 std::shared_ptr<UserSession>
151 loginSessionByToken(const boost::string_view token)
152 {
153 applySessionTimeouts();
154 auto sessionIt = authTokens.find(std::string(token));
155 if (sessionIt == authTokens.end())
156 {
157 return nullptr;
158 }
159 std::shared_ptr<UserSession> userSession = sessionIt->second;
160 userSession->lastUpdated = std::chrono::steady_clock::now();
161 return userSession;
162 }
163
164 std::shared_ptr<UserSession> getSessionByUid(const boost::string_view uid)
165 {
166 applySessionTimeouts();
167 // TODO(Ed) this is inefficient
168 auto sessionIt = authTokens.begin();
169 while (sessionIt != authTokens.end())
170 {
171 if (sessionIt->second->uniqueId == uid)
172 {
173 return sessionIt->second;
174 }
175 sessionIt++;
176 }
177 return nullptr;
178 }
179
180 void removeSession(std::shared_ptr<UserSession> session)
181 {
182 authTokens.erase(session->sessionToken);
183 needWrite = true;
184 }
185
186 std::vector<const std::string*> getUniqueIds(
187 bool getAll = true,
188 const PersistenceType& type = PersistenceType::SINGLE_REQUEST)
189 {
190 applySessionTimeouts();
191
192 std::vector<const std::string*> ret;
193 ret.reserve(authTokens.size());
194 for (auto& session : authTokens)
195 {
196 if (getAll || type == session.second->persistence)
197 {
198 ret.push_back(&session.second->uniqueId);
199 }
200 }
201 return ret;
202 }
203
204 bool needsWrite()
205 {
206 return needWrite;
207 }
208 int getTimeoutInSeconds() const
209 {
210 return std::chrono::seconds(timeoutInMinutes).count();
211 };
212
213 // Persistent data middleware needs to be able to serialize our authTokens
214 // structure, which is private
215 friend Middleware;
216
217 static SessionStore& getInstance()
218 {
219 static SessionStore sessionStore;
220 return sessionStore;
221 }
222
223 SessionStore(const SessionStore&) = delete;
224 SessionStore& operator=(const SessionStore&) = delete;
225
226 private:
227 SessionStore() : timeoutInMinutes(60)
228 {
229 }
230
231 void applySessionTimeouts()
232 {
233 auto timeNow = std::chrono::steady_clock::now();
234 if (timeNow - lastTimeoutUpdate > std::chrono::minutes(1))
235 {
236 lastTimeoutUpdate = timeNow;
237 auto authTokensIt = authTokens.begin();
238 while (authTokensIt != authTokens.end())
239 {
240 if (timeNow - authTokensIt->second->lastUpdated >=
241 timeoutInMinutes)
242 {
243 authTokensIt = authTokens.erase(authTokensIt);
244 needWrite = true;
245 }
246 else
247 {
248 authTokensIt++;
249 }
250 }
251 }
252 }
253 std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate;
254 boost::container::flat_map<std::string, std::shared_ptr<UserSession>>
255 authTokens;
256 std::random_device rd;
257 bool needWrite{false};
258 std::chrono::minutes timeoutInMinutes;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100259};
260
Ed Tanous1abe55e2018-09-05 08:30:59 -0700261} // namespace persistent_data
262} // namespace crow
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200263
264// to_json(...) definition for objects of UserSession type
Ed Tanous1abe55e2018-09-05 08:30:59 -0700265namespace nlohmann
266{
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200267template <>
Ed Tanous1abe55e2018-09-05 08:30:59 -0700268struct adl_serializer<std::shared_ptr<crow::persistent_data::UserSession>>
269{
270 static void
271 to_json(nlohmann::json& j,
272 const std::shared_ptr<crow::persistent_data::UserSession>& p)
273 {
274 if (p->persistence !=
275 crow::persistent_data::PersistenceType::SINGLE_REQUEST)
276 {
277 j = nlohmann::json{{"unique_id", p->uniqueId},
278 {"session_token", p->sessionToken},
279 {"username", p->username},
280 {"csrf_token", p->csrfToken}};
281 }
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200282 }
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200283};
Ed Tanous1abe55e2018-09-05 08:30:59 -0700284} // namespace nlohmann