summaryrefslogtreecommitdiff
path: root/include/sessions.hpp
diff options
context:
space:
mode:
Diffstat (limited to 'include/sessions.hpp')
-rw-r--r--include/sessions.hpp444
1 files changed, 248 insertions, 196 deletions
diff --git a/include/sessions.hpp b/include/sessions.hpp
index f549fde696..510f566476 100644
--- a/include/sessions.hpp
+++ b/include/sessions.hpp
@@ -1,232 +1,284 @@
#pragma once
-#include <nlohmann/json.hpp>
-#include <pam_authenticate.hpp>
-#include <webassets.hpp>
-#include <random>
#include <crow/app.h>
#include <crow/http_request.h>
#include <crow/http_response.h>
+
#include <boost/container/flat_map.hpp>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>
+#include <nlohmann/json.hpp>
+#include <pam_authenticate.hpp>
+#include <random>
+#include <webassets.hpp>
-namespace crow {
+namespace crow
+{
-namespace persistent_data {
+namespace persistent_data
+{
-enum class PersistenceType {
- TIMEOUT, // User session times out after a predetermined amount of time
- SINGLE_REQUEST // User times out once this request is completed.
+enum class PersistenceType
+{
+ TIMEOUT, // User session times out after a predetermined amount of time
+ SINGLE_REQUEST // User times out once this request is completed.
};
-struct UserSession {
- std::string uniqueId;
- std::string sessionToken;
- std::string username;
- std::string csrfToken;
- std::chrono::time_point<std::chrono::steady_clock> lastUpdated;
- PersistenceType persistence;
-
- /**
- * @brief Fills object with data from UserSession's JSON representation
- *
- * This replaces nlohmann's from_json to ensure no-throw approach
- *
- * @param[in] j JSON object from which data should be loaded
- *
- * @return a shared pointer if data has been loaded properly, nullptr
- * otherwise
- */
- static std::shared_ptr<UserSession> fromJson(const nlohmann::json& j) {
- std::shared_ptr<UserSession> userSession = std::make_shared<UserSession>();
- for (const auto& element : j.items()) {
- const std::string* thisValue =
- element.value().get_ptr<const std::string*>();
- if (thisValue == nullptr) {
- BMCWEB_LOG_ERROR << "Error reading persistent store. Property "
- << element.key() << " was not of type string";
- return nullptr;
- }
- if (element.key() == "unique_id") {
- userSession->uniqueId = *thisValue;
- } else if (element.key() == "session_token") {
- userSession->sessionToken = *thisValue;
- } else if (element.key() == "csrf_token") {
- userSession->csrfToken = *thisValue;
- } else if (element.key() == "username") {
- userSession->username = *thisValue;
- } else {
- BMCWEB_LOG_ERROR << "Got unexpected property reading persistent file: "
- << element.key();
- return nullptr;
- }
- }
+struct UserSession
+{
+ std::string uniqueId;
+ std::string sessionToken;
+ std::string username;
+ std::string csrfToken;
+ std::chrono::time_point<std::chrono::steady_clock> lastUpdated;
+ PersistenceType persistence;
+
+ /**
+ * @brief Fills object with data from UserSession's JSON representation
+ *
+ * This replaces nlohmann's from_json to ensure no-throw approach
+ *
+ * @param[in] j JSON object from which data should be loaded
+ *
+ * @return a shared pointer if data has been loaded properly, nullptr
+ * otherwise
+ */
+ static std::shared_ptr<UserSession> fromJson(const nlohmann::json& j)
+ {
+ std::shared_ptr<UserSession> userSession =
+ std::make_shared<UserSession>();
+ for (const auto& element : j.items())
+ {
+ const std::string* thisValue =
+ element.value().get_ptr<const std::string*>();
+ if (thisValue == nullptr)
+ {
+ BMCWEB_LOG_ERROR << "Error reading persistent store. Property "
+ << element.key() << " was not of type string";
+ return nullptr;
+ }
+ if (element.key() == "unique_id")
+ {
+ userSession->uniqueId = *thisValue;
+ }
+ else if (element.key() == "session_token")
+ {
+ userSession->sessionToken = *thisValue;
+ }
+ else if (element.key() == "csrf_token")
+ {
+ userSession->csrfToken = *thisValue;
+ }
+ else if (element.key() == "username")
+ {
+ userSession->username = *thisValue;
+ }
+ else
+ {
+ BMCWEB_LOG_ERROR
+ << "Got unexpected property reading persistent file: "
+ << element.key();
+ return nullptr;
+ }
+ }
- // For now, sessions that were persisted through a reboot get their idle
- // timer reset. This could probably be overcome with a better understanding
- // of wall clock time and steady timer time, possibly persisting values with
- // wall clock time instead of steady timer, but the tradeoffs of all the
- // corner cases involved are non-trivial, so this is done temporarily
- userSession->lastUpdated = std::chrono::steady_clock::now();
- userSession->persistence = PersistenceType::TIMEOUT;
+ // For now, sessions that were persisted through a reboot get their idle
+ // timer reset. This could probably be overcome with a better
+ // understanding of wall clock time and steady timer time, possibly
+ // persisting values with wall clock time instead of steady timer, but
+ // the tradeoffs of all the corner cases involved are non-trivial, so
+ // this is done temporarily
+ userSession->lastUpdated = std::chrono::steady_clock::now();
+ userSession->persistence = PersistenceType::TIMEOUT;
- return userSession;
- }
+ return userSession;
+ }
};
class Middleware;
-class SessionStore {
- public:
- std::shared_ptr<UserSession> generateUserSession(
- const boost::string_view username,
- PersistenceType persistence = PersistenceType::TIMEOUT) {
- // TODO(ed) find a secure way to not generate session identifiers if
- // persistence is set to SINGLE_REQUEST
- static constexpr std::array<char, 62> alphanum = {
- '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'b', 'C',
- 'D', 'E', 'F', 'g', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
- 'Q', 'r', 'S', 'T', 'U', 'v', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
- 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
- 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
-
- // entropy: 30 characters, 62 possibilities. log2(62^30) = 178 bits of
- // entropy. OWASP recommends at least 60
- // https://www.owasp.org/index.php/Session_Management_Cheat_Sheet#Session_ID_Entropy
- std::string sessionToken;
- sessionToken.resize(20, '0');
- std::uniform_int_distribution<int> dist(0, alphanum.size() - 1);
- for (int i = 0; i < sessionToken.size(); ++i) {
- sessionToken[i] = alphanum[dist(rd)];
+class SessionStore
+{
+ public:
+ std::shared_ptr<UserSession> generateUserSession(
+ const boost::string_view username,
+ PersistenceType persistence = PersistenceType::TIMEOUT)
+ {
+ // TODO(ed) find a secure way to not generate session identifiers if
+ // persistence is set to SINGLE_REQUEST
+ static constexpr std::array<char, 62> alphanum = {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'b', 'C',
+ 'D', 'E', 'F', 'g', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+ 'Q', 'r', 'S', 'T', 'U', 'v', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
+ 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
+ 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
+
+ // entropy: 30 characters, 62 possibilities. log2(62^30) = 178 bits of
+ // entropy. OWASP recommends at least 60
+ // https://www.owasp.org/index.php/Session_Management_Cheat_Sheet#Session_ID_Entropy
+ std::string sessionToken;
+ sessionToken.resize(20, '0');
+ std::uniform_int_distribution<int> dist(0, alphanum.size() - 1);
+ for (int i = 0; i < sessionToken.size(); ++i)
+ {
+ sessionToken[i] = alphanum[dist(rd)];
+ }
+ // Only need csrf tokens for cookie based auth, token doesn't matter
+ std::string csrfToken;
+ csrfToken.resize(20, '0');
+ for (int i = 0; i < csrfToken.size(); ++i)
+ {
+ csrfToken[i] = alphanum[dist(rd)];
+ }
+
+ std::string uniqueId;
+ uniqueId.resize(10, '0');
+ for (int i = 0; i < uniqueId.size(); ++i)
+ {
+ uniqueId[i] = alphanum[dist(rd)];
+ }
+ auto session = std::make_shared<UserSession>(UserSession{
+ uniqueId, sessionToken, std::string(username), csrfToken,
+ std::chrono::steady_clock::now(), persistence});
+ auto it = authTokens.emplace(std::make_pair(sessionToken, session));
+ // Only need to write to disk if session isn't about to be destroyed.
+ needWrite = persistence == PersistenceType::TIMEOUT;
+ return it.first->second;
}
- // Only need csrf tokens for cookie based auth, token doesn't matter
- std::string csrfToken;
- csrfToken.resize(20, '0');
- for (int i = 0; i < csrfToken.size(); ++i) {
- csrfToken[i] = alphanum[dist(rd)];
+
+ std::shared_ptr<UserSession>
+ loginSessionByToken(const boost::string_view token)
+ {
+ applySessionTimeouts();
+ auto sessionIt = authTokens.find(std::string(token));
+ if (sessionIt == authTokens.end())
+ {
+ return nullptr;
+ }
+ std::shared_ptr<UserSession> userSession = sessionIt->second;
+ userSession->lastUpdated = std::chrono::steady_clock::now();
+ return userSession;
}
- std::string uniqueId;
- uniqueId.resize(10, '0');
- for (int i = 0; i < uniqueId.size(); ++i) {
- uniqueId[i] = alphanum[dist(rd)];
+ std::shared_ptr<UserSession> getSessionByUid(const boost::string_view uid)
+ {
+ applySessionTimeouts();
+ // TODO(Ed) this is inefficient
+ auto sessionIt = authTokens.begin();
+ while (sessionIt != authTokens.end())
+ {
+ if (sessionIt->second->uniqueId == uid)
+ {
+ return sessionIt->second;
+ }
+ sessionIt++;
+ }
+ return nullptr;
}
- auto session = std::make_shared<UserSession>(
- UserSession{uniqueId, sessionToken, std::string(username), csrfToken,
- std::chrono::steady_clock::now(), persistence});
- auto it = authTokens.emplace(std::make_pair(sessionToken, session));
- // Only need to write to disk if session isn't about to be destroyed.
- needWrite = persistence == PersistenceType::TIMEOUT;
- return it.first->second;
- }
-
- std::shared_ptr<UserSession> loginSessionByToken(
- const boost::string_view token) {
- applySessionTimeouts();
- auto sessionIt = authTokens.find(std::string(token));
- if (sessionIt == authTokens.end()) {
- return nullptr;
+
+ void removeSession(std::shared_ptr<UserSession> session)
+ {
+ authTokens.erase(session->sessionToken);
+ needWrite = true;
}
- std::shared_ptr<UserSession> userSession = sessionIt->second;
- userSession->lastUpdated = std::chrono::steady_clock::now();
- return userSession;
- }
-
- std::shared_ptr<UserSession> getSessionByUid(const boost::string_view uid) {
- applySessionTimeouts();
- // TODO(Ed) this is inefficient
- auto sessionIt = authTokens.begin();
- while (sessionIt != authTokens.end()) {
- if (sessionIt->second->uniqueId == uid) {
- return sessionIt->second;
- }
- sessionIt++;
+
+ std::vector<const std::string*> getUniqueIds(
+ bool getAll = true,
+ const PersistenceType& type = PersistenceType::SINGLE_REQUEST)
+ {
+ applySessionTimeouts();
+
+ std::vector<const std::string*> ret;
+ ret.reserve(authTokens.size());
+ for (auto& session : authTokens)
+ {
+ if (getAll || type == session.second->persistence)
+ {
+ ret.push_back(&session.second->uniqueId);
+ }
+ }
+ return ret;
+ }
+
+ bool needsWrite()
+ {
+ return needWrite;
+ }
+ int getTimeoutInSeconds() const
+ {
+ return std::chrono::seconds(timeoutInMinutes).count();
+ };
+
+ // Persistent data middleware needs to be able to serialize our authTokens
+ // structure, which is private
+ friend Middleware;
+
+ static SessionStore& getInstance()
+ {
+ static SessionStore sessionStore;
+ return sessionStore;
}
- return nullptr;
- }
-
- void removeSession(std::shared_ptr<UserSession> session) {
- authTokens.erase(session->sessionToken);
- needWrite = true;
- }
-
- std::vector<const std::string*> getUniqueIds(
- bool getAll = true,
- const PersistenceType& type = PersistenceType::SINGLE_REQUEST) {
- applySessionTimeouts();
-
- std::vector<const std::string*> ret;
- ret.reserve(authTokens.size());
- for (auto& session : authTokens) {
- if (getAll || type == session.second->persistence) {
- ret.push_back(&session.second->uniqueId);
- }
+
+ SessionStore(const SessionStore&) = delete;
+ SessionStore& operator=(const SessionStore&) = delete;
+
+ private:
+ SessionStore() : timeoutInMinutes(60)
+ {
}
- return ret;
- }
-
- bool needsWrite() { return needWrite; }
- int getTimeoutInSeconds() const {
- return std::chrono::seconds(timeoutInMinutes).count();
- };
-
- // Persistent data middleware needs to be able to serialize our authTokens
- // structure, which is private
- friend Middleware;
-
- static SessionStore& getInstance() {
- static SessionStore sessionStore;
- return sessionStore;
- }
-
- SessionStore(const SessionStore&) = delete;
- SessionStore& operator=(const SessionStore&) = delete;
-
- private:
- SessionStore() : timeoutInMinutes(60) {}
-
- void applySessionTimeouts() {
- auto timeNow = std::chrono::steady_clock::now();
- if (timeNow - lastTimeoutUpdate > std::chrono::minutes(1)) {
- lastTimeoutUpdate = timeNow;
- auto authTokensIt = authTokens.begin();
- while (authTokensIt != authTokens.end()) {
- if (timeNow - authTokensIt->second->lastUpdated >= timeoutInMinutes) {
- authTokensIt = authTokens.erase(authTokensIt);
- needWrite = true;
- } else {
- authTokensIt++;
+
+ void applySessionTimeouts()
+ {
+ auto timeNow = std::chrono::steady_clock::now();
+ if (timeNow - lastTimeoutUpdate > std::chrono::minutes(1))
+ {
+ lastTimeoutUpdate = timeNow;
+ auto authTokensIt = authTokens.begin();
+ while (authTokensIt != authTokens.end())
+ {
+ if (timeNow - authTokensIt->second->lastUpdated >=
+ timeoutInMinutes)
+ {
+ authTokensIt = authTokens.erase(authTokensIt);
+ needWrite = true;
+ }
+ else
+ {
+ authTokensIt++;
+ }
+ }
}
- }
}
- }
- std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate;
- boost::container::flat_map<std::string, std::shared_ptr<UserSession>>
- authTokens;
- std::random_device rd;
- bool needWrite{false};
- std::chrono::minutes timeoutInMinutes;
+ std::chrono::time_point<std::chrono::steady_clock> lastTimeoutUpdate;
+ boost::container::flat_map<std::string, std::shared_ptr<UserSession>>
+ authTokens;
+ std::random_device rd;
+ bool needWrite{false};
+ std::chrono::minutes timeoutInMinutes;
};
-} // namespace persistent_data
-} // namespace crow
+} // namespace persistent_data
+} // namespace crow
// to_json(...) definition for objects of UserSession type
-namespace nlohmann {
+namespace nlohmann
+{
template <>
-struct adl_serializer<std::shared_ptr<crow::persistent_data::UserSession>> {
- static void to_json(
- nlohmann::json& j,
- const std::shared_ptr<crow::persistent_data::UserSession>& p) {
- if (p->persistence !=
- crow::persistent_data::PersistenceType::SINGLE_REQUEST) {
- j = nlohmann::json{{"unique_id", p->uniqueId},
- {"session_token", p->sessionToken},
- {"username", p->username},
- {"csrf_token", p->csrfToken}};
+struct adl_serializer<std::shared_ptr<crow::persistent_data::UserSession>>
+{
+ static void
+ to_json(nlohmann::json& j,
+ const std::shared_ptr<crow::persistent_data::UserSession>& p)
+ {
+ if (p->persistence !=
+ crow::persistent_data::PersistenceType::SINGLE_REQUEST)
+ {
+ j = nlohmann::json{{"unique_id", p->uniqueId},
+ {"session_token", p->sessionToken},
+ {"username", p->username},
+ {"csrf_token", p->csrfToken}};
+ }
}
- }
};
-} // namespace nlohmann
+} // namespace nlohmann