1d055a34aSNan Zhou #pragma once 2d055a34aSNan Zhou 33ccb3adbSEd Tanous #include "common.hpp" 43ccb3adbSEd Tanous #include "forward_unauthorized.hpp" 53ccb3adbSEd Tanous #include "http_request.hpp" 63ccb3adbSEd Tanous #include "http_response.hpp" 73ccb3adbSEd Tanous #include "http_utility.hpp" 83ccb3adbSEd Tanous #include "pam_authenticate.hpp" 9d055a34aSNan Zhou #include "webroutes.hpp" 10d055a34aSNan Zhou 11d055a34aSNan Zhou #include <boost/container/flat_set.hpp> 12d055a34aSNan Zhou 13d055a34aSNan Zhou #include <random> 14d055a34aSNan Zhou #include <utility> 15d055a34aSNan Zhou 16d055a34aSNan Zhou namespace crow 17d055a34aSNan Zhou { 18d055a34aSNan Zhou 19d055a34aSNan Zhou namespace authentication 20d055a34aSNan Zhou { 21d055a34aSNan Zhou 22b1d736fcSEd Tanous inline void cleanupTempSession(const Request& req) 23d055a34aSNan Zhou { 24d055a34aSNan Zhou // TODO(ed) THis should really be handled by the persistent data 25d055a34aSNan Zhou // middleware, but because it is upstream, it doesn't have access to the 26d055a34aSNan Zhou // session information. Should the data middleware persist the current 27d055a34aSNan Zhou // user session? 28d055a34aSNan Zhou if (req.session != nullptr && 29d055a34aSNan Zhou req.session->persistence == 30d055a34aSNan Zhou persistent_data::PersistenceType::SINGLE_REQUEST) 31d055a34aSNan Zhou { 32d055a34aSNan Zhou persistent_data::SessionStore::getInstance().removeSession(req.session); 33d055a34aSNan Zhou } 34d055a34aSNan Zhou } 35d055a34aSNan Zhou 36d055a34aSNan Zhou #ifdef BMCWEB_ENABLE_BASIC_AUTHENTICATION 37d055a34aSNan Zhou static std::shared_ptr<persistent_data::UserSession> 38d055a34aSNan Zhou performBasicAuth(const boost::asio::ip::address& clientIp, 39d055a34aSNan Zhou std::string_view authHeader) 40d055a34aSNan Zhou { 41d055a34aSNan Zhou BMCWEB_LOG_DEBUG << "[AuthMiddleware] Basic authentication"; 42d055a34aSNan Zhou 4311ba3979SEd Tanous if (!authHeader.starts_with("Basic ")) 44d055a34aSNan Zhou { 45d055a34aSNan Zhou return nullptr; 46d055a34aSNan Zhou } 47d055a34aSNan Zhou 48d055a34aSNan Zhou std::string_view param = authHeader.substr(strlen("Basic ")); 49d055a34aSNan Zhou std::string authData; 50d055a34aSNan Zhou 51d055a34aSNan Zhou if (!crow::utility::base64Decode(param, authData)) 52d055a34aSNan Zhou { 53d055a34aSNan Zhou return nullptr; 54d055a34aSNan Zhou } 55d055a34aSNan Zhou std::size_t separator = authData.find(':'); 56d055a34aSNan Zhou if (separator == std::string::npos) 57d055a34aSNan Zhou { 58d055a34aSNan Zhou return nullptr; 59d055a34aSNan Zhou } 60d055a34aSNan Zhou 61d055a34aSNan Zhou std::string user = authData.substr(0, separator); 62d055a34aSNan Zhou separator += 1; 63d055a34aSNan Zhou if (separator > authData.size()) 64d055a34aSNan Zhou { 65d055a34aSNan Zhou return nullptr; 66d055a34aSNan Zhou } 67d055a34aSNan Zhou std::string pass = authData.substr(separator); 68d055a34aSNan Zhou 69d055a34aSNan Zhou BMCWEB_LOG_DEBUG << "[AuthMiddleware] Authenticating user: " << user; 70d055a34aSNan Zhou BMCWEB_LOG_DEBUG << "[AuthMiddleware] User IPAddress: " 71d055a34aSNan Zhou << clientIp.to_string(); 72d055a34aSNan Zhou 73d055a34aSNan Zhou int pamrc = pamAuthenticateUser(user, pass); 74d055a34aSNan Zhou bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD; 75d055a34aSNan Zhou if ((pamrc != PAM_SUCCESS) && !isConfigureSelfOnly) 76d055a34aSNan Zhou { 77d055a34aSNan Zhou return nullptr; 78d055a34aSNan Zhou } 79d055a34aSNan Zhou 80d055a34aSNan Zhou // TODO(ed) generateUserSession is a little expensive for basic 81d055a34aSNan Zhou // auth, as it generates some random identifiers that will never be 82d055a34aSNan Zhou // used. This should have a "fast" path for when user tokens aren't 83d055a34aSNan Zhou // needed. 84d055a34aSNan Zhou // This whole flow needs to be revisited anyway, as we can't be 85d055a34aSNan Zhou // calling directly into pam for every request 86d055a34aSNan Zhou return persistent_data::SessionStore::getInstance().generateUserSession( 87bb759e3aSEd Tanous user, clientIp, std::nullopt, 88d055a34aSNan Zhou persistent_data::PersistenceType::SINGLE_REQUEST, isConfigureSelfOnly); 89d055a34aSNan Zhou } 90d055a34aSNan Zhou #endif 91d055a34aSNan Zhou 92d055a34aSNan Zhou #ifdef BMCWEB_ENABLE_SESSION_AUTHENTICATION 93d055a34aSNan Zhou static std::shared_ptr<persistent_data::UserSession> 94d055a34aSNan Zhou performTokenAuth(std::string_view authHeader) 95d055a34aSNan Zhou { 96d055a34aSNan Zhou BMCWEB_LOG_DEBUG << "[AuthMiddleware] Token authentication"; 9711ba3979SEd Tanous if (!authHeader.starts_with("Token ")) 98d055a34aSNan Zhou { 99d055a34aSNan Zhou return nullptr; 100d055a34aSNan Zhou } 101d055a34aSNan Zhou std::string_view token = authHeader.substr(strlen("Token ")); 102d055a34aSNan Zhou auto sessionOut = 103d055a34aSNan Zhou persistent_data::SessionStore::getInstance().loginSessionByToken(token); 104d055a34aSNan Zhou return sessionOut; 105d055a34aSNan Zhou } 106d055a34aSNan Zhou #endif 107d055a34aSNan Zhou 108d055a34aSNan Zhou #ifdef BMCWEB_ENABLE_XTOKEN_AUTHENTICATION 109d055a34aSNan Zhou static std::shared_ptr<persistent_data::UserSession> 110d055a34aSNan Zhou performXtokenAuth(const boost::beast::http::header<true>& reqHeader) 111d055a34aSNan Zhou { 112d055a34aSNan Zhou BMCWEB_LOG_DEBUG << "[AuthMiddleware] X-Auth-Token authentication"; 113d055a34aSNan Zhou 114d055a34aSNan Zhou std::string_view token = reqHeader["X-Auth-Token"]; 115d055a34aSNan Zhou if (token.empty()) 116d055a34aSNan Zhou { 117d055a34aSNan Zhou return nullptr; 118d055a34aSNan Zhou } 119d055a34aSNan Zhou auto sessionOut = 120d055a34aSNan Zhou persistent_data::SessionStore::getInstance().loginSessionByToken(token); 121d055a34aSNan Zhou return sessionOut; 122d055a34aSNan Zhou } 123d055a34aSNan Zhou #endif 124d055a34aSNan Zhou 125d055a34aSNan Zhou #ifdef BMCWEB_ENABLE_COOKIE_AUTHENTICATION 126d055a34aSNan Zhou static std::shared_ptr<persistent_data::UserSession> 1271d869608SEd Tanous performCookieAuth(boost::beast::http::verb method [[maybe_unused]], 128d055a34aSNan Zhou const boost::beast::http::header<true>& reqHeader) 129d055a34aSNan Zhou { 130d055a34aSNan Zhou BMCWEB_LOG_DEBUG << "[AuthMiddleware] Cookie authentication"; 131d055a34aSNan Zhou 132d055a34aSNan Zhou std::string_view cookieValue = reqHeader["Cookie"]; 133d055a34aSNan Zhou if (cookieValue.empty()) 134d055a34aSNan Zhou { 135d055a34aSNan Zhou return nullptr; 136d055a34aSNan Zhou } 137d055a34aSNan Zhou 138d055a34aSNan Zhou auto startIndex = cookieValue.find("SESSION="); 139d055a34aSNan Zhou if (startIndex == std::string::npos) 140d055a34aSNan Zhou { 141d055a34aSNan Zhou return nullptr; 142d055a34aSNan Zhou } 143d055a34aSNan Zhou startIndex += sizeof("SESSION=") - 1; 144d055a34aSNan Zhou auto endIndex = cookieValue.find(';', startIndex); 145d055a34aSNan Zhou if (endIndex == std::string::npos) 146d055a34aSNan Zhou { 147d055a34aSNan Zhou endIndex = cookieValue.size(); 148d055a34aSNan Zhou } 14989492a15SPatrick Williams std::string_view authKey = cookieValue.substr(startIndex, 15089492a15SPatrick Williams endIndex - startIndex); 151d055a34aSNan Zhou 152d055a34aSNan Zhou std::shared_ptr<persistent_data::UserSession> sessionOut = 153d055a34aSNan Zhou persistent_data::SessionStore::getInstance().loginSessionByToken( 154d055a34aSNan Zhou authKey); 155d055a34aSNan Zhou if (sessionOut == nullptr) 156d055a34aSNan Zhou { 157d055a34aSNan Zhou return nullptr; 158d055a34aSNan Zhou } 159d055a34aSNan Zhou #ifndef BMCWEB_INSECURE_DISABLE_CSRF_PREVENTION 160d055a34aSNan Zhou // RFC7231 defines methods that need csrf protection 161d055a34aSNan Zhou if (method != boost::beast::http::verb::get) 162d055a34aSNan Zhou { 163d055a34aSNan Zhou std::string_view csrf = reqHeader["X-XSRF-TOKEN"]; 164d055a34aSNan Zhou // Make sure both tokens are filled 165d055a34aSNan Zhou if (csrf.empty() || sessionOut->csrfToken.empty()) 166d055a34aSNan Zhou { 167d055a34aSNan Zhou return nullptr; 168d055a34aSNan Zhou } 169d055a34aSNan Zhou 170d055a34aSNan Zhou if (csrf.size() != persistent_data::sessionTokenSize) 171d055a34aSNan Zhou { 172d055a34aSNan Zhou return nullptr; 173d055a34aSNan Zhou } 174d055a34aSNan Zhou // Reject if csrf token not available 175d055a34aSNan Zhou if (!crow::utility::constantTimeStringCompare(csrf, 176d055a34aSNan Zhou sessionOut->csrfToken)) 177d055a34aSNan Zhou { 178d055a34aSNan Zhou return nullptr; 179d055a34aSNan Zhou } 180d055a34aSNan Zhou } 181d055a34aSNan Zhou #endif 182d055a34aSNan Zhou return sessionOut; 183d055a34aSNan Zhou } 184d055a34aSNan Zhou #endif 185d055a34aSNan Zhou 186d055a34aSNan Zhou #ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION 187d055a34aSNan Zhou static std::shared_ptr<persistent_data::UserSession> 188d055a34aSNan Zhou performTLSAuth(Response& res, 189d055a34aSNan Zhou const boost::beast::http::header<true>& reqHeader, 190d055a34aSNan Zhou const std::weak_ptr<persistent_data::UserSession>& session) 191d055a34aSNan Zhou { 192d055a34aSNan Zhou if (auto sp = session.lock()) 193d055a34aSNan Zhou { 194d055a34aSNan Zhou // set cookie only if this is req from the browser. 195d055a34aSNan Zhou if (reqHeader["User-Agent"].empty()) 196d055a34aSNan Zhou { 197d055a34aSNan Zhou BMCWEB_LOG_DEBUG << " TLS session: " << sp->uniqueId 198d055a34aSNan Zhou << " will be used for this request."; 199d055a34aSNan Zhou return sp; 200d055a34aSNan Zhou } 201d055a34aSNan Zhou // TODO: change this to not switch to cookie auth 202*994fd86aSEd Tanous res.addHeader(boost::beast::http::field::set_cookie, 203d055a34aSNan Zhou "XSRF-TOKEN=" + sp->csrfToken + 204*994fd86aSEd Tanous "; SameSite=Strict; Secure"); 205*994fd86aSEd Tanous res.addHeader(boost::beast::http::field::set_cookie, 206*994fd86aSEd Tanous "SESSION=" + sp->sessionToken + 207*994fd86aSEd Tanous "; SameSite=Strict; Secure; HttpOnly"); 208*994fd86aSEd Tanous res.addHeader(boost::beast::http::field::set_cookie, 209d055a34aSNan Zhou "IsAuthenticated=true; Secure"); 210d055a34aSNan Zhou BMCWEB_LOG_DEBUG << " TLS session: " << sp->uniqueId 211d055a34aSNan Zhou << " with cookie will be used for this request."; 212d055a34aSNan Zhou return sp; 213d055a34aSNan Zhou } 214d055a34aSNan Zhou return nullptr; 215d055a34aSNan Zhou } 216d055a34aSNan Zhou #endif 217d055a34aSNan Zhou 218d055a34aSNan Zhou // checks if request can be forwarded without authentication 219d055a34aSNan Zhou [[maybe_unused]] static bool isOnAllowlist(std::string_view url, 220d055a34aSNan Zhou boost::beast::http::verb method) 221d055a34aSNan Zhou { 222d055a34aSNan Zhou if (boost::beast::http::verb::get == method) 223d055a34aSNan Zhou { 224d055a34aSNan Zhou if (url == "/redfish/v1" || url == "/redfish/v1/" || 225d055a34aSNan Zhou url == "/redfish" || url == "/redfish/" || 226d055a34aSNan Zhou url == "/redfish/v1/odata" || url == "/redfish/v1/odata/") 227d055a34aSNan Zhou { 228d055a34aSNan Zhou return true; 229d055a34aSNan Zhou } 230d055a34aSNan Zhou if (crow::webroutes::routes.find(std::string(url)) != 231d055a34aSNan Zhou crow::webroutes::routes.end()) 232d055a34aSNan Zhou { 233d055a34aSNan Zhou return true; 234d055a34aSNan Zhou } 235d055a34aSNan Zhou } 236d055a34aSNan Zhou 237d055a34aSNan Zhou // it's allowed to POST on session collection & login without 238d055a34aSNan Zhou // authentication 239d055a34aSNan Zhou if (boost::beast::http::verb::post == method) 240d055a34aSNan Zhou { 241d055a34aSNan Zhou if ((url == "/redfish/v1/SessionService/Sessions") || 242d055a34aSNan Zhou (url == "/redfish/v1/SessionService/Sessions/") || 243d055a34aSNan Zhou (url == "/redfish/v1/SessionService/Sessions/Members") || 244d055a34aSNan Zhou (url == "/redfish/v1/SessionService/Sessions/Members/") || 245d055a34aSNan Zhou (url == "/login")) 246d055a34aSNan Zhou { 247d055a34aSNan Zhou return true; 248d055a34aSNan Zhou } 249d055a34aSNan Zhou } 250d055a34aSNan Zhou 251d055a34aSNan Zhou return false; 252d055a34aSNan Zhou } 253d055a34aSNan Zhou 254d055a34aSNan Zhou [[maybe_unused]] static std::shared_ptr<persistent_data::UserSession> 255d055a34aSNan Zhou authenticate( 25602cad96eSEd Tanous const boost::asio::ip::address& ipAddress [[maybe_unused]], 2573acced2cSNan Zhou Response& res [[maybe_unused]], 2583acced2cSNan Zhou boost::beast::http::verb method [[maybe_unused]], 259d055a34aSNan Zhou const boost::beast::http::header<true>& reqHeader, 260d055a34aSNan Zhou [[maybe_unused]] const std::shared_ptr<persistent_data::UserSession>& 261d055a34aSNan Zhou session) 262d055a34aSNan Zhou { 263d055a34aSNan Zhou const persistent_data::AuthConfigMethods& authMethodsConfig = 264d055a34aSNan Zhou persistent_data::SessionStore::getInstance().getAuthMethodsConfig(); 265d055a34aSNan Zhou 266d055a34aSNan Zhou std::shared_ptr<persistent_data::UserSession> sessionOut = nullptr; 267d055a34aSNan Zhou #ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION 268d055a34aSNan Zhou if (authMethodsConfig.tls) 269d055a34aSNan Zhou { 270d055a34aSNan Zhou sessionOut = performTLSAuth(res, reqHeader, session); 271d055a34aSNan Zhou } 272d055a34aSNan Zhou #endif 273d055a34aSNan Zhou #ifdef BMCWEB_ENABLE_XTOKEN_AUTHENTICATION 274d055a34aSNan Zhou if (sessionOut == nullptr && authMethodsConfig.xtoken) 275d055a34aSNan Zhou { 276d055a34aSNan Zhou sessionOut = performXtokenAuth(reqHeader); 277d055a34aSNan Zhou } 278d055a34aSNan Zhou #endif 279d055a34aSNan Zhou #ifdef BMCWEB_ENABLE_COOKIE_AUTHENTICATION 280d055a34aSNan Zhou if (sessionOut == nullptr && authMethodsConfig.cookie) 281d055a34aSNan Zhou { 282d055a34aSNan Zhou sessionOut = performCookieAuth(method, reqHeader); 283d055a34aSNan Zhou } 284d055a34aSNan Zhou #endif 285d055a34aSNan Zhou std::string_view authHeader = reqHeader["Authorization"]; 286d055a34aSNan Zhou BMCWEB_LOG_DEBUG << "authHeader=" << authHeader; 287d055a34aSNan Zhou 288d055a34aSNan Zhou if (sessionOut == nullptr && authMethodsConfig.sessionToken) 289d055a34aSNan Zhou { 290d055a34aSNan Zhou #ifdef BMCWEB_ENABLE_SESSION_AUTHENTICATION 291d055a34aSNan Zhou sessionOut = performTokenAuth(authHeader); 292d055a34aSNan Zhou #endif 293d055a34aSNan Zhou } 294d055a34aSNan Zhou if (sessionOut == nullptr && authMethodsConfig.basic) 295d055a34aSNan Zhou { 296d055a34aSNan Zhou #ifdef BMCWEB_ENABLE_BASIC_AUTHENTICATION 297d055a34aSNan Zhou sessionOut = performBasicAuth(ipAddress, authHeader); 298d055a34aSNan Zhou #endif 299d055a34aSNan Zhou } 300d055a34aSNan Zhou if (sessionOut != nullptr) 301d055a34aSNan Zhou { 302d055a34aSNan Zhou return sessionOut; 303d055a34aSNan Zhou } 304d055a34aSNan Zhou 305d055a34aSNan Zhou return nullptr; 306d055a34aSNan Zhou } 307d055a34aSNan Zhou 308d055a34aSNan Zhou } // namespace authentication 309d055a34aSNan Zhou } // namespace crow 310