summaryrefslogtreecommitdiffstats
path: root/include/token_authorization_middleware.hpp
diff options
context:
space:
mode:
Diffstat (limited to 'include/token_authorization_middleware.hpp')
-rw-r--r--include/token_authorization_middleware.hpp205
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
OpenPOWER on IntegriCloud