blob: f7f937df07afa943fa78d1df5f110c162c49cacd [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 Tanouse1ae5332018-07-09 15:21:27 -070039 * @return a shared pointer if data has been loaded properly, nullptr otherwise
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +010040 */
Ed Tanouse1ae5332018-07-09 15:21:27 -070041 static std::shared_ptr<UserSession> fromJson(const nlohmann::json& j) {
42 std::shared_ptr<UserSession> userSession = std::make_shared<UserSession>();
43 for (const auto& element : j.items()) {
44 const std::string* thisValue =
45 element.value().get_ptr<const std::string*>();
46 if (thisValue == nullptr) {
Ed Tanous55c7b7a2018-05-22 15:27:24 -070047 BMCWEB_LOG_ERROR << "Error reading persistent store. Property "
Ed Tanouse1ae5332018-07-09 15:21:27 -070048 << element.key() << " was not of type string";
49 return nullptr;
50 }
51 if (element.key() == "unique_id") {
Ed Tanous55c7b7a2018-05-22 15:27:24 -070052 userSession->uniqueId = *thisValue;
Ed Tanouse1ae5332018-07-09 15:21:27 -070053 } else if (element.key() == "session_token") {
Ed Tanous55c7b7a2018-05-22 15:27:24 -070054 userSession->sessionToken = *thisValue;
Ed Tanouse1ae5332018-07-09 15:21:27 -070055 } else if (element.key() == "csrf_token") {
Ed Tanous55c7b7a2018-05-22 15:27:24 -070056 userSession->csrfToken = *thisValue;
Ed Tanouse1ae5332018-07-09 15:21:27 -070057 } else if (element.key() == "username") {
58 userSession->username = *thisValue;
59 } else {
Ed Tanous55c7b7a2018-05-22 15:27:24 -070060 BMCWEB_LOG_ERROR << "Got unexpected property reading persistent file: "
Ed Tanouse1ae5332018-07-09 15:21:27 -070061 << element.key();
62 return nullptr;
63 }
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +010064 }
65
Ed Tanouse1ae5332018-07-09 15:21:27 -070066 // For now, sessions that were persisted through a reboot get their idle
67 // timer reset. This could probably be overcome with a better understanding
68 // of wall clock time and steady timer time, possibly persisting values with
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +010069 // wall clock time instead of steady timer, but the tradeoffs of all the
70 // corner cases involved are non-trivial, so this is done temporarily
Ed Tanous55c7b7a2018-05-22 15:27:24 -070071 userSession->lastUpdated = std::chrono::steady_clock::now();
Ed Tanouse1ae5332018-07-09 15:21:27 -070072 userSession->persistence = PersistenceType::TIMEOUT;
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +010073
Ed Tanouse1ae5332018-07-09 15:21:27 -070074 return userSession;
Kowalski, Kamil5cef0f72018-02-15 15:26:51 +010075 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010076};
77
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010078class Middleware;
79
80class SessionStore {
81 public:
Ed Tanous55c7b7a2018-05-22 15:27:24 -070082 std::shared_ptr<UserSession> generateUserSession(
Ed Tanouse0d918b2018-03-27 17:41:04 -070083 const boost::string_view username,
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010084 PersistenceType persistence = PersistenceType::TIMEOUT) {
85 // TODO(ed) find a secure way to not generate session identifiers if
86 // persistence is set to SINGLE_REQUEST
87 static constexpr std::array<char, 62> alphanum = {
Ed Tanous55c7b7a2018-05-22 15:27:24 -070088 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'b', 'C',
89 'D', 'E', 'F', 'g', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
90 'Q', 'r', 'S', 'T', 'U', 'v', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010091 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
92 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
93
94 // entropy: 30 characters, 62 possibilities. log2(62^30) = 178 bits of
95 // entropy. OWASP recommends at least 60
96 // https://www.owasp.org/index.php/Session_Management_Cheat_Sheet#Session_ID_Entropy
Ed Tanous55c7b7a2018-05-22 15:27:24 -070097 std::string sessionToken;
98 sessionToken.resize(20, '0');
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010099 std::uniform_int_distribution<int> dist(0, alphanum.size() - 1);
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700100 for (int i = 0; i < sessionToken.size(); ++i) {
101 sessionToken[i] = alphanum[dist(rd)];
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100102 }
103 // Only need csrf tokens for cookie based auth, token doesn't matter
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700104 std::string csrfToken;
105 csrfToken.resize(20, '0');
106 for (int i = 0; i < csrfToken.size(); ++i) {
107 csrfToken[i] = alphanum[dist(rd)];
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100108 }
109
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700110 std::string uniqueId;
111 uniqueId.resize(10, '0');
112 for (int i = 0; i < uniqueId.size(); ++i) {
113 uniqueId[i] = alphanum[dist(rd)];
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100114 }
Ed Tanouse0d918b2018-03-27 17:41:04 -0700115 auto session = std::make_shared<UserSession>(
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700116 UserSession{uniqueId, sessionToken, std::string(username), csrfToken,
Ed Tanouse0d918b2018-03-27 17:41:04 -0700117 std::chrono::steady_clock::now(), persistence});
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700118 auto it = authTokens.emplace(std::make_pair(sessionToken, session));
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100119 // Only need to write to disk if session isn't about to be destroyed.
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700120 needWrite = persistence == PersistenceType::TIMEOUT;
Ed Tanouse0d918b2018-03-27 17:41:04 -0700121 return it.first->second;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100122 }
123
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700124 std::shared_ptr<UserSession> loginSessionByToken(
Ed Tanouse0d918b2018-03-27 17:41:04 -0700125 const boost::string_view token) {
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700126 applySessionTimeouts();
127 auto sessionIt = authTokens.find(std::string(token));
128 if (sessionIt == authTokens.end()) {
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100129 return nullptr;
130 }
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700131 std::shared_ptr<UserSession> userSession = sessionIt->second;
132 userSession->lastUpdated = std::chrono::steady_clock::now();
133 return userSession;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100134 }
135
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700136 std::shared_ptr<UserSession> getSessionByUid(const boost::string_view uid) {
137 applySessionTimeouts();
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100138 // TODO(Ed) this is inefficient
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700139 auto sessionIt = authTokens.begin();
140 while (sessionIt != authTokens.end()) {
141 if (sessionIt->second->uniqueId == uid) {
142 return sessionIt->second;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100143 }
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700144 sessionIt++;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100145 }
146 return nullptr;
147 }
148
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700149 void removeSession(std::shared_ptr<UserSession> session) {
150 authTokens.erase(session->sessionToken);
151 needWrite = true;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100152 }
153
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700154 std::vector<const std::string*> getUniqueIds(
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100155 bool getAll = true,
156 const PersistenceType& type = PersistenceType::SINGLE_REQUEST) {
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700157 applySessionTimeouts();
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100158
159 std::vector<const std::string*> ret;
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700160 ret.reserve(authTokens.size());
161 for (auto& session : authTokens) {
Ed Tanouse0d918b2018-03-27 17:41:04 -0700162 if (getAll || type == session.second->persistence) {
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700163 ret.push_back(&session.second->uniqueId);
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100164 }
165 }
166 return ret;
167 }
168
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700169 bool needsWrite() { return needWrite; }
170 int getTimeoutInSeconds() const {
171 return std::chrono::seconds(timeoutInMinutes).count();
Borawski.Lukasz5d27b852018-02-08 13:24:24 +0100172 };
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100173
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700174 // Persistent data middleware needs to be able to serialize our authTokens
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100175 // structure, which is private
176 friend Middleware;
177
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200178 static SessionStore& getInstance() {
179 static SessionStore sessionStore;
180 return sessionStore;
181 }
182
183 SessionStore(const SessionStore&) = delete;
184 SessionStore& operator=(const SessionStore&) = delete;
185
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100186 private:
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700187 SessionStore() : timeoutInMinutes(60) {}
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200188
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700189 void applySessionTimeouts() {
190 auto timeNow = std::chrono::steady_clock::now();
191 if (timeNow - lastTimeoutUpdate > std::chrono::minutes(1)) {
192 lastTimeoutUpdate = timeNow;
193 auto authTokensIt = authTokens.begin();
194 while (authTokensIt != authTokens.end()) {
195 if (timeNow - authTokensIt->second->lastUpdated >= timeoutInMinutes) {
196 authTokensIt = authTokens.erase(authTokensIt);
197 needWrite = true;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100198 } else {
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700199 authTokensIt++;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100200 }
201 }
202 }
203 }
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700204 std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate;
Ed Tanouse0d918b2018-03-27 17:41:04 -0700205 boost::container::flat_map<std::string, std::shared_ptr<UserSession>>
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700206 authTokens;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100207 std::random_device rd;
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700208 bool needWrite{false};
209 std::chrono::minutes timeoutInMinutes;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100210};
211
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700212} // namespace persistent_data
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100213} // namespace crow
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200214
215// to_json(...) definition for objects of UserSession type
216namespace nlohmann {
217template <>
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700218struct adl_serializer<std::shared_ptr<crow::persistent_data::UserSession>> {
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200219 static void to_json(
220 nlohmann::json& j,
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700221 const std::shared_ptr<crow::persistent_data::UserSession>& p) {
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200222 if (p->persistence !=
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700223 crow::persistent_data::PersistenceType::SINGLE_REQUEST) {
224 j = nlohmann::json{{"unique_id", p->uniqueId},
225 {"session_token", p->sessionToken},
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200226 {"username", p->username},
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700227 {"csrf_token", p->csrfToken}};
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200228 }
229 }
230};
231} // namespace nlohmann