blob: f8e74b9af029910f0cea1014adc3a8f13adac909 [file] [log] [blame]
#pragma once
#include "webroutes.hpp"
#include <app.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/container/flat_set.hpp>
#include <common.hpp>
#include <forward_unauthorized.hpp>
#include <http_request.hpp>
#include <http_response.hpp>
#include <http_utility.hpp>
#include <pam_authenticate.hpp>
#include <random>
#include <utility>
namespace crow
{
namespace authorization
{
static void cleanupTempSession(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 authHeader)
{
BMCWEB_LOG_DEBUG << "[AuthMiddleware] Basic authentication";
if (!boost::starts_with(authHeader, "Basic "))
{
return nullptr;
}
std::string_view param = authHeader.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
std::string unsupportedClientId;
return persistent_data::SessionStore::getInstance().generateUserSession(
user, clientIp, unsupportedClientId,
persistent_data::PersistenceType::SINGLE_REQUEST, isConfigureSelfOnly);
}
#endif
#ifdef BMCWEB_ENABLE_SESSION_AUTHENTICATION
static std::shared_ptr<persistent_data::UserSession>
performTokenAuth(std::string_view authHeader)
{
BMCWEB_LOG_DEBUG << "[AuthMiddleware] Token authentication";
if (!boost::starts_with(authHeader, "Token "))
{
return nullptr;
}
std::string_view token = authHeader.substr(strlen("Token "));
auto sessionOut =
persistent_data::SessionStore::getInstance().loginSessionByToken(token);
return sessionOut;
}
#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 sessionOut =
persistent_data::SessionStore::getInstance().loginSessionByToken(token);
return sessionOut;
}
#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> sessionOut =
persistent_data::SessionStore::getInstance().loginSessionByToken(
authKey);
if (sessionOut == 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() || sessionOut->csrfToken.empty())
{
return nullptr;
}
if (csrf.size() != persistent_data::sessionTokenSize)
{
return nullptr;
}
// Reject if csrf token not available
if (!crow::utility::constantTimeStringCompare(csrf,
sessionOut->csrfToken))
{
return nullptr;
}
}
#endif
return sessionOut;
}
#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;
}
std::string_view cookieValue = reqHeader["Cookie"];
if (cookieValue.empty() ||
cookieValue.find("SESSION=") == std::string::npos)
{
// 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 == "/login"))
{
return true;
}
}
return false;
}
[[maybe_unused]] static std::shared_ptr<persistent_data::UserSession>
authenticate(
boost::asio::ip::address& ipAddress [[maybe_unused]],
Response& res [[maybe_unused]], boost::beast::http::verb method,
const boost::beast::http::header<true>& reqHeader,
[[maybe_unused]] const std::shared_ptr<persistent_data::UserSession>&
session)
{
const persistent_data::AuthConfigMethods& authMethodsConfig =
persistent_data::SessionStore::getInstance().getAuthMethodsConfig();
std::shared_ptr<persistent_data::UserSession> sessionOut = nullptr;
#ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
if (authMethodsConfig.tls)
{
sessionOut = performTLSAuth(res, reqHeader, session);
}
#endif
#ifdef BMCWEB_ENABLE_XTOKEN_AUTHENTICATION
if (sessionOut == nullptr && authMethodsConfig.xtoken)
{
sessionOut = performXtokenAuth(reqHeader);
}
#endif
#ifdef BMCWEB_ENABLE_COOKIE_AUTHENTICATION
if (sessionOut == nullptr && authMethodsConfig.cookie)
{
sessionOut = performCookieAuth(method, reqHeader);
}
#endif
std::string_view authHeader = reqHeader["Authorization"];
BMCWEB_LOG_DEBUG << "authHeader=" << authHeader;
if (sessionOut == nullptr && authMethodsConfig.sessionToken)
{
#ifdef BMCWEB_ENABLE_SESSION_AUTHENTICATION
sessionOut = performTokenAuth(authHeader);
#endif
}
if (sessionOut == nullptr && authMethodsConfig.basic)
{
#ifdef BMCWEB_ENABLE_BASIC_AUTHENTICATION
sessionOut = performBasicAuth(ipAddress, authHeader);
#endif
}
if (sessionOut != nullptr)
{
return sessionOut;
}
return nullptr;
}
} // namespace authorization
} // namespace crow