blob: dc4ec8c46b72b8256f5432e05ae2456005342d2c [file] [log] [blame]
Ed Tanousba9f9a62017-10-11 16:40:35 -07001#pragma once
2
Ed Tanousba9f9a62017-10-11 16:40:35 -07003#include <nlohmann/json.hpp>
4#include <pam_authenticate.hpp>
5#include <webassets.hpp>
Ed Tanousbabedfe2018-01-12 10:50:26 -08006
7include <random>
Ed Tanousba9f9a62017-10-11 16:40:35 -07008#include <crow/app.h>
9#include <crow/http_request.h>
10#include <crow/http_response.h>
11#include <boost/container/flat_map.hpp>
12#include <boost/uuid/uuid.hpp>
13#include <boost/uuid/uuid_generators.hpp>
14#include <boost/uuid/uuid_io.hpp>
15
16namespace crow {
17
18namespace PersistentData {
19struct UserSession {
20 std::string unique_id;
21 std::string session_token;
22 std::string username;
23 std::string csrf_token;
Ed Tanousc963aa42017-10-27 16:00:19 -070024 std::chrono::time_point<std::chrono::steady_clock> last_updated;
Ed Tanousba9f9a62017-10-11 16:40:35 -070025};
26
27void to_json(nlohmann::json& j, const UserSession& p) {
28 j = nlohmann::json{{"unique_id", p.unique_id},
29 {"session_token", p.session_token},
30 {"username", p.username},
31 {"csrf_token", p.csrf_token}};
32}
33
34void from_json(const nlohmann::json& j, UserSession& p) {
35 try {
36 p.unique_id = j.at("unique_id").get<std::string>();
37 p.session_token = j.at("session_token").get<std::string>();
38 p.username = j.at("username").get<std::string>();
39 p.csrf_token = j.at("csrf_token").get<std::string>();
Ed Tanousc963aa42017-10-27 16:00:19 -070040 // For now, sessions that were persisted through a reboot get their timer
41 // reset. This could probably be overcome with a better understanding of
42 // wall clock time and steady timer time, possibly persisting values with
43 // wall clock time instead of steady timer, but the tradeoffs of all the
44 // corner cases involved are non-trivial, so this is done temporarily
45 p.last_updated = std::chrono::steady_clock::now();
Ed Tanousba9f9a62017-10-11 16:40:35 -070046 } catch (std::out_of_range) {
47 // do nothing. Session API incompatibility, leave sessions empty
48 }
49}
50
Ed Tanousc963aa42017-10-27 16:00:19 -070051class Middleware;
Ed Tanousba9f9a62017-10-11 16:40:35 -070052
Ed Tanousc963aa42017-10-27 16:00:19 -070053class SessionStore {
Ed Tanousba9f9a62017-10-11 16:40:35 -070054 public:
Ed Tanousc963aa42017-10-27 16:00:19 -070055 const UserSession& generate_user_session(const std::string& username) {
Ed Tanousba9f9a62017-10-11 16:40:35 -070056 static constexpr std::array<char, 62> alphanum = {
57 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
58 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
59 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
60 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
61 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
62
63 // entropy: 30 characters, 62 possibilies. log2(62^30) = 178 bits of
64 // entropy. OWASP recommends at least 60
65 // https://www.owasp.org/index.php/Session_Management_Cheat_Sheet#Session_ID_Entropy
66 std::string session_token;
67 session_token.resize(20, '0');
68 std::uniform_int_distribution<int> dist(0, alphanum.size() - 1);
69 for (int i = 0; i < session_token.size(); ++i) {
70 session_token[i] = alphanum[dist(rd)];
71 }
72 // Only need csrf tokens for cookie based auth, token doesn't matter
73 std::string csrf_token;
74 csrf_token.resize(20, '0');
75 for (int i = 0; i < csrf_token.size(); ++i) {
76 csrf_token[i] = alphanum[dist(rd)];
77 }
78
79 std::string unique_id;
80 unique_id.resize(10, '0');
81 for (int i = 0; i < unique_id.size(); ++i) {
82 unique_id[i] = alphanum[dist(rd)];
83 }
Ed Tanousc963aa42017-10-27 16:00:19 -070084 const auto session_it = auth_tokens.emplace(
85 session_token,
86 std::move(UserSession{unique_id, session_token, username, csrf_token,
87 std::chrono::steady_clock::now()}));
88 const UserSession& user = (session_it).first->second;
89 need_write_ = true;
90 return user;
Ed Tanousba9f9a62017-10-11 16:40:35 -070091 }
92
Ed Tanousc963aa42017-10-27 16:00:19 -070093 const UserSession* login_session_by_token(const std::string& token) {
94 apply_session_timeouts();
95 auto session_it = auth_tokens.find(token);
96 if (session_it == auth_tokens.end()) {
97 return nullptr;
98 }
99 UserSession& foo = session_it->second;
100 foo.last_updated = std::chrono::steady_clock::now();
101 return &foo;
102 }
103
104 const UserSession* get_session_by_uid(const std::string& uid) {
105 apply_session_timeouts();
106 // TODO(Ed) this is inefficient
107 auto session_it = auth_tokens.begin();
108 while (session_it != auth_tokens.end()) {
109 if (session_it->second.unique_id == uid) {
110 return &session_it->second;
111 }
112 session_it++;
113 }
114 return nullptr;
115 }
116
117 void remove_session(const UserSession* session) {
118 auth_tokens.erase(session->session_token);
119 need_write_ = true;
120 }
121
122 std::vector<const std::string*> get_unique_ids() {
123 std::vector<const std::string*> ret;
124 ret.reserve(auth_tokens.size());
125 for (auto& session : auth_tokens) {
126 ret.push_back(&session.second.unique_id);
127 }
128 return ret;
129 }
130
131 bool needs_write() { return need_write_; }
132
133 // Persistent data middleware needs to be able to serialize our auth_tokens
134 // structure, which is private
135 friend Middleware;
136
137 private:
138 void apply_session_timeouts() {
139 std::chrono::minutes timeout(60);
140 auto time_now = std::chrono::steady_clock::now();
141 if (time_now - last_timeout_update > std::chrono::minutes(1)) {
142 last_timeout_update = time_now;
143 auto auth_tokens_it = auth_tokens.begin();
144 while (auth_tokens_it != auth_tokens.end()) {
145 if (time_now - auth_tokens_it->second.last_updated >= timeout) {
146 auth_tokens_it = auth_tokens.erase(auth_tokens_it);
147 need_write_ = true;
148 } else {
149 auth_tokens_it++;
150 }
151 }
152 }
153 }
154 std::chrono::time_point<std::chrono::steady_clock> last_timeout_update;
155 boost::container::flat_map<std::string, UserSession> auth_tokens;
Ed Tanousba9f9a62017-10-11 16:40:35 -0700156 std::random_device rd;
Ed Tanousc963aa42017-10-27 16:00:19 -0700157 bool need_write_{false};
158};
159
160class Middleware {
161 // todo(ed) should read this from a fixed location somewhere, not CWD
162 static constexpr const char* filename = "bmcweb_persistent_data.json";
163 int json_revision = 1;
164
165 public:
166 struct context {
167 SessionStore* sessions;
168 };
169
170 Middleware() { read_data(); }
171
172 ~Middleware() {
173 if (sessions.needs_write()) {
174 write_data();
175 }
176 }
177
178 void before_handle(crow::request& req, response& res, context& ctx) {
179 ctx.sessions = &sessions;
180 }
181
182 void after_handle(request& req, response& res, context& ctx) {}
183
184 // TODO(ed) this should really use protobuf, or some other serialization
185 // library, but adding another dependency is somewhat outside the scope of
186 // this application for the moment
187 void read_data() {
188 std::ifstream persistent_file(filename);
189 int file_revision = 0;
190 if (persistent_file.is_open()) {
191 // call with exceptions disabled
192 auto data = nlohmann::json::parse(persistent_file, nullptr, false);
193 if (!data.is_discarded()) {
194 file_revision = data.value("revision", 0);
195 sessions.auth_tokens =
196 data.value("sessions", decltype(sessions.auth_tokens)());
197 system_uuid = data.value("system_uuid", "");
198 }
199 }
200 bool need_write = false;
201
202 if (system_uuid.empty()) {
203 system_uuid = boost::uuids::to_string(boost::uuids::random_generator()());
204 need_write = true;
205 }
206 if (file_revision < json_revision) {
207 need_write = true;
208 }
209 // write revision changes or system uuid changes immediately
210 if (need_write) {
211 write_data();
212 }
213 }
214
215 void write_data() {
216 std::ofstream persistent_file(filename);
217 nlohmann::json data;
218 data["sessions"] = sessions.auth_tokens;
219 data["system_uuid"] = system_uuid;
220 data["revision"] = json_revision;
221 persistent_file << data;
222 }
223
224 SessionStore sessions;
225 std::string system_uuid;
Ed Tanousba9f9a62017-10-11 16:40:35 -0700226};
227
228} // namespaec PersistentData
229} // namespace crow