summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKowalski, Kamil <kamil.kowalski@intel.com>2018-01-31 15:24:59 +0300
committerEd Tanous <ed.tanous@intel.com>2018-02-06 02:40:03 +0300
commit2b7981f6e53f76c662d427ced8bd9cffc5dde695 (patch)
tree22412d4e43c4a963e01067487c6f9d1175d5409e
parent109799e512a6abcad3303dffa0505fcf2a521a1a (diff)
downloadbmcweb-2b7981f6e53f76c662d427ced8bd9cffc5dde695.tar.xz
Session and SessionCollection
New Redfish-Core nodes added (removed from redfish_v1.hpp) - Session and SessionCollection. Tested manually on x86 VM and Wolfpass Platform. Behavior almost identical to what was before - differences: - SessionCollection - now only returns TIMEOUT presistence sessions, not SINGLE - Aquiring sessions from session storage now applies timeouts Change-Id: I68bf4fa7fa1c8371216a7d4daa30bbfb653cfa72 Signed-off-by: Kowalski, Kamil <kamil.kowalski@intel.com>
-rw-r--r--include/persistent_data_middleware.hpp173
-rw-r--r--include/redfish_v1.hpp114
-rw-r--r--include/session_storage_singleton.hpp10
-rw-r--r--include/sessions.hpp181
-rw-r--r--include/token_authorization_middleware.hpp77
-rw-r--r--redfish-core/include/redfish.hpp4
-rw-r--r--redfish-core/lib/redfish_sessions.hpp226
-rw-r--r--src/webserver_main.cpp4
8 files changed, 466 insertions, 323 deletions
diff --git a/include/persistent_data_middleware.hpp b/include/persistent_data_middleware.hpp
index b52e2258d5..9d6195c578 100644
--- a/include/persistent_data_middleware.hpp
+++ b/include/persistent_data_middleware.hpp
@@ -4,6 +4,7 @@
#include <pam_authenticate.hpp>
#include <webassets.hpp>
#include <random>
+#include "session_storage_singleton.hpp"
#include <crow/app.h>
#include <crow/http_request.h>
#include <crow/http_response.h>
@@ -16,182 +17,23 @@ namespace crow {
namespace PersistentData {
-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 unique_id;
- std::string session_token;
- std::string username;
- std::string csrf_token;
- std::chrono::time_point<std::chrono::steady_clock> last_updated;
- PersistenceType persistence;
-};
-
-void to_json(nlohmann::json& j, const UserSession& p) {
- if (p.persistence != PersistenceType::SINGLE_REQUEST) {
- j = nlohmann::json{{"unique_id", p.unique_id},
- {"session_token", p.session_token},
- {"username", p.username},
- {"csrf_token", p.csrf_token}};
- }
-}
-
-void from_json(const nlohmann::json& j, UserSession& p) {
- try {
- p.unique_id = j.at("unique_id").get<std::string>();
- p.session_token = j.at("session_token").get<std::string>();
- p.username = j.at("username").get<std::string>();
- p.csrf_token = j.at("csrf_token").get<std::string>();
- // For now, sessions that were persisted through a reboot get their 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
- p.last_updated = std::chrono::steady_clock::now();
- } catch (std::out_of_range) {
- // do nothing. Session API incompatibility, leave sessions empty
- }
-}
-
-class Middleware;
-
-class SessionStore {
- public:
- const UserSession& generate_user_session(
- const std::string& 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 session_token;
- session_token.resize(20, '0');
- std::uniform_int_distribution<int> dist(0, alphanum.size() - 1);
- for (int i = 0; i < session_token.size(); ++i) {
- session_token[i] = alphanum[dist(rd)];
- }
- // Only need csrf tokens for cookie based auth, token doesn't matter
- std::string csrf_token;
- csrf_token.resize(20, '0');
- for (int i = 0; i < csrf_token.size(); ++i) {
- csrf_token[i] = alphanum[dist(rd)];
- }
-
- std::string unique_id;
- unique_id.resize(10, '0');
- for (int i = 0; i < unique_id.size(); ++i) {
- unique_id[i] = alphanum[dist(rd)];
- }
-
- const auto session_it = auth_tokens.emplace(
- session_token,
- std::move(UserSession{unique_id, session_token, username, csrf_token,
- std::chrono::steady_clock::now(), persistence}));
- const UserSession& user = (session_it).first->second;
- // Only need to write to disk if session isn't about to be destroyed.
- need_write_ = persistence == PersistenceType::TIMEOUT;
- return user;
- }
-
- const UserSession* login_session_by_token(const std::string& token) {
- apply_session_timeouts();
- auto session_it = auth_tokens.find(token);
- if (session_it == auth_tokens.end()) {
- return nullptr;
- }
- UserSession& foo = session_it->second;
- foo.last_updated = std::chrono::steady_clock::now();
- return &foo;
- }
-
- const UserSession* get_session_by_uid(const std::string& uid) {
- apply_session_timeouts();
- // TODO(Ed) this is inefficient
- auto session_it = auth_tokens.begin();
- while (session_it != auth_tokens.end()) {
- if (session_it->second.unique_id == uid) {
- return &session_it->second;
- }
- session_it++;
- }
- return nullptr;
- }
-
- void remove_session(const UserSession* session) {
- auth_tokens.erase(session->session_token);
- need_write_ = true;
- }
-
- std::vector<const std::string*> get_unique_ids() {
- std::vector<const std::string*> ret;
- ret.reserve(auth_tokens.size());
- for (auto& session : auth_tokens) {
- ret.push_back(&session.second.unique_id);
- }
- return ret;
- }
-
- bool needs_write() { return need_write_; }
-
- // Persistent data middleware needs to be able to serialize our auth_tokens
- // structure, which is private
- friend Middleware;
-
- private:
- void apply_session_timeouts() {
- std::chrono::minutes timeout(60);
- auto time_now = std::chrono::steady_clock::now();
- if (time_now - last_timeout_update > std::chrono::minutes(1)) {
- last_timeout_update = time_now;
- auto auth_tokens_it = auth_tokens.begin();
- while (auth_tokens_it != auth_tokens.end()) {
- if (time_now - auth_tokens_it->second.last_updated >= timeout) {
- auth_tokens_it = auth_tokens.erase(auth_tokens_it);
- need_write_ = true;
- } else {
- auth_tokens_it++;
- }
- }
- }
- }
- std::chrono::time_point<std::chrono::steady_clock> last_timeout_update;
- boost::container::flat_map<std::string, UserSession> auth_tokens;
- std::random_device rd;
- bool need_write_{false};
-};
-
class Middleware {
// todo(ed) should read this from a fixed location somewhere, not CWD
static constexpr const char* filename = "bmcweb_persistent_data.json";
int json_revision = 1;
public:
- struct context {
- SessionStore* sessions;
- };
+ struct context {};
Middleware() { read_data(); }
~Middleware() {
- if (sessions.needs_write()) {
+ if (PersistentData::session_store->needs_write()) {
write_data();
}
}
- void before_handle(crow::request& req, response& res, context& ctx) {
- ctx.sessions = &sessions;
- }
+ void before_handle(crow::request& req, response& res, context& ctx) {}
void after_handle(request& req, response& res, context& ctx) {}
@@ -206,8 +48,8 @@ class Middleware {
auto data = nlohmann::json::parse(persistent_file, nullptr, false);
if (!data.is_discarded()) {
file_revision = data.value("revision", 0);
- sessions.auth_tokens =
- data.value("sessions", decltype(sessions.auth_tokens)());
+ PersistentData::session_store->auth_tokens =
+ data.value("sessions", decltype(session_store->auth_tokens)());
system_uuid = data.value("system_uuid", "");
}
}
@@ -229,13 +71,12 @@ class Middleware {
void write_data() {
std::ofstream persistent_file(filename);
nlohmann::json data;
- data["sessions"] = sessions.auth_tokens;
+ data["sessions"] = PersistentData::session_store->auth_tokens;
data["system_uuid"] = system_uuid;
data["revision"] = json_revision;
persistent_file << data;
}
- SessionStore sessions;
std::string system_uuid;
};
diff --git a/include/redfish_v1.hpp b/include/redfish_v1.hpp
index 1a5ee74eee..3aecae09fe 100644
--- a/include/redfish_v1.hpp
+++ b/include/redfish_v1.hpp
@@ -168,9 +168,8 @@ void request_routes(Crow<Middlewares...>& app) {
for (int user_index = 0; user_index < users.size();
user_index++) {
member_array.push_back(
- {{"@odata.id",
- "/redfish/v1/AccountService/Accounts/" +
- std::to_string(user_index)}});
+ {{"@odata.id", "/redfish/v1/AccountService/Accounts/" +
+ std::to_string(user_index)}});
}
res.json_value["Members"] = member_array;
}
@@ -222,110 +221,6 @@ void request_routes(Crow<Middlewares...>& app) {
res.end();
});
- CROW_ROUTE(app, "/redfish/v1/SessionService/Sessions/")
- .methods("POST"_method, "GET"_method)([&](const crow::request& req,
- crow::response& res) {
- auto& session_store =
- app.template get_middleware<PersistentData::Middleware>().sessions;
- if (req.method == "POST"_method) {
- // call with exceptions disabled
- auto login_credentials =
- nlohmann::json::parse(req.body, nullptr, false);
- if (login_credentials.is_discarded()) {
- res.code = 400;
- res.end();
- return;
- }
- // check for username/password in the root object
- auto user_it = login_credentials.find("UserName");
- auto pass_it = login_credentials.find("Password");
- if (user_it == login_credentials.end() ||
- pass_it == login_credentials.end()) {
- res.code = 400;
- res.end();
- return;
- }
-
- std::string username = user_it->get<const std::string>();
- std::string password = pass_it->get<const std::string>();
- if (username.empty() || password.empty()) {
- res.code = 400;
- res.end();
- return;
- }
-
- if (!pam_authenticate_user(username, password)) {
- res.code = 401;
- res.end();
- return;
- }
- auto session = session_store.generate_user_session(username);
- res.code = 200;
- res.add_header("X-Auth-Token", session.session_token);
- res.json_value = {
- {"@odata.context", "/redfish/v1/$metadata#Session"},
- {"@odata.id",
- "/redfish/v1/SessionService/Sessions/" + session.unique_id},
- {"@odata.type", "#Session.v1_0_3.Session"},
- {"Id", session.unique_id},
- {"Name", "User Session"},
- {"Description", "Manager User Session"},
- {"UserName", username}};
- } else { // assume get
- std::vector<const std::string*> session_ids =
- session_store.get_unique_ids();
- res.json_value = {
- {"@odata.context",
- "/redfish/v1/$metadata#SessionCollection.SessionCollection"},
- {"@odata.id", "/redfish/v1/SessionService/Sessions"},
- {"@odata.type", "#SessionCollection.SessionCollection"},
- {"Name", "Session Collection"},
- {"Description", "Session Collection"},
- {"Members@odata.count", session_ids.size()}
-
- };
- nlohmann::json member_array = nlohmann::json::array();
- for (auto session_uid : session_ids) {
- member_array.push_back(
- {{"@odata.id",
- "/redfish/v1/SessionService/Sessions/" + *session_uid}});
- }
- res.json_value["Members"] = member_array;
- }
- res.end();
- });
-
- CROW_ROUTE(app, "/redfish/v1/SessionService/Sessions/<str>/")
- .methods("GET"_method, "DELETE"_method)([&](
- const crow::request& req, crow::response& res,
- const std::string& session_id) {
- auto& session_store =
- app.template get_middleware<PersistentData::Middleware>().sessions;
- // TODO(Ed) this is inefficient
- auto session = session_store.get_session_by_uid(session_id);
-
- if (session == nullptr) {
- res.code = 404;
- res.end();
- return;
- }
- if (req.method == "DELETE"_method) {
- session_store.remove_session(session);
- res.code = 200;
- } else { // assume get
- res.json_value = {
- {"@odata.context", "/redfish/v1/$metadata#Session.Session"},
- {"@odata.id",
- "/redfish/v1/SessionService/Sessions/" + session->unique_id},
- {"@odata.type", "#Session.v1_0_3.Session"},
- {"Id", session->unique_id},
- {"Name", "User Session"},
- {"Description", "Manager User Session"},
- {"UserName", session->username}};
- }
- res.end();
- });
-
CROW_ROUTE(app, "/redfish/v1/Managers/")
.methods("GET"_method)(
[&](const crow::request& req, crow::response& res) {
@@ -365,9 +260,8 @@ void request_routes(Crow<Middlewares...>& app) {
{"Id", "openbmc"},
{"Name", "OpenBmc Manager"},
{"Description", "Baseboard Management Controller"},
- {"UUID",
- app.template get_middleware<PersistentData::Middleware>()
- .system_uuid},
+ {"UUID", app.template get_middleware<PersistentData::Middleware>()
+ .system_uuid},
{"Model", "OpenBmc"}, // TODO(ed), get model
{"DateTime", time_buffer.data()},
{"Status",
diff --git a/include/session_storage_singleton.hpp b/include/session_storage_singleton.hpp
new file mode 100644
index 0000000000..6ff8a0e303
--- /dev/null
+++ b/include/session_storage_singleton.hpp
@@ -0,0 +1,10 @@
+#pragma once
+#include "sessions.hpp"
+
+namespace crow {
+namespace PersistentData {
+
+static std::shared_ptr<SessionStore> session_store;
+
+} // namespace PersistentData
+} // namespace crow
diff --git a/include/sessions.hpp b/include/sessions.hpp
new file mode 100644
index 0000000000..6d4ab4da57
--- /dev/null
+++ b/include/sessions.hpp
@@ -0,0 +1,181 @@
+#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>
+
+namespace crow {
+
+namespace PersistentData {
+
+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 unique_id;
+ std::string session_token;
+ std::string username;
+ std::string csrf_token;
+ std::chrono::time_point<std::chrono::steady_clock> last_updated;
+ PersistenceType persistence;
+};
+
+void to_json(nlohmann::json& j, const UserSession& p) {
+ if (p.persistence != PersistenceType::SINGLE_REQUEST) {
+ j = nlohmann::json{{"unique_id", p.unique_id},
+ {"session_token", p.session_token},
+ {"username", p.username},
+ {"csrf_token", p.csrf_token}};
+ }
+}
+
+void from_json(const nlohmann::json& j, UserSession& p) {
+ try {
+ p.unique_id = j.at("unique_id").get<std::string>();
+ p.session_token = j.at("session_token").get<std::string>();
+ p.username = j.at("username").get<std::string>();
+ p.csrf_token = j.at("csrf_token").get<std::string>();
+ // For now, sessions that were persisted through a reboot get their 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
+ p.last_updated = std::chrono::steady_clock::now();
+ } catch (std::out_of_range) {
+ // do nothing. Session API incompatibility, leave sessions empty
+ }
+}
+
+class Middleware;
+
+class SessionStore {
+ public:
+ const UserSession& generate_user_session(
+ const std::string& 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 session_token;
+ session_token.resize(20, '0');
+ std::uniform_int_distribution<int> dist(0, alphanum.size() - 1);
+ for (int i = 0; i < session_token.size(); ++i) {
+ session_token[i] = alphanum[dist(rd)];
+ }
+ // Only need csrf tokens for cookie based auth, token doesn't matter
+ std::string csrf_token;
+ csrf_token.resize(20, '0');
+ for (int i = 0; i < csrf_token.size(); ++i) {
+ csrf_token[i] = alphanum[dist(rd)];
+ }
+
+ std::string unique_id;
+ unique_id.resize(10, '0');
+ for (int i = 0; i < unique_id.size(); ++i) {
+ unique_id[i] = alphanum[dist(rd)];
+ }
+
+ const auto session_it = auth_tokens.emplace(
+ session_token,
+ std::move(UserSession{unique_id, session_token, username, csrf_token,
+ std::chrono::steady_clock::now(), persistence}));
+ const UserSession& user = (session_it).first->second;
+ // Only need to write to disk if session isn't about to be destroyed.
+ need_write_ = persistence == PersistenceType::TIMEOUT;
+ return user;
+ }
+
+ const UserSession* login_session_by_token(const std::string& token) {
+ apply_session_timeouts();
+ auto session_it = auth_tokens.find(token);
+ if (session_it == auth_tokens.end()) {
+ return nullptr;
+ }
+ UserSession& foo = session_it->second;
+ foo.last_updated = std::chrono::steady_clock::now();
+ return &foo;
+ }
+
+ const UserSession* get_session_by_uid(const std::string& uid) {
+ apply_session_timeouts();
+ // TODO(Ed) this is inefficient
+ auto session_it = auth_tokens.begin();
+ while (session_it != auth_tokens.end()) {
+ if (session_it->second.unique_id == uid) {
+ return &session_it->second;
+ }
+ session_it++;
+ }
+ return nullptr;
+ }
+
+ void remove_session(const UserSession* session) {
+ auth_tokens.erase(session->session_token);
+ need_write_ = true;
+ }
+
+ std::vector<const std::string*> get_unique_ids(
+ bool getAll = true,
+ const PersistenceType& type = PersistenceType::SINGLE_REQUEST) {
+ apply_session_timeouts();
+
+ std::vector<const std::string*> ret;
+ ret.reserve(auth_tokens.size());
+ for (auto& session : auth_tokens) {
+ if (getAll || type == session.second.persistence) {
+ ret.push_back(&session.second.unique_id);
+ }
+ }
+ return ret;
+ }
+
+ bool needs_write() { return need_write_; }
+
+ // Persistent data middleware needs to be able to serialize our auth_tokens
+ // structure, which is private
+ friend Middleware;
+
+ private:
+ void apply_session_timeouts() {
+ std::chrono::minutes timeout(60);
+ auto time_now = std::chrono::steady_clock::now();
+ if (time_now - last_timeout_update > std::chrono::minutes(1)) {
+ last_timeout_update = time_now;
+ auto auth_tokens_it = auth_tokens.begin();
+ while (auth_tokens_it != auth_tokens.end()) {
+ if (time_now - auth_tokens_it->second.last_updated >= timeout) {
+ auth_tokens_it = auth_tokens.erase(auth_tokens_it);
+ need_write_ = true;
+ } else {
+ auth_tokens_it++;
+ }
+ }
+ }
+ }
+ std::chrono::time_point<std::chrono::steady_clock> last_timeout_update;
+ boost::container::flat_map<std::string, UserSession> auth_tokens;
+ std::random_device rd;
+ bool need_write_{false};
+};
+
+} // namespaec PersistentData
+} // namespace crow
diff --git a/include/token_authorization_middleware.hpp b/include/token_authorization_middleware.hpp
index 809902022f..7497b43f9f 100644
--- a/include/token_authorization_middleware.hpp
+++ b/include/token_authorization_middleware.hpp
@@ -20,23 +20,20 @@ class Middleware {
struct context {
const crow::PersistentData::UserSession* session;
};
- template <typename AllContext>
- void before_handle(crow::request& req, response& res, context& ctx,
- AllContext& allctx) {
- auto& sessions =
- allctx.template get<crow::PersistentData::Middleware>().sessions;
+
+ void before_handle(crow::request& req, response& res, context& ctx) {
std::string auth_header = req.get_header_value("Authorization");
if (auth_header != "") {
// Reject any kind of auth other than basic or token
if (boost::starts_with(auth_header, "Basic ")) {
- ctx.session = perform_basic_auth(auth_header, sessions);
+ ctx.session = perform_basic_auth(auth_header);
} else if (boost::starts_with(auth_header, "Token ")) {
- ctx.session = perform_token_auth(auth_header, sessions);
+ ctx.session = perform_token_auth(auth_header);
}
} else if (req.headers.count("X-Auth-Token") == 1) {
- ctx.session = perform_xtoken_auth(req, sessions);
+ ctx.session = perform_xtoken_auth(req);
} else if (req.headers.count("Cookie") == 1) {
- ctx.session = perform_cookie_auth(req, sessions);
+ ctx.session = perform_cookie_auth(req);
}
if (ctx.session == nullptr && !is_on_whitelist(req)) {
@@ -61,17 +58,13 @@ class Middleware {
if (ctx.session != nullptr &&
ctx.session->persistence ==
crow::PersistentData::PersistenceType::SINGLE_REQUEST) {
- auto& session_store =
- allctx.template get<crow::PersistentData::Middleware>().sessions;
-
- session_store->remove_session(ctx.session);
+ PersistentData::session_store->remove_session(ctx.session);
}
}
private:
const crow::PersistentData::UserSession* perform_basic_auth(
- const std::string& auth_header,
- crow::PersistentData::SessionStore* sessions) const {
+ const std::string& auth_header) const {
CROW_LOG_DEBUG << "[AuthMiddleware] Basic authentication";
std::string auth_data;
@@ -103,33 +96,30 @@ class Middleware {
// needed.
// This whole flow needs to be revisited anyway, as we can't be
// calling directly into pam for every request
- return &(sessions->generate_user_session(
+ return &(PersistentData::session_store->generate_user_session(
user, crow::PersistentData::PersistenceType::SINGLE_REQUEST));
}
const crow::PersistentData::UserSession* perform_token_auth(
- const std::string& auth_header,
- crow::PersistentData::SessionStore* sessions) const {
+ const std::string& auth_header) const {
CROW_LOG_DEBUG << "[AuthMiddleware] Token authentication";
std::string token = auth_header.substr(strlen("Token "));
- auto session = sessions->login_session_by_token(token);
+ auto session = PersistentData::session_store->login_session_by_token(token);
return session;
}
const crow::PersistentData::UserSession* perform_xtoken_auth(
- const crow::request& req,
- crow::PersistentData::SessionStore* sessions) const {
+ const crow::request& req) const {
CROW_LOG_DEBUG << "[AuthMiddleware] X-Auth-Token authentication";
auto& token = req.get_header_value("X-Auth-Token");
- auto session = sessions->login_session_by_token(token);
+ auto session = PersistentData::session_store->login_session_by_token(token);
return session;
}
const crow::PersistentData::UserSession* perform_cookie_auth(
- const crow::request& req,
- crow::PersistentData::SessionStore* sessions) const {
+ const crow::request& req) const {
CROW_LOG_DEBUG << "[AuthMiddleware] Cookie authentication";
auto& cookie_value = req.get_header_value("Cookie");
@@ -147,7 +137,7 @@ class Middleware {
cookie_value.substr(start_index, end_index - start_index);
const crow::PersistentData::UserSession* session =
- sessions->login_session_by_token(auth_key);
+ PersistentData::session_store->login_session_by_token(auth_key);
if (session == nullptr) {
return nullptr;
}
@@ -266,10 +256,8 @@ void request_routes(Crow<Middlewares...>& app) {
if (!pam_authenticate_user(username, password)) {
res.code = res.code = static_cast<int>(HttpRespCode::UNAUTHORIZED);
} else {
- auto& context =
- app.template get_context<PersistentData::Middleware>(req);
- auto& session_store = context.sessions;
- auto& session = session_store->generate_user_session(username);
+ auto& session =
+ PersistentData::session_store->generate_user_session(username);
if (looks_like_ibm) {
// IBM requires a very specific login structure, and doesn't
@@ -279,9 +267,8 @@ void request_routes(Crow<Middlewares...>& app) {
{"message", "200 OK"},
{"status", "ok"}};
res.add_header("Set-Cookie", "XSRF-TOKEN=" + session.csrf_token);
- res.add_header(
- "Set-Cookie",
- "SESSION=" + session.session_token + "; Secure; HttpOnly");
+ res.add_header("Set-Cookie", "SESSION=" + session.session_token +
+ "; Secure; HttpOnly");
res.write(ret.dump());
} else {
@@ -300,21 +287,19 @@ void request_routes(Crow<Middlewares...>& app) {
});
CROW_ROUTE(app, "/logout")
- .methods(
- "POST"_method)([&](const crow::request& req, crow::response& res) {
- auto& session_store =
- app.template get_context<PersistentData::Middleware>(req).sessions;
- auto& session =
- app.template get_context<TokenAuthorization::Middleware>(req)
- .session;
- if (session != nullptr) {
- session_store->remove_session(session);
- }
- res.code = static_cast<int>(HttpRespCode::OK);
- res.end();
- return;
+ .methods("POST"_method)(
+ [&](const crow::request& req, crow::response& res) {
+ auto& session =
+ app.template get_context<TokenAuthorization::Middleware>(req)
+ .session;
+ if (session != nullptr) {
+ PersistentData::session_store->remove_session(session);
+ }
+ res.code = static_cast<int>(HttpRespCode::OK);
+ res.end();
+ return;
- });
+ });
}
} // namespaec TokenAuthorization
} // namespace crow
diff --git a/redfish-core/include/redfish.hpp b/redfish-core/include/redfish.hpp
index c4b765c982..c26d90beec 100644
--- a/redfish-core/include/redfish.hpp
+++ b/redfish-core/include/redfish.hpp
@@ -15,6 +15,7 @@
*/
#pragma once
+#include "../lib/redfish_sessions.hpp"
#include "../lib/service_root.hpp"
namespace redfish {
@@ -34,10 +35,13 @@ class RedfishService {
RedfishService(CrowApp& app) {
auto privilegeProvider = PrivilegeProvider();
serviceRootPtr = std::make_unique<ServiceRoot>(app, privilegeProvider);
+ sessionsCollectionPtr =
+ std::make_unique<SessionCollection>(app, privilegeProvider);
}
private:
std::unique_ptr<ServiceRoot> serviceRootPtr;
+ std::unique_ptr<SessionCollection> sessionsCollectionPtr;
};
} // namespace redfish
diff --git a/redfish-core/lib/redfish_sessions.hpp b/redfish-core/lib/redfish_sessions.hpp
new file mode 100644
index 0000000000..0835fa18a8
--- /dev/null
+++ b/redfish-core/lib/redfish_sessions.hpp
@@ -0,0 +1,226 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+#pragma once
+#include <tuple>
+#include "node.hpp"
+#include "session_storage_singleton.hpp"
+
+namespace redfish {
+
+class SessionCollection;
+
+class Sessions : public Node {
+ public:
+ template <typename CrowApp, typename PrivilegeProvider>
+ Sessions(CrowApp& app, PrivilegeProvider& provider)
+ : Node(app, provider, "#Session.v1_0_2.Session",
+ "/redfish/v1/SessionService/Sessions/<str>", std::string()) {
+ nodeJson["@odata.type"] = Node::odataType;
+ nodeJson["@odata.context"] = "/redfish/v1/$metadata#Session.Session";
+ nodeJson["Name"] = "User Session";
+ nodeJson["Description"] = "Manager User Session";
+ }
+
+ private:
+ void doGet(crow::response& res, const crow::request& req,
+ const std::vector<std::string>& params) override {
+ auto session =
+ crow::PersistentData::session_store->get_session_by_uid(params[0]);
+
+ if (session == nullptr) {
+ res.code = static_cast<int>(HttpRespCode::NOT_FOUND);
+ res.end();
+ return;
+ }
+
+ nodeJson["Id"] = session->unique_id;
+ nodeJson["UserName"] = session->username;
+ nodeJson["@odata.id"] =
+ "/redfish/v1/SessionService/Sessions/" + session->unique_id;
+
+ res.json_value = nodeJson;
+ res.end();
+ }
+
+ void doDelete(crow::response& res, const crow::request& req,
+ const std::vector<std::string>& params) override {
+ // Need only 1 param which should be id of session to be deleted
+ if (params.size() != 1) {
+ res.code = static_cast<int>(HttpRespCode::BAD_REQUEST);
+ res.end();
+ return;
+ }
+
+ auto session =
+ crow::PersistentData::session_store->get_session_by_uid(params[0]);
+
+ if (session == nullptr) {
+ res.code = static_cast<int>(HttpRespCode::NOT_FOUND);
+ res.end();
+ return;
+ }
+
+ crow::PersistentData::session_store->remove_session(session);
+ res.code = static_cast<int>(HttpRespCode::OK);
+ res.end();
+ }
+
+ /**
+ * This allows SessionCollection to reuse this class' doGet method, to
+ * maintain consistency of returned data, as Collection's doPost should return
+ * data for created member which should match member's doGet result in 100%
+ */
+ friend SessionCollection;
+
+ nlohmann::json nodeJson;
+};
+
+class SessionCollection : public Node {
+ public:
+ template <typename CrowApp, typename PrivilegeProvider>
+ SessionCollection(CrowApp& app, PrivilegeProvider& provider)
+ : Node(app, provider, "#SessionCollection.SessionCollection",
+ "/redfish/v1/SessionService/Sessions/"),
+ memberSession(app, provider) {
+ nodeJson["@odata.type"] = Node::odataType;
+ nodeJson["@odata.id"] = Node::odataId;
+ nodeJson["@odata.context"] =
+ "/redfish/v1/$metadata#SessionCollection.SessionCollection";
+ nodeJson["Name"] = "Session Collection";
+ nodeJson["Description"] = "Session Collection";
+ nodeJson["Members@odata.count"] = 0;
+ nodeJson["Members"] = nlohmann::json::array();
+ }
+
+ private:
+ void doGet(crow::response& res, const crow::request& req,
+ const std::vector<std::string>& params) override {
+ std::vector<const std::string*> session_ids =
+ crow::PersistentData::session_store->get_unique_ids(
+ false, crow::PersistentData::PersistenceType::TIMEOUT);
+
+ nodeJson["Members@odata.count"] = session_ids.size();
+ nodeJson["Members"] = nlohmann::json::array();
+ for (const auto& uid : session_ids) {
+ nodeJson["Members"].push_back(
+ {{"@odata.id", "/redfish/v1/SessionService/Sessions/" + *uid}});
+ }
+
+ res.json_value = nodeJson;
+ res.end();
+ }
+
+ void doPost(crow::response& res, const crow::request& req,
+ const std::vector<std::string>& params) override {
+ std::string username;
+ bool userAuthSuccessful = authenticateUser(req, &res.code, &username);
+
+ if (!userAuthSuccessful) {
+ res.end();
+ return;
+ }
+
+ // User is authenticated - create session for him
+ auto session =
+ crow::PersistentData::session_store->generate_user_session(username);
+ res.add_header("X-Auth-Token", session.session_token);
+
+ // Return data for created session
+ memberSession.doGet(res, req, {session.unique_id});
+
+ // No need for res.end(), as it is called by doGet()
+ }
+
+ /**
+ * @brief Verifies data provided in request and tries to authenticate user
+ *
+ * @param[in] req Crow request containing authentication data
+ * @param[out] httpRespCode HTTP Code that should be returned in response
+ * @param[out] user Retrieved username - not filled on failure
+ *
+ * @return true if authentication was successful, false otherwise
+ */
+ bool authenticateUser(const crow::request& req, int* httpRespCode,
+ std::string* user) {
+ // We need only UserName and Password - nothing more, nothing less
+ static constexpr const unsigned int numberOfRequiredFieldsInReq = 2;
+
+ // call with exceptions disabled
+ auto login_credentials = nlohmann::json::parse(req.body, nullptr, false);
+ if (login_credentials.is_discarded()) {
+ *httpRespCode = static_cast<int>(HttpRespCode::BAD_REQUEST);
+
+ return false;
+ }
+
+ // Check that there are only as many fields as there should be
+ if (login_credentials.size() != numberOfRequiredFieldsInReq) {
+ *httpRespCode = static_cast<int>(HttpRespCode::BAD_REQUEST);
+
+ return false;
+ }
+
+ // Find fields that we need - UserName and Password
+ auto user_it = login_credentials.find("UserName");
+ auto pass_it = login_credentials.find("Password");
+ if (user_it == login_credentials.end() ||
+ pass_it == login_credentials.end()) {
+ *httpRespCode = static_cast<int>(HttpRespCode::BAD_REQUEST);
+
+ return false;
+ }
+
+ // Check that given data is of valid type (string)
+ if (!user_it->is_string() || !pass_it->is_string()) {
+ *httpRespCode = static_cast<int>(HttpRespCode::BAD_REQUEST);
+
+ return false;
+ }
+
+ // Extract username and password
+ std::string username = user_it->get<const std::string>();
+ std::string password = pass_it->get<const std::string>();
+
+ // Verify that required fields are not empty
+ if (username.empty() || password.empty()) {
+ *httpRespCode = static_cast<int>(HttpRespCode::BAD_REQUEST);
+
+ return false;
+ }
+
+ // Finally - try to authenticate user
+ if (!pam_authenticate_user(username, password)) {
+ *httpRespCode = static_cast<int>(HttpRespCode::UNAUTHORIZED);
+
+ return false;
+ }
+
+ // User authenticated successfully
+ *httpRespCode = static_cast<int>(HttpRespCode::OK);
+ *user = username;
+
+ return true;
+ }
+
+ /**
+ * Member session to ensure consistency between collection's doPost and
+ * member's doGet, as they should return 100% matching data
+ */
+ Sessions memberSession;
+ nlohmann::json nodeJson;
+};
+
+} // namespace redfish
diff --git a/src/webserver_main.cpp b/src/webserver_main.cpp
index 834a5df41e..f504cc7ed8 100644
--- a/src/webserver_main.cpp
+++ b/src/webserver_main.cpp
@@ -12,10 +12,10 @@
#include <webassets.hpp>
#include <memory>
#include <string>
+#include "redfish.hpp"
#include <crow/app.h>
#include <boost/asio.hpp>
#include <systemd/sd-daemon.h>
-#include "redfish.hpp"
constexpr int defaultPort = 18080;
@@ -41,6 +41,8 @@ void setup_socket(crow::Crow<Middlewares...>& app) {
int main(int argc, char** argv) {
auto io = std::make_shared<boost::asio::io_service>();
+ crow::PersistentData::session_store =
+ std::make_shared<crow::PersistentData::SessionStore>();
crow::App<crow::PersistentData::Middleware,
crow::TokenAuthorization::Middleware,
crow::SecurityHeadersMiddleware>