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> 127*1d869608SEd 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 202ade2fe78SKarol Niczyj res.addHeader("Set-Cookie", 203d055a34aSNan Zhou "XSRF-TOKEN=" + sp->csrfToken + 204d055a34aSNan Zhou "; SameSite=Strict; Secure\r\nSet-Cookie: SESSION=" + 205d055a34aSNan Zhou sp->sessionToken + 206d055a34aSNan Zhou "; SameSite=Strict; Secure; HttpOnly\r\nSet-Cookie: " 207d055a34aSNan Zhou "IsAuthenticated=true; Secure"); 208d055a34aSNan Zhou BMCWEB_LOG_DEBUG << " TLS session: " << sp->uniqueId 209d055a34aSNan Zhou << " with cookie will be used for this request."; 210d055a34aSNan Zhou return sp; 211d055a34aSNan Zhou } 212d055a34aSNan Zhou return nullptr; 213d055a34aSNan Zhou } 214d055a34aSNan Zhou #endif 215d055a34aSNan Zhou 216d055a34aSNan Zhou // checks if request can be forwarded without authentication 217d055a34aSNan Zhou [[maybe_unused]] static bool isOnAllowlist(std::string_view url, 218d055a34aSNan Zhou boost::beast::http::verb method) 219d055a34aSNan Zhou { 220d055a34aSNan Zhou if (boost::beast::http::verb::get == method) 221d055a34aSNan Zhou { 222d055a34aSNan Zhou if (url == "/redfish/v1" || url == "/redfish/v1/" || 223d055a34aSNan Zhou url == "/redfish" || url == "/redfish/" || 224d055a34aSNan Zhou url == "/redfish/v1/odata" || url == "/redfish/v1/odata/") 225d055a34aSNan Zhou { 226d055a34aSNan Zhou return true; 227d055a34aSNan Zhou } 228d055a34aSNan Zhou if (crow::webroutes::routes.find(std::string(url)) != 229d055a34aSNan Zhou crow::webroutes::routes.end()) 230d055a34aSNan Zhou { 231d055a34aSNan Zhou return true; 232d055a34aSNan Zhou } 233d055a34aSNan Zhou } 234d055a34aSNan Zhou 235d055a34aSNan Zhou // it's allowed to POST on session collection & login without 236d055a34aSNan Zhou // authentication 237d055a34aSNan Zhou if (boost::beast::http::verb::post == method) 238d055a34aSNan Zhou { 239d055a34aSNan Zhou if ((url == "/redfish/v1/SessionService/Sessions") || 240d055a34aSNan Zhou (url == "/redfish/v1/SessionService/Sessions/") || 241d055a34aSNan Zhou (url == "/redfish/v1/SessionService/Sessions/Members") || 242d055a34aSNan Zhou (url == "/redfish/v1/SessionService/Sessions/Members/") || 243d055a34aSNan Zhou (url == "/login")) 244d055a34aSNan Zhou { 245d055a34aSNan Zhou return true; 246d055a34aSNan Zhou } 247d055a34aSNan Zhou } 248d055a34aSNan Zhou 249d055a34aSNan Zhou return false; 250d055a34aSNan Zhou } 251d055a34aSNan Zhou 252d055a34aSNan Zhou [[maybe_unused]] static std::shared_ptr<persistent_data::UserSession> 253d055a34aSNan Zhou authenticate( 25402cad96eSEd Tanous const boost::asio::ip::address& ipAddress [[maybe_unused]], 2553acced2cSNan Zhou Response& res [[maybe_unused]], 2563acced2cSNan Zhou boost::beast::http::verb method [[maybe_unused]], 257d055a34aSNan Zhou const boost::beast::http::header<true>& reqHeader, 258d055a34aSNan Zhou [[maybe_unused]] const std::shared_ptr<persistent_data::UserSession>& 259d055a34aSNan Zhou session) 260d055a34aSNan Zhou { 261d055a34aSNan Zhou const persistent_data::AuthConfigMethods& authMethodsConfig = 262d055a34aSNan Zhou persistent_data::SessionStore::getInstance().getAuthMethodsConfig(); 263d055a34aSNan Zhou 264d055a34aSNan Zhou std::shared_ptr<persistent_data::UserSession> sessionOut = nullptr; 265d055a34aSNan Zhou #ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION 266d055a34aSNan Zhou if (authMethodsConfig.tls) 267d055a34aSNan Zhou { 268d055a34aSNan Zhou sessionOut = performTLSAuth(res, reqHeader, session); 269d055a34aSNan Zhou } 270d055a34aSNan Zhou #endif 271d055a34aSNan Zhou #ifdef BMCWEB_ENABLE_XTOKEN_AUTHENTICATION 272d055a34aSNan Zhou if (sessionOut == nullptr && authMethodsConfig.xtoken) 273d055a34aSNan Zhou { 274d055a34aSNan Zhou sessionOut = performXtokenAuth(reqHeader); 275d055a34aSNan Zhou } 276d055a34aSNan Zhou #endif 277d055a34aSNan Zhou #ifdef BMCWEB_ENABLE_COOKIE_AUTHENTICATION 278d055a34aSNan Zhou if (sessionOut == nullptr && authMethodsConfig.cookie) 279d055a34aSNan Zhou { 280d055a34aSNan Zhou sessionOut = performCookieAuth(method, reqHeader); 281d055a34aSNan Zhou } 282d055a34aSNan Zhou #endif 283d055a34aSNan Zhou std::string_view authHeader = reqHeader["Authorization"]; 284d055a34aSNan Zhou BMCWEB_LOG_DEBUG << "authHeader=" << authHeader; 285d055a34aSNan Zhou 286d055a34aSNan Zhou if (sessionOut == nullptr && authMethodsConfig.sessionToken) 287d055a34aSNan Zhou { 288d055a34aSNan Zhou #ifdef BMCWEB_ENABLE_SESSION_AUTHENTICATION 289d055a34aSNan Zhou sessionOut = performTokenAuth(authHeader); 290d055a34aSNan Zhou #endif 291d055a34aSNan Zhou } 292d055a34aSNan Zhou if (sessionOut == nullptr && authMethodsConfig.basic) 293d055a34aSNan Zhou { 294d055a34aSNan Zhou #ifdef BMCWEB_ENABLE_BASIC_AUTHENTICATION 295d055a34aSNan Zhou sessionOut = performBasicAuth(ipAddress, authHeader); 296d055a34aSNan Zhou #endif 297d055a34aSNan Zhou } 298d055a34aSNan Zhou if (sessionOut != nullptr) 299d055a34aSNan Zhou { 300d055a34aSNan Zhou return sessionOut; 301d055a34aSNan Zhou } 302d055a34aSNan Zhou 303d055a34aSNan Zhou return nullptr; 304d055a34aSNan Zhou } 305d055a34aSNan Zhou 306d055a34aSNan Zhou } // namespace authentication 307d055a34aSNan Zhou } // namespace crow 308