diff options
author | Ed Tanous <ed.tanous@intel.com> | 2018-03-28 03:41:04 +0300 |
---|---|---|
committer | Ed Tanous <ed.tanous@intel.com> | 2018-06-29 21:18:14 +0300 |
commit | e0d918bc397350aa21af3dab9faa6e21748f6373 (patch) | |
tree | bdc13d1e9937c0f153e89f3ab1be050d4bf52ee7 /include/token_authorization_middleware.hpp | |
parent | 77dd8813e9a1f2baec63d959de15af39a8cd12d0 (diff) | |
download | bmcweb-e0d918bc397350aa21af3dab9faa6e21748f6373.tar.xz |
Boost beast
This commit is the beginings of attempting to transition away from
crow, and toward boost::beast. Unit tests are passing, and
implementation appears to be slightly faster than crow.
Change-Id: Ic8d946dc7a04f514c67b1098f181eee1ced69171
Diffstat (limited to 'include/token_authorization_middleware.hpp')
-rw-r--r-- | include/token_authorization_middleware.hpp | 187 |
1 files changed, 102 insertions, 85 deletions
diff --git a/include/token_authorization_middleware.hpp b/include/token_authorization_middleware.hpp index 07540d42b0..49649dd978 100644 --- a/include/token_authorization_middleware.hpp +++ b/include/token_authorization_middleware.hpp @@ -5,10 +5,9 @@ #include <webassets.hpp> #include <random> #include <crow/app.h> -#include <crow/http_codes.h> +#include <crow/common.h> #include <crow/http_request.h> #include <crow/http_response.h> -#include <boost/bimap.hpp> #include <boost/container/flat_set.hpp> namespace crow { @@ -18,7 +17,7 @@ namespace TokenAuthorization { class Middleware { public: struct context { - const crow::PersistentData::UserSession* session; + std::shared_ptr<crow::PersistentData::UserSession> session; }; void before_handle(crow::request& req, response& res, context& ctx) { @@ -27,25 +26,27 @@ class Middleware { } ctx.session = perform_xtoken_auth(req); - if (ctx.session == nullptr) { ctx.session = perform_cookie_auth(req); } - - const std::string& auth_header = req.get_header_value("Authorization"); - // Reject any kind of auth other than basic or token - if (ctx.session == nullptr && boost::starts_with(auth_header, "Token ")) { - ctx.session = perform_token_auth(auth_header); - } - - if (ctx.session == nullptr && boost::starts_with(auth_header, "Basic ")) { - ctx.session = perform_basic_auth(auth_header); + if (ctx.session == nullptr) { + boost::string_view auth_header = req.get_header_value("Authorization"); + if (!auth_header.empty()) { + // Reject any kind of auth other than basic or token + if (boost::starts_with(auth_header, "Token ")) { + ctx.session = perform_token_auth(auth_header); + } else if (boost::starts_with(auth_header, "Basic ")) { + ctx.session = perform_basic_auth(auth_header); + } + } } if (ctx.session == nullptr) { CROW_LOG_WARNING << "[AuthMiddleware] authorization failed"; - res.code = static_cast<int>(HttpRespCode::UNAUTHORIZED); + + res.result(boost::beast::http::status::unauthorized); res.add_header("WWW-Authenticate", "Basic"); + res.end(); return; } @@ -57,10 +58,10 @@ class Middleware { 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? + // 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) { @@ -69,12 +70,12 @@ class Middleware { } private: - const crow::PersistentData::UserSession* perform_basic_auth( - const std::string& auth_header) const { + const std::shared_ptr<crow::PersistentData::UserSession> perform_basic_auth( + boost::string_view auth_header) const { CROW_LOG_DEBUG << "[AuthMiddleware] Basic authentication"; std::string auth_data; - std::string param = auth_header.substr(strlen("Basic ")); + boost::string_view param = auth_header.substr(strlen("Basic ")); if (!crow::utility::base64_decode(param, auth_data)) { return nullptr; } @@ -102,24 +103,24 @@ class Middleware { // needed. // This whole flow needs to be revisited anyway, as we can't be // calling directly into pam for every request - return &(PersistentData::session_store->generate_user_session( - user, crow::PersistentData::PersistenceType::SINGLE_REQUEST)); + 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) const { + const std::shared_ptr<crow::PersistentData::UserSession> perform_token_auth( + boost::string_view auth_header) const { CROW_LOG_DEBUG << "[AuthMiddleware] Token authentication"; - std::string token = auth_header.substr(strlen("Token ")); + boost::string_view token = auth_header.substr(strlen("Token ")); auto session = PersistentData::session_store->login_session_by_token(token); return session; } - const crow::PersistentData::UserSession* perform_xtoken_auth( + const std::shared_ptr<crow::PersistentData::UserSession> perform_xtoken_auth( const crow::request& req) const { CROW_LOG_DEBUG << "[AuthMiddleware] X-Auth-Token authentication"; - const std::string& token = req.get_header_value("X-Auth-Token"); + boost::string_view token = req.get_header_value("X-Auth-Token"); if (token.empty()) { return nullptr; } @@ -127,11 +128,11 @@ class Middleware { return session; } - const crow::PersistentData::UserSession* perform_cookie_auth( + const std::shared_ptr<crow::PersistentData::UserSession> perform_cookie_auth( const crow::request& req) const { CROW_LOG_DEBUG << "[AuthMiddleware] Cookie authentication"; - auto& cookie_value = req.get_header_value("Cookie"); + boost::string_view cookie_value = req.get_header_value("Cookie"); if (cookie_value.empty()) { return nullptr; } @@ -145,18 +146,18 @@ class Middleware { if (end_index == std::string::npos) { end_index = cookie_value.size(); } - std::string auth_key = + boost::string_view auth_key = cookie_value.substr(start_index, end_index - start_index); - const crow::PersistentData::UserSession* session = + const std::shared_ptr<crow::PersistentData::UserSession> session = PersistentData::session_store->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"); + if (req.method() != "GET"_method) { + boost::string_view csrf = req.get_header_value("X-XSRF-TOKEN"); // Make sure both tokens are filled if (csrf.empty() || session->csrf_token.empty()) { return nullptr; @@ -171,21 +172,21 @@ class Middleware { // 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) { - CROW_LOG_DEBUG << "TESTING ROUTE " << req.url; + // it's allowed to GET root node without authentica tion + if ("GET"_method == req.method()) { if (req.url == "/redfish/v1" || req.url == "/redfish/v1/") { return true; - } else if (crow::webassets::routes.find(req.url) != + } else if (crow::webassets::routes.find(std::string(req.url)) != crow::webassets::routes.end()) { return true; } } - // 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 == "/redfish/v1/SessionService/Sessions/") || + // 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 == "/redfish/v1/SessionService/Sessions/") || (req.url == "/login") || (req.url == "/logout")) { return true; } @@ -207,35 +208,38 @@ void request_routes(Crow<Middlewares...>& app) { CROW_ROUTE(app, "/login") .methods( "POST"_method)([&](const crow::request& req, crow::response& res) { - std::string content_type; - auto content_type_it = req.headers.find("content-type"); - if (content_type_it != req.headers.end()) { - content_type = content_type_it->second; - boost::algorithm::to_lower(content_type); - } - const std::string* username; - const std::string* password; + boost::string_view content_type = req.get_header_value("content-type"); + boost::string_view username; + boost::string_view password; + bool looks_like_ibm = false; - // This object needs to be declared at this scope so the strings within - // it are not destroyed before we can use them + // This object needs to be declared at this scope so the strings + // within it are not destroyed before we can use them nlohmann::json login_credentials; // Check if auth was provided by a payload if (content_type == "application/json") { login_credentials = nlohmann::json::parse(req.body, nullptr, false); if (login_credentials.is_discarded()) { - res.code = 400; + res.result(boost::beast::http::status::bad_request); res.end(); return; } + // check for username/password in the root object // THis method is how intel APIs authenticate - auto user_it = login_credentials.find("username"); - auto pass_it = login_credentials.find("password"); + nlohmann::json::iterator user_it = login_credentials.find("username"); + nlohmann::json::iterator pass_it = login_credentials.find("password"); if (user_it != login_credentials.end() && pass_it != login_credentials.end()) { - username = user_it->get_ptr<const std::string*>(); - password = pass_it->get_ptr<const std::string*>(); + const std::string* user_str = + user_it->get_ptr<const std::string*>(); + const std::string* pass_str = + pass_it->get_ptr<const std::string*>(); + if (user_str != nullptr && pass_str != nullptr) { + username = *user_str; + password = *pass_str; + } } else { // Openbmc appears to push a data object that contains the same // keys (username and password), attempt to use that @@ -245,56 +249,70 @@ void request_routes(Crow<Middlewares...>& app) { // "password"] if (data_it->is_array()) { if (data_it->size() == 2) { - username = (*data_it)[0].get_ptr<const std::string*>(); - password = (*data_it)[1].get_ptr<const std::string*>(); + nlohmann::json::iterator user_it2 = data_it->begin(); + nlohmann::json::iterator pass_it2 = data_it->begin() + 1; looks_like_ibm = true; + if (user_it2 != data_it->end() && + pass_it2 != data_it->end()) { + const std::string* user_str = + user_it2->get_ptr<const std::string*>(); + const std::string* pass_str = + pass_it2->get_ptr<const std::string*>(); + if (user_str != nullptr && pass_str != nullptr) { + username = *user_str; + password = *pass_str; + } + } } + } else if (data_it->is_object()) { - auto user_it = data_it->find("username"); - auto pass_it = data_it->find("password"); - if (user_it != data_it->end() && pass_it != data_it->end()) { - username = user_it->get_ptr<const std::string*>(); - password = pass_it->get_ptr<const std::string*>(); + nlohmann::json::iterator user_it2 = data_it->find("username"); + nlohmann::json::iterator pass_it2 = data_it->find("password"); + if (user_it2 != data_it->end() && pass_it2 != data_it->end()) { + const std::string* user_str = + user_it2->get_ptr<const std::string*>(); + const std::string* pass_str = + pass_it2->get_ptr<const std::string*>(); + if (user_str != nullptr && pass_str != nullptr) { + username = *user_str; + password = *pass_str; + } } } } } } else { - // check if auth was provided as a query string - auto user_it = req.headers.find("username"); - auto pass_it = req.headers.find("password"); - if (user_it != req.headers.end() && pass_it != req.headers.end()) { - username = &user_it->second; - password = &pass_it->second; - } + // check if auth was provided as a headers + username = req.get_header_value("username"); + password = req.get_header_value("password"); } - if (username != nullptr && !username->empty() && password != nullptr && - !password->empty()) { - if (!pam_authenticate_user(*username, *password)) { - res.code = res.code = static_cast<int>(HttpRespCode::UNAUTHORIZED); + if (!username.empty() && !password.empty()) { + if (!pam_authenticate_user(username, password)) { + res.result(boost::beast::http::status::unauthorized); } else { - auto& session = - PersistentData::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 // actually look at the status code. TODO(ed).... Fix that // upstream - res.json_value = {{"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 + + res.json_value = { + {"data", "User '" + std::string(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"); } else { // if content type is json, assume json token - res.json_value = {{"token", session.session_token}}; + res.json_value = {{"token", session->session_token}}; } } } else { - res.code = static_cast<int>(HttpRespCode::BAD_REQUEST); + res.result(boost::beast::http::status::bad_request); } res.end(); }); @@ -308,7 +326,6 @@ void request_routes(Crow<Middlewares...>& app) { if (session != nullptr) { PersistentData::session_store->remove_session(session); } - res.code = static_cast<int>(HttpRespCode::OK); res.end(); return; |