summaryrefslogtreecommitdiff
path: root/include/token_authorization_middleware.hpp
diff options
context:
space:
mode:
authorBorawski.Lukasz <lukasz.borawski@intel.com>2018-01-05 16:56:09 +0300
committerEd Tanous <ed.tanous@intel.com>2018-01-18 01:52:27 +0300
commit9d8fd3037f9b9e3f243a00f2d6b2c8ca589f21cd (patch)
treeb3a32943513a39f3fcb7a86d7027bd032f92500e /include/token_authorization_middleware.hpp
parent1623897cb470ae218eba8fb18560f33d2b968507 (diff)
downloadbmcweb-9d8fd3037f9b9e3f243a00f2d6b2c8ca589f21cd.tar.xz
Middleware prepared for Redfish integration
This is adapted from Lukasz patchset, and fixes minor errors in how basic authentication was processed. Tested by: 1. Logging into the webui, and ensuring that cookie auth completes (by looking at the chrome debugger) 2. Checked that basic auth works by accessing the redfish accounts schema using the following command: wget -d https://localhost:18080/redfish/v1/SessionService/Sessions/ --user=root --password=<password> --no-check-certificate Change-Id: I21920a56c52288a74a2b7d587b7c2d7eeeae8d6f Signed-off-by: Borawski.Lukasz <lukasz.borawski@intel.com> Signed-off-by: Ed Tanous <ed.tanous@intel.com>
Diffstat (limited to 'include/token_authorization_middleware.hpp')
-rw-r--r--include/token_authorization_middleware.hpp275
1 files changed, 182 insertions, 93 deletions
diff --git a/include/token_authorization_middleware.hpp b/include/token_authorization_middleware.hpp
index 81d9e58033..bbbaa156eb 100644
--- a/include/token_authorization_middleware.hpp
+++ b/include/token_authorization_middleware.hpp
@@ -5,119 +5,190 @@
#include <webassets.hpp>
#include <random>
#include <crow/app.h>
+#include <crow/http_codes.h>
#include <crow/http_request.h>
#include <crow/http_response.h>
#include <boost/bimap.hpp>
#include <boost/container/flat_set.hpp>
+
namespace crow {
namespace TokenAuthorization {
-struct User {};
class Middleware {
public:
- struct context {};
+ struct context {
+ const crow::PersistentData::UserSession* session;
+ };
template <typename AllContext>
void before_handle(crow::request& req, response& res, context& ctx,
AllContext& allctx) {
- auto return_unauthorized = [&req, &res]() {
- res.code = 401;
- res.end();
- };
-
- if (crow::webassets::routes.find(req.url) !=
- crow::webassets::routes.end()) {
- // TODO this is total hackery to allow the login page to work before the
- // user is authenticated. Also, it will be quite slow for all pages
- // instead of a one time hit for the whitelist entries. Ideally, this
- // should be done in the url router handler, with tagged routes for the
- // whitelist entries. Another option would be to whitelist a minimal form
- // based page that didn't load the full angular UI until after login
- } else if (req.url == "/login" || req.url == "/redfish/v1" ||
- req.url == "/redfish/v1/" ||
- req.url == "/redfish/v1/$metadata" ||
- (req.url == "/redfish/v1/SessionService/Sessions/" &&
- req.method == "POST"_method)) {
- } else {
- // Normal, non login, non static file request
- // Check for an authorization header, reject if not present
- std::string auth_key;
- bool require_csrf = true;
- if (req.headers.count("Authorization") == 1) {
- std::string auth_header = req.get_header_value("Authorization");
- // If the user is attempting any kind of auth other than token, reject
- if (!boost::starts_with(auth_header, "Token ")) {
- return_unauthorized();
- return;
- }
- auth_key = auth_header.substr(6);
- require_csrf = false;
- } else if (req.headers.count("X-Auth-Token") == 1) {
- auth_key = req.get_header_value("X-Auth-Token");
- require_csrf = false;
- } else {
- int count = req.headers.count("Cookie");
- if (count == 1) {
- auto& cookie_value = req.get_header_value("Cookie");
- auto start_index = cookie_value.find("SESSION=");
- if (start_index != std::string::npos) {
- start_index += 8;
- auto end_index = cookie_value.find(";", start_index);
- if (end_index == std::string::npos) {
- end_index = cookie_value.size();
- }
- auth_key =
- cookie_value.substr(start_index, end_index - start_index);
- }
- }
- require_csrf = true; // Cookies require CSRF
+ auto& sessions =
+ allctx.template get<crow::PersistentData::Middleware>().sessions;
+ 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);
+ } else if (boost::starts_with(auth_header, "Token ")) {
+ ctx.session = perform_token_auth(auth_header, sessions);
}
- if (auth_key.empty()) {
- res.code = 400;
- res.end();
- return;
+ } else if (req.headers.count("X-Auth-Token") == 1) {
+ ctx.session = perform_xtoken_auth(req, sessions);
+ } else if (req.headers.count("Cookie") == 1) {
+ ctx.session = perform_cookie_auth(req, sessions);
+ }
+
+ if (ctx.session == nullptr && !is_on_whitelist(req)) {
+ CROW_LOG_WARNING << "[AuthMiddleware] authorization failed";
+ res.code = static_cast<int>(HttpRespCode::UNAUTHORIZED);
+ res.add_header("WWW-Authenticate", "Basic");
+ res.end();
+ return;
+ }
+
+ // TODO get user privileges here and propagate it via MW context
+ // else let the request continue unharmed
+ }
+
+ template <typename AllContext>
+ void after_handle(request& req, response& res, context& ctx,
+ AllContext& allctx) {
+ // TODO(ed) THis should really be handled by the persistent data middleware,
+ // but because it is upstream, it doesn't have access to the session
+ // information. Should the data middleware persist the current user
+ // session?
+ 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);
+ }
+ }
+
+ private:
+ const crow::PersistentData::UserSession* perform_basic_auth(
+ const std::string& auth_header,
+ crow::PersistentData::SessionStore* sessions) const {
+ CROW_LOG_DEBUG << "[AuthMiddleware] Basic authentication";
+
+ std::string auth_data;
+ std::string param = auth_header.substr(strlen("Basic "));
+ if (!crow::utility::base64_decode(param, auth_data)) {
+ return nullptr;
+ }
+ std::size_t separator = auth_data.find(':');
+ if (separator == std::string::npos) {
+ return nullptr;
+ }
+
+ std::string user = auth_data.substr(0, separator);
+ separator += 1;
+ if (separator > auth_data.size()) {
+ return nullptr;
+ }
+ std::string pass = auth_data.substr(separator);
+
+ CROW_LOG_DEBUG << "[AuthMiddleware] Authenticating user: " << user;
+
+ if (!pam_authenticate_user(user, pass)) {
+ return nullptr;
+ }
+
+ // TODO(ed) generate_user_session is a little expensive for basic
+ // auth, as it generates some random identifiers that will never be
+ // used. This should have a "fast" path for when user tokens aren't
+ // 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(
+ user, crow::PersistentData::PersistenceType::SINGLE_REQUEST));
+ }
+
+ const crow::PersistentData::UserSession* perform_token_auth(
+ const std::string& auth_header,
+ crow::PersistentData::SessionStore* sessions) const {
+ CROW_LOG_DEBUG << "[AuthMiddleware] Token authentication";
+
+ std::string token = auth_header.substr(strlen("Token "));
+ auto session = sessions->login_session_by_token(token);
+ return session;
+ }
+
+ const crow::PersistentData::UserSession* perform_xtoken_auth(
+ const crow::request& req,
+ crow::PersistentData::SessionStore* sessions) 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);
+ return session;
+ }
+
+ const crow::PersistentData::UserSession* perform_cookie_auth(
+ const crow::request& req,
+ crow::PersistentData::SessionStore* sessions) const {
+ CROW_LOG_DEBUG << "[AuthMiddleware] Cookie authentication";
+
+ auto& cookie_value = req.get_header_value("Cookie");
+
+ auto start_index = cookie_value.find("SESSION=");
+ if (start_index == std::string::npos) {
+ return nullptr;
+ }
+ start_index += sizeof("SESSION=");
+ auto end_index = cookie_value.find(";", start_index);
+ if (end_index == std::string::npos) {
+ end_index = cookie_value.size();
+ }
+ std::string auth_key =
+ cookie_value.substr(start_index, end_index - start_index);
+
+ const crow::PersistentData::UserSession* session =
+ sessions->login_session_by_token(auth_key);
+ if (session == nullptr) {
+ return nullptr;
+ }
+
+ // RFC7231 defines methods that need csrf protection
+ if (req.method != "GET"_method) {
+ const std::string& csrf = req.get_header_value("X-XSRF-TOKEN");
+ // Make sure both tokens are filled
+ if (csrf.empty() || session->csrf_token.empty()) {
+ return nullptr;
}
- auto& data_mw = allctx.template get<PersistentData::Middleware>();
- const PersistentData::UserSession* session =
- data_mw.sessions->login_session_by_token(auth_key);
- if (session == nullptr) {
- return_unauthorized();
- return;
+ // Reject if csrf token not available
+ if (csrf != session->csrf_token) {
+ return nullptr;
}
+ }
+ return session;
+ }
- if (require_csrf) {
- // RFC7231 defines methods that need csrf protection
- if (req.method != "GET"_method) {
- const std::string& csrf = req.get_header_value("X-XSRF-TOKEN");
- // Make sure both tokens are filled
- if (csrf.empty() || session->csrf_token.empty()) {
- return_unauthorized();
- return;
- }
- // Reject if csrf token not available
- if (csrf != session->csrf_token) {
- return_unauthorized();
- return;
- }
- }
+ // checks if request can be forwarded without authentication
+ bool is_on_whitelist(const crow::request& req) const {
+ // it's allowed to GET root node without authentication
+ if ("GET"_method == req.method) {
+ if (req.url == "/redfish/v1") {
+ return true;
+ } else if (crow::webassets::routes.find(req.url) !=
+ crow::webassets::routes.end()) {
+ return true;
}
+ }
- if (req.url == "/logout" && req.method == "POST"_method) {
- data_mw.sessions->remove_session(session);
- res.code = 200;
- res.end();
- return;
+ // it's allowed to POST on session collection & login without authentication
+ if ("POST"_method == req.method) {
+ if ((req.url == "/redfish/v1/SessionService/Sessions") ||
+ (req.url == "/login") || (req.url == "/logout")) {
+ return true;
}
-
- // else let the request continue unharmed
}
- }
- void after_handle(request& req, response& res, context& ctx) {
- // Do nothing
+ return false;
}
-
- boost::container::flat_set<std::string> allowed_routes;
};
// TODO(ed) see if there is a better way to allow middlewares to request
@@ -193,7 +264,7 @@ void request_routes(Crow<Middlewares...>& app) {
if (!username.empty() && !password.empty()) {
if (!pam_authenticate_user(username, password)) {
- res.code = 401;
+ res.code = res.code = static_cast<int>(HttpRespCode::UNAUTHORIZED);
} else {
auto& context =
app.template get_context<PersistentData::Middleware>(req);
@@ -207,10 +278,11 @@ void request_routes(Crow<Middlewares...>& app) {
nlohmann::json ret{{"data", "User '" + username + "' logged in"},
{"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", "XSRF-TOKEN=" + session.csrf_token);
+
res.write(ret.dump());
} else {
// if content type is json, assume json token
@@ -222,10 +294,27 @@ void request_routes(Crow<Middlewares...>& app) {
}
} else {
- res.code = 400;
+ res.code = static_cast<int>(HttpRespCode::BAD_REQUEST);
}
res.end();
});
+
+ 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;
+
+ });
}
} // namespaec TokenAuthorization
} // namespace crow