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