blob: ebbb23bd748779ba2638f1462bea6450a0797370 [file] [log] [blame]
#ifndef THIRD_PARTY_GBMCWEB_INCLUDE_AUTHENTICATION_H_
#define THIRD_PARTY_GBMCWEB_INCLUDE_AUTHENTICATION_H_
#include <memory>
#include <string_view>
#include <string>
#include "boost/container/flat_set.hpp" // NOLINT
#include "common.hpp" // NOLINT
#include "http_request.hpp"
#include "http_response.hpp"
#include "logging.hpp"
#include "forward_unauthorized.hpp" // NOLINT
#include "http_utility.hpp" // NOLINT
#include "pam_authenticate.hpp" // NOLINT
#include "sessions.hpp"
#include "webroutes.hpp"
namespace crow {
namespace authentication {
inline void cleanupTempSession(const Request& req) {
// 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 (req.session != nullptr &&
req.session->persistence ==
persistent_data::PersistenceType::SINGLE_REQUEST) {
persistent_data::SessionStore::getInstance().removeSession(req.session);
}
}
#ifdef BMCWEB_ENABLE_BASIC_AUTHENTICATION
static std::shared_ptr<persistent_data::UserSession> performBasicAuth(
const boost::asio::ip::address& clientIp, std::string_view auth_header) {
BMCWEB_LOG_DEBUG << "[AuthMiddleware] Basic authentication";
if (!auth_header.starts_with("Basic ")) {
return nullptr;
}
std::string_view param = auth_header.substr(strlen("Basic "));
std::string authData;
if (!crow::utility::base64Decode(param, authData)) {
return nullptr;
}
std::size_t separator = authData.find(':');
if (separator == std::string::npos) {
return nullptr;
}
std::string user = authData.substr(0, separator);
separator += 1;
if (separator > authData.size()) {
return nullptr;
}
std::string pass = authData.substr(separator);
BMCWEB_LOG_DEBUG << "[AuthMiddleware] Authenticating user: " << user;
BMCWEB_LOG_DEBUG << "[AuthMiddleware] User IPAddress: "
<< clientIp.to_string();
int pamrc = pamAuthenticateUser(user, pass);
bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD;
if ((pamrc != PAM_SUCCESS) && !isConfigureSelfOnly) {
return nullptr;
}
// TODO(ed) generateUserSession 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 persistent_data::SessionStore::getInstance().generateUserSession(
user, clientIp, std::nullopt,
persistent_data::PersistenceType::SINGLE_REQUEST, isConfigureSelfOnly);
}
#endif
#ifdef BMCWEB_ENABLE_SESSION_AUTHENTICATION
static std::shared_ptr<persistent_data::UserSession> performTokenAuth(
std::string_view auth_header) {
BMCWEB_LOG_DEBUG << "[AuthMiddleware] Token authentication";
if (!auth_header.starts_with("Token ")) {
return nullptr;
}
std::string_view token = auth_header.substr(strlen("Token "));
auto session_out =
persistent_data::SessionStore::getInstance().loginSessionByToken(token);
return session_out;
}
#endif
#ifdef BMCWEB_ENABLE_XTOKEN_AUTHENTICATION
static std::shared_ptr<persistent_data::UserSession> performXtokenAuth(
const boost::beast::http::header<true>& reqHeader) {
BMCWEB_LOG_DEBUG << "[AuthMiddleware] X-Auth-Token authentication";
std::string_view token = reqHeader["X-Auth-Token"];
if (token.empty()) {
return nullptr;
}
auto session_out =
persistent_data::SessionStore::getInstance().loginSessionByToken(token);
return session_out;
}
#endif
#ifdef BMCWEB_ENABLE_COOKIE_AUTHENTICATION
static std::shared_ptr<persistent_data::UserSession> performCookieAuth(
boost::beast::http::verb method,
const boost::beast::http::header<true>& reqHeader) {
BMCWEB_LOG_DEBUG << "[AuthMiddleware] Cookie authentication";
std::string_view cookieValue = reqHeader["Cookie"];
if (cookieValue.empty()) {
return nullptr;
}
auto startIndex = cookieValue.find("SESSION=");
if (startIndex == std::string::npos) {
return nullptr;
}
startIndex += sizeof("SESSION=") - 1;
auto endIndex = cookieValue.find(';', startIndex);
if (endIndex == std::string::npos) {
endIndex = cookieValue.size();
}
std::string_view authKey =
cookieValue.substr(startIndex, endIndex - startIndex);
std::shared_ptr<persistent_data::UserSession> session_out =
persistent_data::SessionStore::getInstance().loginSessionByToken(authKey);
if (session_out == nullptr) {
return nullptr;
}
#ifndef BMCWEB_INSECURE_DISABLE_CSRF_PREVENTION
// RFC7231 defines methods that need csrf protection
if (method != boost::beast::http::verb::get) {
std::string_view csrf = reqHeader["X-XSRF-TOKEN"];
// Make sure both tokens are filled
if (csrf.empty() || session_out->csrfToken.empty()) {
return nullptr;
}
if (csrf.size() != persistent_data::sessionTokenSize) {
return nullptr;
}
// Reject if csrf token not available
if (!crow::utility::constantTimeStringCompare(csrf,
session_out->csrfToken)) {
return nullptr;
}
}
#endif
return session_out;
}
#endif
#ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
static std::shared_ptr<persistent_data::UserSession> performTLSAuth(
Response& res, const boost::beast::http::header<true>& reqHeader,
const std::weak_ptr<persistent_data::UserSession>& session) {
if (auto sp = session.lock()) {
// set cookie only if this is req from the browser.
if (reqHeader["User-Agent"].empty()) {
BMCWEB_LOG_DEBUG << " TLS session: " << sp->uniqueId
<< " will be used for this request.";
return sp;
}
// TODO: change this to not switch to cookie auth
res.addHeader("Set-Cookie",
"XSRF-TOKEN=" + sp->csrfToken +
"; SameSite=Strict; Secure\r\nSet-Cookie: SESSION=" +
sp->sessionToken +
"; SameSite=Strict; Secure; HttpOnly\r\nSet-Cookie: "
"IsAuthenticated=true; Secure");
BMCWEB_LOG_DEBUG << " TLS session: " << sp->uniqueId
<< " with cookie will be used for this request.";
return sp;
}
return nullptr;
}
#endif
// checks if request can be forwarded without authentication
[[maybe_unused]] static bool isOnAllowlist(std::string_view url,
boost::beast::http::verb method) {
if (boost::beast::http::verb::get == method) {
if (url == "/redfish/v1" || url == "/redfish/v1/" || url == "/redfish" ||
url == "/redfish/" || url == "/redfish/v1/odata" ||
url == "/redfish/v1/odata/") {
return true;
}
if (crow::webroutes::routes.find(std::string(url)) !=
crow::webroutes::routes.end()) {
return true;
}
}
// it's allowed to POST on session collection & login without
// authentication
if (boost::beast::http::verb::post == method) {
if ((url == "/redfish/v1/SessionService/Sessions") ||
(url == "/redfish/v1/SessionService/Sessions/") ||
(url == "/redfish/v1/SessionService/Sessions/Members") ||
(url == "/redfish/v1/SessionService/Sessions/Members/") ||
(url == "/login")) {
return true;
}
}
return false;
}
[[maybe_unused]] static std::shared_ptr<persistent_data::UserSession>
authenticate(
const boost::asio::ip::address& ipAddress [[maybe_unused]],
Response& res [[maybe_unused]],
boost::beast::http::verb method [[maybe_unused]],
const boost::beast::http::header<true>& reqHeader,
[[maybe_unused]] const std::shared_ptr<persistent_data::UserSession>&
session) {
const persistent_data::AuthConfigMethods& auth_methods_config =
persistent_data::SessionStore::getInstance().getAuthMethodsConfig();
std::shared_ptr<persistent_data::UserSession> session_out = nullptr;
#ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
if (auth_methods_config.tls) {
session_out = performTLSAuth(res, reqHeader, session);
}
#endif
#ifdef BMCWEB_ENABLE_XTOKEN_AUTHENTICATION
if (session_out == nullptr && auth_methods_config.xtoken) {
session_out = performXtokenAuth(reqHeader);
}
#endif
#ifdef BMCWEB_ENABLE_COOKIE_AUTHENTICATION
if (session_out == nullptr && auth_methods_config.cookie) {
session_out = performCookieAuth(method, reqHeader);
}
#endif
std::string_view auth_header = reqHeader["Authorization"];
BMCWEB_LOG_DEBUG << "auth_header=" << auth_header;
if (session_out == nullptr && auth_methods_config.sessionToken) {
#ifdef BMCWEB_ENABLE_SESSION_AUTHENTICATION
session_out = performTokenAuth(auth_header);
#endif
}
if (session_out == nullptr && auth_methods_config.basic) {
#ifdef BMCWEB_ENABLE_BASIC_AUTHENTICATION
session_out = performBasicAuth(ipAddress, auth_header);
#endif
}
if (session_out != nullptr) {
return session_out;
}
return nullptr;
}
} // namespace authentication
} // namespace crow
#endif // THIRD_PARTY_GBMCWEB_INCLUDE_AUTHENTICATION_H_