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