diff options
Diffstat (limited to 'include/token_authorization_middleware.hpp')
| -rw-r--r-- | include/token_authorization_middleware.hpp | 205 |
1 files changed, 104 insertions, 101 deletions
diff --git a/include/token_authorization_middleware.hpp b/include/token_authorization_middleware.hpp index eebd4f0..9642282 100644 --- a/include/token_authorization_middleware.hpp +++ b/include/token_authorization_middleware.hpp @@ -2,49 +2,48 @@ #include <base64.hpp> #include <pam_authenticate.hpp> +#include <persistent_data_middleware.hpp> #include <webassets.hpp> #include <random> #include <crow/app.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 {}; -using random_bytes_engine = - std::independent_bits_engine<std::random_device, CHAR_BIT, unsigned char>; - class Middleware { public: - Middleware() { - for (auto& route : crow::webassets::routes) { - allowed_routes.emplace(route); - } - allowed_routes.emplace("/login"); - }; - struct context {}; - - void before_handle(crow::request& req, response& res, context& ctx) { + 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 (allowed_routes.find(req.url.c_str()) != allowed_routes.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 @@ -53,6 +52,10 @@ class Middleware { 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) { @@ -62,36 +65,50 @@ class Middleware { start_index += 8; auto end_index = cookie_value.find(";", start_index); if (end_index == std::string::npos) { - end_index = cookie_value.size() - 1; + end_index = cookie_value.size(); } auth_key = - cookie_value.substr(start_index, end_index - start_index + 1); + cookie_value.substr(start_index, end_index - start_index); } } + require_csrf = true; // Cookies require CSRF } if (auth_key.empty()) { res.code = 400; res.end(); return; } - std::cout << "auth_key=" << auth_key << "\n"; - - for (auto& token : this->auth_tokens) { - std::cout << "token=" << token << "\n"; - } - - // TODO(ed), use span here instead of constructing a new string - if (this->auth_tokens.find(auth_key) == this->auth_tokens.end()) { + auto& data_mw = allctx.template get<PersistentData::Middleware>(); + auto session_it = data_mw.auth_tokens->find(auth_key); + if (session_it == data_mw.auth_tokens->end()) { return_unauthorized(); return; } - if (req.url == "/logout") { - this->auth_tokens.erase(auth_key); + 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_it->second.csrf_token.empty()) { + return_unauthorized(); + return; + } + // Reject if csrf token not available + if (csrf != session_it->second.csrf_token) { + return_unauthorized(); + return; + } + } + } + + if (req.url == "/logout" && req.method == "POST"_method) { + data_mw.auth_tokens->erase(auth_key); res.code = 200; res.end(); return; } + // else let the request continue unharmed } } @@ -100,19 +117,18 @@ class Middleware { // Do nothing } - boost::container::flat_set<std::string> auth_tokens; boost::container::flat_set<std::string> allowed_routes; - random_bytes_engine rbe; }; -// TODO(ed) see if there is a better way to allow middlewares to request routes. +// TODO(ed) see if there is a better way to allow middlewares to request +// routes. // Possibly an init function on first construction? template <typename... Middlewares> void request_routes(Crow<Middlewares...>& app) { - static_assert(black_magic::contains<TokenAuthorization::Middleware, - Middlewares...>::value, - "TokenAuthorization middleware must be enabled in app to use " - "auth routes"); + static_assert( + black_magic::contains<PersistentData::Middleware, Middlewares...>::value, + "TokenAuthorization middleware must be enabled in app to use " + "auth routes"); CROW_ROUTE(app, "/login") .methods( "POST"_method)([&](const crow::request& req, crow::response& res) { @@ -127,44 +143,44 @@ void request_routes(Crow<Middlewares...>& app) { bool looks_like_ibm = false; // Check if auth was provided by a payload if (content_type == "application/json") { - try { - auto login_credentials = nlohmann::json::parse(req.body); - // 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"); - if (user_it != login_credentials.end() && - pass_it != login_credentials.end()) { - username = user_it->get<const std::string>(); - password = pass_it->get<const std::string>(); - } else { - // Openbmc appears to push a data object that contains the same - // keys (username and password), attempt to use that - auto data_it = login_credentials.find("data"); - if (data_it != login_credentials.end()) { - // Some apis produce an array of value ["username", "password"] - if (data_it->is_array()) { - if (data_it->size() == 2) { - username = (*data_it)[0].get<const std::string>(); - password = (*data_it)[1].get<const std::string>(); - looks_like_ibm = true; - } - } 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<const std::string>(); - password = pass_it->get<const std::string>(); - } - } - } - } - } catch (...) { - // TODO(ed) figure out how to not throw on a bad json parse + 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 + // THis method is how intel APIs authenticate + 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()) { + username = user_it->get<const std::string>(); + password = pass_it->get<const std::string>(); + } else { + // Openbmc appears to push a data object that contains the same + // keys (username and password), attempt to use that + auto data_it = login_credentials.find("data"); + if (data_it != login_credentials.end()) { + // Some apis produce an array of value ["username", + // "password"] + if (data_it->is_array()) { + if (data_it->size() == 2) { + username = (*data_it)[0].get<const std::string>(); + password = (*data_it)[1].get<const std::string>(); + looks_like_ibm = true; + } + } 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<const std::string>(); + password = pass_it->get<const std::string>(); + } + } + } + } } else { // check if auth was provided as a query string auto user_it = req.headers.find("username"); @@ -179,41 +195,28 @@ void request_routes(Crow<Middlewares...>& app) { if (!pam_authenticate_user(username, password)) { res.code = 401; } else { - // THis should be a multiple of 3 to make sure that base64 doesn't - // end with an equals sign at the end. we could strip it off - // afterward - std::string token(30, 'a'); - // TODO(ed) for some reason clang-tidy finds a divide by zero - // error in cstdlibc here commented out for now. - // Needs investigation - auto& m = app.template get_middleware<Middleware>(); - std::generate(std::begin(token), std::end(token), - std::ref(m.rbe)); // NOLINT - std::string encoded_token; - if (!base64::base64_encode(token, encoded_token)) { - res.code = 500; + auto& auth_middleware = + app.template get_middleware<PersistentData::Middleware>(); + auto session = auth_middleware.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 + nlohmann::json ret{{"data", "User '" + username + "' logged in"}, + {"message", "200 OK"}, + {"status", "ok"}}; + 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 { - - m.auth_tokens.insert(encoded_token); - 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 - nlohmann::json ret{ - {"data", "User '" + username + "' logged in"}, - {"message", "200 OK"}, - {"status", "ok"}}; - res.add_header( - "Set-Cookie", - "SESSION=" + encoded_token + "; Secure; HttpOnly"); - res.write(ret.dump()); - } else { - // if content type is json, assume json token - nlohmann::json ret{{"token", encoded_token}}; + // if content type is json, assume json token + nlohmann::json ret{{"token", session.session_token}}; - res.write(ret.dump()); - res.add_header("Content-Type", "application/json"); - } + res.write(ret.dump()); + res.add_header("Content-Type", "application/json"); } } @@ -224,4 +227,4 @@ void request_routes(Crow<Middlewares...>& app) { }); } } // namespaec TokenAuthorization -} // namespace crow
\ No newline at end of file +} // namespace crow |

