blob: f549fde696157a36ffa913ea7ff1f95f63bbe370 [file] [log] [blame]
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +01001#pragma once
2
3#include <nlohmann/json.hpp>
4#include <pam_authenticate.hpp>
5#include <webassets.hpp>
6#include <random>
7#include <crow/app.h>
8#include <crow/http_request.h>
9#include <crow/http_response.h>
10#include <boost/container/flat_map.hpp>
11#include <boost/uuid/uuid.hpp>
12#include <boost/uuid/uuid_generators.hpp>
13#include <boost/uuid/uuid_io.hpp>
14
15namespace crow {
16
Ed Tanous55c7b7a2018-05-22 15:27:24 -070017namespace persistent_data {
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010018
19enum class PersistenceType {
20 TIMEOUT, // User session times out after a predetermined amount of time
21 SINGLE_REQUEST // User times out once this request is completed.
22};
23
24struct UserSession {
Ed Tanous55c7b7a2018-05-22 15:27:24 -070025 std::string uniqueId;
26 std::string sessionToken;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010027 std::string username;
Ed Tanous55c7b7a2018-05-22 15:27:24 -070028 std::string csrfToken;
29 std::chrono::time_point<std::chrono::steady_clock> lastUpdated;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010030 PersistenceType persistence;
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +010031
32 /**
33 * @brief Fills object with data from UserSession's JSON representation
34 *
35 * This replaces nlohmann's from_json to ensure no-throw approach
36 *
37 * @param[in] j JSON object from which data should be loaded
38 *
Ed Tanousa434f2b2018-07-27 13:04:22 -070039 * @return a shared pointer if data has been loaded properly, nullptr
40 * otherwise
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +010041 */
Ed Tanouse1ae5332018-07-09 15:21:27 -070042 static std::shared_ptr<UserSession> fromJson(const nlohmann::json& j) {
43 std::shared_ptr<UserSession> userSession = std::make_shared<UserSession>();
44 for (const auto& element : j.items()) {
45 const std::string* thisValue =
46 element.value().get_ptr<const std::string*>();
47 if (thisValue == nullptr) {
Ed Tanous55c7b7a2018-05-22 15:27:24 -070048 BMCWEB_LOG_ERROR << "Error reading persistent store. Property "
Ed Tanousa434f2b2018-07-27 13:04:22 -070049 << element.key() << " was not of type string";
Ed Tanouse1ae5332018-07-09 15:21:27 -070050 return nullptr;
51 }
52 if (element.key() == "unique_id") {
Ed Tanous55c7b7a2018-05-22 15:27:24 -070053 userSession->uniqueId = *thisValue;
Ed Tanouse1ae5332018-07-09 15:21:27 -070054 } else if (element.key() == "session_token") {
Ed Tanous55c7b7a2018-05-22 15:27:24 -070055 userSession->sessionToken = *thisValue;
Ed Tanouse1ae5332018-07-09 15:21:27 -070056 } else if (element.key() == "csrf_token") {
Ed Tanous55c7b7a2018-05-22 15:27:24 -070057 userSession->csrfToken = *thisValue;
Ed Tanouse1ae5332018-07-09 15:21:27 -070058 } else if (element.key() == "username") {
59 userSession->username = *thisValue;
60 } else {
Ed Tanous55c7b7a2018-05-22 15:27:24 -070061 BMCWEB_LOG_ERROR << "Got unexpected property reading persistent file: "
Ed Tanousa434f2b2018-07-27 13:04:22 -070062 << element.key();
Ed Tanouse1ae5332018-07-09 15:21:27 -070063 return nullptr;
64 }
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +010065 }
66
Ed Tanouse1ae5332018-07-09 15:21:27 -070067 // For now, sessions that were persisted through a reboot get their idle
68 // timer reset. This could probably be overcome with a better understanding
69 // of wall clock time and steady timer time, possibly persisting values with
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +010070 // wall clock time instead of steady timer, but the tradeoffs of all the
71 // corner cases involved are non-trivial, so this is done temporarily
Ed Tanous55c7b7a2018-05-22 15:27:24 -070072 userSession->lastUpdated = std::chrono::steady_clock::now();
Ed Tanouse1ae5332018-07-09 15:21:27 -070073 userSession->persistence = PersistenceType::TIMEOUT;
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +010074
Ed Tanouse1ae5332018-07-09 15:21:27 -070075 return userSession;
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +010076 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010077};
78
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010079class Middleware;
80
81class SessionStore {
82 public:
Ed Tanous55c7b7a2018-05-22 15:27:24 -070083 std::shared_ptr<UserSession> generateUserSession(
Ed Tanouse0d918b2018-03-27 17:41:04 -070084 const boost::string_view username,
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010085 PersistenceType persistence = PersistenceType::TIMEOUT) {
86 // TODO(ed) find a secure way to not generate session identifiers if
87 // persistence is set to SINGLE_REQUEST
88 static constexpr std::array<char, 62> alphanum = {
Ed Tanous55c7b7a2018-05-22 15:27:24 -070089 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'b', 'C',
90 'D', 'E', 'F', 'g', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
91 'Q', 'r', 'S', 'T', 'U', 'v', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010092 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
93 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
94
95 // entropy: 30 characters, 62 possibilities. log2(62^30) = 178 bits of
96 // entropy. OWASP recommends at least 60
97 // https://www.owasp.org/index.php/Session_Management_Cheat_Sheet#Session_ID_Entropy
Ed Tanous55c7b7a2018-05-22 15:27:24 -070098 std::string sessionToken;
99 sessionToken.resize(20, '0');
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100100 std::uniform_int_distribution<int> dist(0, alphanum.size() - 1);
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700101 for (int i = 0; i < sessionToken.size(); ++i) {
102 sessionToken[i] = alphanum[dist(rd)];
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100103 }
104 // Only need csrf tokens for cookie based auth, token doesn't matter
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700105 std::string csrfToken;
106 csrfToken.resize(20, '0');
107 for (int i = 0; i < csrfToken.size(); ++i) {
108 csrfToken[i] = alphanum[dist(rd)];
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100109 }
110
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700111 std::string uniqueId;
112 uniqueId.resize(10, '0');
113 for (int i = 0; i < uniqueId.size(); ++i) {
114 uniqueId[i] = alphanum[dist(rd)];
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100115 }
Ed Tanouse0d918b2018-03-27 17:41:04 -0700116 auto session = std::make_shared<UserSession>(
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700117 UserSession{uniqueId, sessionToken, std::string(username), csrfToken,
Ed Tanouse0d918b2018-03-27 17:41:04 -0700118 std::chrono::steady_clock::now(), persistence});
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700119 auto it = authTokens.emplace(std::make_pair(sessionToken, session));
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100120 // Only need to write to disk if session isn't about to be destroyed.
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700121 needWrite = persistence == PersistenceType::TIMEOUT;
Ed Tanouse0d918b2018-03-27 17:41:04 -0700122 return it.first->second;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100123 }
124
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700125 std::shared_ptr<UserSession> loginSessionByToken(
Ed Tanouse0d918b2018-03-27 17:41:04 -0700126 const boost::string_view token) {
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700127 applySessionTimeouts();
128 auto sessionIt = authTokens.find(std::string(token));
129 if (sessionIt == authTokens.end()) {
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100130 return nullptr;
131 }
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700132 std::shared_ptr<UserSession> userSession = sessionIt->second;
133 userSession->lastUpdated = std::chrono::steady_clock::now();
134 return userSession;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100135 }
136
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700137 std::shared_ptr<UserSession> getSessionByUid(const boost::string_view uid) {
138 applySessionTimeouts();
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100139 // TODO(Ed) this is inefficient
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700140 auto sessionIt = authTokens.begin();
141 while (sessionIt != authTokens.end()) {
142 if (sessionIt->second->uniqueId == uid) {
143 return sessionIt->second;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100144 }
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700145 sessionIt++;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100146 }
147 return nullptr;
148 }
149
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700150 void removeSession(std::shared_ptr<UserSession> session) {
151 authTokens.erase(session->sessionToken);
152 needWrite = true;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100153 }
154
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700155 std::vector<const std::string*> getUniqueIds(
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100156 bool getAll = true,
157 const PersistenceType& type = PersistenceType::SINGLE_REQUEST) {
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700158 applySessionTimeouts();
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100159
160 std::vector<const std::string*> ret;
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700161 ret.reserve(authTokens.size());
162 for (auto& session : authTokens) {
Ed Tanouse0d918b2018-03-27 17:41:04 -0700163 if (getAll || type == session.second->persistence) {
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700164 ret.push_back(&session.second->uniqueId);
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100165 }
166 }
167 return ret;
168 }
169
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700170 bool needsWrite() { return needWrite; }
171 int getTimeoutInSeconds() const {
172 return std::chrono::seconds(timeoutInMinutes).count();
Borawski.Lukasz5d27b852018-02-08 13:24:24 +0100173 };
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100174
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700175 // Persistent data middleware needs to be able to serialize our authTokens
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100176 // structure, which is private
177 friend Middleware;
178
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200179 static SessionStore& getInstance() {
180 static SessionStore sessionStore;
181 return sessionStore;
182 }
183
184 SessionStore(const SessionStore&) = delete;
185 SessionStore& operator=(const SessionStore&) = delete;
186
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100187 private:
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700188 SessionStore() : timeoutInMinutes(60) {}
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200189
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700190 void applySessionTimeouts() {
191 auto timeNow = std::chrono::steady_clock::now();
192 if (timeNow - lastTimeoutUpdate > std::chrono::minutes(1)) {
193 lastTimeoutUpdate = timeNow;
194 auto authTokensIt = authTokens.begin();
195 while (authTokensIt != authTokens.end()) {
196 if (timeNow - authTokensIt->second->lastUpdated >= timeoutInMinutes) {
197 authTokensIt = authTokens.erase(authTokensIt);
198 needWrite = true;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100199 } else {
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700200 authTokensIt++;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100201 }
202 }
203 }
204 }
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700205 std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate;
Ed Tanouse0d918b2018-03-27 17:41:04 -0700206 boost::container::flat_map<std::string, std::shared_ptr<UserSession>>
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700207 authTokens;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100208 std::random_device rd;
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700209 bool needWrite{false};
210 std::chrono::minutes timeoutInMinutes;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100211};
212
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700213} // namespace persistent_data
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100214} // namespace crow
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200215
216// to_json(...) definition for objects of UserSession type
217namespace nlohmann {
218template <>
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700219struct adl_serializer<std::shared_ptr<crow::persistent_data::UserSession>> {
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200220 static void to_json(
221 nlohmann::json& j,
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700222 const std::shared_ptr<crow::persistent_data::UserSession>& p) {
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200223 if (p->persistence !=
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700224 crow::persistent_data::PersistenceType::SINGLE_REQUEST) {
225 j = nlohmann::json{{"unique_id", p->uniqueId},
226 {"session_token", p->sessionToken},
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200227 {"username", p->username},
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700228 {"csrf_token", p->csrfToken}};
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200229 }
230 }
231};
232} // namespace nlohmann