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