1d055a34aSNan Zhou #pragma once 2d055a34aSNan Zhou 329aab242SPaul Fertser #include "cookies.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 22*bd79bce8SPatrick Williams inline std::shared_ptr<persistent_data::UserSession> performBasicAuth( 23*bd79bce8SPatrick Williams const boost::asio::ip::address& clientIp, std::string_view authHeader) 24d055a34aSNan Zhou { 2562598e31SEd Tanous BMCWEB_LOG_DEBUG("[AuthMiddleware] Basic authentication"); 26d055a34aSNan Zhou 2711ba3979SEd Tanous if (!authHeader.starts_with("Basic ")) 28d055a34aSNan Zhou { 29d055a34aSNan Zhou return nullptr; 30d055a34aSNan Zhou } 31d055a34aSNan Zhou 32d055a34aSNan Zhou std::string_view param = authHeader.substr(strlen("Basic ")); 33d055a34aSNan Zhou std::string authData; 34d055a34aSNan Zhou 35d055a34aSNan Zhou if (!crow::utility::base64Decode(param, authData)) 36d055a34aSNan Zhou { 37d055a34aSNan Zhou return nullptr; 38d055a34aSNan Zhou } 39d055a34aSNan Zhou std::size_t separator = authData.find(':'); 40d055a34aSNan Zhou if (separator == std::string::npos) 41d055a34aSNan Zhou { 42d055a34aSNan Zhou return nullptr; 43d055a34aSNan Zhou } 44d055a34aSNan Zhou 45d055a34aSNan Zhou std::string user = authData.substr(0, separator); 46d055a34aSNan Zhou separator += 1; 47d055a34aSNan Zhou if (separator > authData.size()) 48d055a34aSNan Zhou { 49d055a34aSNan Zhou return nullptr; 50d055a34aSNan Zhou } 51d055a34aSNan Zhou std::string pass = authData.substr(separator); 52d055a34aSNan Zhou 5362598e31SEd Tanous BMCWEB_LOG_DEBUG("[AuthMiddleware] Authenticating user: {}", user); 5462598e31SEd Tanous BMCWEB_LOG_DEBUG("[AuthMiddleware] User IPAddress: {}", 5562598e31SEd Tanous clientIp.to_string()); 56d055a34aSNan Zhou 57d055a34aSNan Zhou int pamrc = pamAuthenticateUser(user, pass); 58d055a34aSNan Zhou bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD; 59d055a34aSNan Zhou if ((pamrc != PAM_SUCCESS) && !isConfigureSelfOnly) 60d055a34aSNan Zhou { 61d055a34aSNan Zhou return nullptr; 62d055a34aSNan Zhou } 63d055a34aSNan Zhou 6489cda63dSEd Tanous // Attempt to locate an existing Basic Auth session from the same ip address 6589cda63dSEd Tanous // and user 6689cda63dSEd Tanous for (auto& session : 6789cda63dSEd Tanous persistent_data::SessionStore::getInstance().getSessions()) 6889cda63dSEd Tanous { 6989cda63dSEd Tanous if (session->sessionType != persistent_data::SessionType::Basic) 7089cda63dSEd Tanous { 7189cda63dSEd Tanous continue; 7289cda63dSEd Tanous } 7389cda63dSEd Tanous if (session->clientIp != redfish::ip_util::toString(clientIp)) 7489cda63dSEd Tanous { 7589cda63dSEd Tanous continue; 7689cda63dSEd Tanous } 7789cda63dSEd Tanous if (session->username != user) 7889cda63dSEd Tanous { 7989cda63dSEd Tanous continue; 8089cda63dSEd Tanous } 8189cda63dSEd Tanous return session; 8289cda63dSEd Tanous } 8389cda63dSEd Tanous 84d055a34aSNan Zhou return persistent_data::SessionStore::getInstance().generateUserSession( 8589cda63dSEd Tanous user, clientIp, std::nullopt, persistent_data::SessionType::Basic, 8689cda63dSEd Tanous isConfigureSelfOnly); 87d055a34aSNan Zhou } 88d055a34aSNan Zhou 8925b54dbaSEd Tanous inline std::shared_ptr<persistent_data::UserSession> 90d055a34aSNan Zhou performTokenAuth(std::string_view authHeader) 91d055a34aSNan Zhou { 9262598e31SEd Tanous BMCWEB_LOG_DEBUG("[AuthMiddleware] Token authentication"); 9311ba3979SEd Tanous if (!authHeader.starts_with("Token ")) 94d055a34aSNan Zhou { 95d055a34aSNan Zhou return nullptr; 96d055a34aSNan Zhou } 97d055a34aSNan Zhou std::string_view token = authHeader.substr(strlen("Token ")); 98d055a34aSNan Zhou auto sessionOut = 99d055a34aSNan Zhou persistent_data::SessionStore::getInstance().loginSessionByToken(token); 100d055a34aSNan Zhou return sessionOut; 101d055a34aSNan Zhou } 102d055a34aSNan Zhou 10325b54dbaSEd Tanous inline std::shared_ptr<persistent_data::UserSession> 104d055a34aSNan Zhou performXtokenAuth(const boost::beast::http::header<true>& reqHeader) 105d055a34aSNan Zhou { 10662598e31SEd Tanous BMCWEB_LOG_DEBUG("[AuthMiddleware] X-Auth-Token authentication"); 107d055a34aSNan Zhou 108d055a34aSNan Zhou std::string_view token = reqHeader["X-Auth-Token"]; 109d055a34aSNan Zhou if (token.empty()) 110d055a34aSNan Zhou { 111d055a34aSNan Zhou return nullptr; 112d055a34aSNan Zhou } 113d055a34aSNan Zhou auto sessionOut = 114d055a34aSNan Zhou persistent_data::SessionStore::getInstance().loginSessionByToken(token); 115d055a34aSNan Zhou return sessionOut; 116d055a34aSNan Zhou } 117d055a34aSNan Zhou 11825b54dbaSEd Tanous inline std::shared_ptr<persistent_data::UserSession> 1191d869608SEd Tanous performCookieAuth(boost::beast::http::verb method [[maybe_unused]], 120d055a34aSNan Zhou const boost::beast::http::header<true>& reqHeader) 121d055a34aSNan Zhou { 1229f217c26SEd Tanous using headers = boost::beast::http::header<true>; 1239f217c26SEd Tanous std::pair<headers::const_iterator, headers::const_iterator> cookies = 1249f217c26SEd Tanous reqHeader.equal_range(boost::beast::http::field::cookie); 125d055a34aSNan Zhou 1269f217c26SEd Tanous for (auto it = cookies.first; it != cookies.second; it++) 127d055a34aSNan Zhou { 1289f217c26SEd Tanous std::string_view cookieValue = it->value(); 1299f217c26SEd Tanous BMCWEB_LOG_DEBUG("Checking cookie {}", cookieValue); 130d055a34aSNan Zhou auto startIndex = cookieValue.find("SESSION="); 131d055a34aSNan Zhou if (startIndex == std::string::npos) 132d055a34aSNan Zhou { 1339f217c26SEd Tanous BMCWEB_LOG_DEBUG( 1349f217c26SEd Tanous "Cookie was present, but didn't look like a session {}", 1359f217c26SEd Tanous cookieValue); 1369f217c26SEd Tanous continue; 137d055a34aSNan Zhou } 138d055a34aSNan Zhou startIndex += sizeof("SESSION=") - 1; 139d055a34aSNan Zhou auto endIndex = cookieValue.find(';', startIndex); 140d055a34aSNan Zhou if (endIndex == std::string::npos) 141d055a34aSNan Zhou { 142d055a34aSNan Zhou endIndex = cookieValue.size(); 143d055a34aSNan Zhou } 144*bd79bce8SPatrick Williams std::string_view authKey = 145*bd79bce8SPatrick Williams cookieValue.substr(startIndex, endIndex - startIndex); 146d055a34aSNan Zhou 147d055a34aSNan Zhou std::shared_ptr<persistent_data::UserSession> sessionOut = 148d055a34aSNan Zhou persistent_data::SessionStore::getInstance().loginSessionByToken( 149d055a34aSNan Zhou authKey); 150d055a34aSNan Zhou if (sessionOut == nullptr) 151d055a34aSNan Zhou { 152d055a34aSNan Zhou return nullptr; 153d055a34aSNan Zhou } 154c3b3ad03SEd Tanous sessionOut->cookieAuth = true; 15525b54dbaSEd Tanous 156576db695SEd Tanous if constexpr (!BMCWEB_INSECURE_DISABLE_CSRF) 15725b54dbaSEd Tanous { 158d055a34aSNan Zhou // RFC7231 defines methods that need csrf protection 159d055a34aSNan Zhou if (method != boost::beast::http::verb::get) 160d055a34aSNan Zhou { 161d055a34aSNan Zhou std::string_view csrf = reqHeader["X-XSRF-TOKEN"]; 162d055a34aSNan Zhou // Make sure both tokens are filled 163d055a34aSNan Zhou if (csrf.empty() || sessionOut->csrfToken.empty()) 164d055a34aSNan Zhou { 165d055a34aSNan Zhou return nullptr; 166d055a34aSNan Zhou } 167d055a34aSNan Zhou 168d055a34aSNan Zhou if (csrf.size() != persistent_data::sessionTokenSize) 169d055a34aSNan Zhou { 170d055a34aSNan Zhou return nullptr; 171d055a34aSNan Zhou } 172d055a34aSNan Zhou // Reject if csrf token not available 173724985ffSEd Tanous if (!bmcweb::constantTimeStringCompare(csrf, 174724985ffSEd Tanous sessionOut->csrfToken)) 175d055a34aSNan Zhou { 176d055a34aSNan Zhou return nullptr; 177d055a34aSNan Zhou } 178d055a34aSNan Zhou } 17925b54dbaSEd Tanous } 1803eaecac3SEd Tanous return sessionOut; 181d055a34aSNan Zhou } 1829f217c26SEd Tanous return nullptr; 1839f217c26SEd Tanous } 184d055a34aSNan Zhou 185*bd79bce8SPatrick Williams inline std::shared_ptr<persistent_data::UserSession> performTLSAuth( 186*bd79bce8SPatrick Williams Response& res, const std::shared_ptr<persistent_data::UserSession>& session) 187d055a34aSNan Zhou { 1883281bcf1SEd Tanous if (session != nullptr) 189d055a34aSNan Zhou { 190994fd86aSEd Tanous res.addHeader(boost::beast::http::field::set_cookie, 191d055a34aSNan Zhou "IsAuthenticated=true; Secure"); 19262598e31SEd Tanous BMCWEB_LOG_DEBUG( 19362598e31SEd Tanous " TLS session: {} with cookie will be used for this request.", 1943281bcf1SEd Tanous session->uniqueId); 195d055a34aSNan Zhou } 1963281bcf1SEd Tanous 1973281bcf1SEd Tanous return session; 198d055a34aSNan Zhou } 199d055a34aSNan Zhou 200d055a34aSNan Zhou // checks if request can be forwarded without authentication 20125b54dbaSEd Tanous inline bool isOnAllowlist(std::string_view url, boost::beast::http::verb method) 202d055a34aSNan Zhou { 20338221509SEd Tanous // Handle the case where the router registers routes as both ending with / 20438221509SEd Tanous // and not. 2058f5df132SVinceChang6637 if (url.ends_with('/') && url != "/") 20638221509SEd Tanous { 20738221509SEd Tanous url.remove_suffix(1); 20838221509SEd Tanous } 209d055a34aSNan Zhou if (boost::beast::http::verb::get == method) 210d055a34aSNan Zhou { 21138221509SEd Tanous if ((url == "/redfish") || // 21238221509SEd Tanous (url == "/redfish/v1") || // 21338221509SEd Tanous (url == "/redfish/v1/odata") || // 21438221509SEd Tanous (url == "/redfish/v1/$metadata")) 215d055a34aSNan Zhou { 216d055a34aSNan Zhou return true; 217d055a34aSNan Zhou } 218d055a34aSNan Zhou if (crow::webroutes::routes.find(std::string(url)) != 219d055a34aSNan Zhou crow::webroutes::routes.end()) 220d055a34aSNan Zhou { 221d055a34aSNan Zhou return true; 222d055a34aSNan Zhou } 223d055a34aSNan Zhou } 224d055a34aSNan Zhou 225d055a34aSNan Zhou // it's allowed to POST on session collection & login without 226d055a34aSNan Zhou // authentication 227d055a34aSNan Zhou if (boost::beast::http::verb::post == method) 228d055a34aSNan Zhou { 229d055a34aSNan Zhou if ((url == "/redfish/v1/SessionService/Sessions") || 230d055a34aSNan Zhou (url == "/redfish/v1/SessionService/Sessions/Members") || 231d055a34aSNan Zhou (url == "/login")) 232d055a34aSNan Zhou { 233d055a34aSNan Zhou return true; 234d055a34aSNan Zhou } 235d055a34aSNan Zhou } 236d055a34aSNan Zhou 237d055a34aSNan Zhou return false; 238d055a34aSNan Zhou } 239d055a34aSNan Zhou 24025b54dbaSEd Tanous inline std::shared_ptr<persistent_data::UserSession> authenticate( 24102cad96eSEd Tanous const boost::asio::ip::address& ipAddress [[maybe_unused]], 2423acced2cSNan Zhou Response& res [[maybe_unused]], 2433acced2cSNan Zhou boost::beast::http::verb method [[maybe_unused]], 244d055a34aSNan Zhou const boost::beast::http::header<true>& reqHeader, 245d055a34aSNan Zhou [[maybe_unused]] const std::shared_ptr<persistent_data::UserSession>& 246d055a34aSNan Zhou session) 247d055a34aSNan Zhou { 248d055a34aSNan Zhou const persistent_data::AuthConfigMethods& authMethodsConfig = 249d055a34aSNan Zhou persistent_data::SessionStore::getInstance().getAuthMethodsConfig(); 250d055a34aSNan Zhou 251d055a34aSNan Zhou std::shared_ptr<persistent_data::UserSession> sessionOut = nullptr; 25225b54dbaSEd Tanous if constexpr (BMCWEB_MUTUAL_TLS_AUTH) 25325b54dbaSEd Tanous { 254d055a34aSNan Zhou if (authMethodsConfig.tls) 255d055a34aSNan Zhou { 2563281bcf1SEd Tanous sessionOut = performTLSAuth(res, session); 257d055a34aSNan Zhou } 25825b54dbaSEd Tanous } 25925b54dbaSEd Tanous if constexpr (BMCWEB_XTOKEN_AUTH) 26025b54dbaSEd Tanous { 261d055a34aSNan Zhou if (sessionOut == nullptr && authMethodsConfig.xtoken) 262d055a34aSNan Zhou { 263d055a34aSNan Zhou sessionOut = performXtokenAuth(reqHeader); 264d055a34aSNan Zhou } 26525b54dbaSEd Tanous } 26625b54dbaSEd Tanous if constexpr (BMCWEB_COOKIE_AUTH) 26725b54dbaSEd Tanous { 268d055a34aSNan Zhou if (sessionOut == nullptr && authMethodsConfig.cookie) 269d055a34aSNan Zhou { 270d055a34aSNan Zhou sessionOut = performCookieAuth(method, reqHeader); 271d055a34aSNan Zhou } 27225b54dbaSEd Tanous } 273d055a34aSNan Zhou std::string_view authHeader = reqHeader["Authorization"]; 27462598e31SEd Tanous BMCWEB_LOG_DEBUG("authHeader={}", authHeader); 27525b54dbaSEd Tanous if constexpr (BMCWEB_SESSION_AUTH) 27625b54dbaSEd Tanous { 277d055a34aSNan Zhou if (sessionOut == nullptr && authMethodsConfig.sessionToken) 278d055a34aSNan Zhou { 279d055a34aSNan Zhou sessionOut = performTokenAuth(authHeader); 280d055a34aSNan Zhou } 28125b54dbaSEd Tanous } 28225b54dbaSEd Tanous if constexpr (BMCWEB_BASIC_AUTH) 28325b54dbaSEd Tanous { 284d055a34aSNan Zhou if (sessionOut == nullptr && authMethodsConfig.basic) 285d055a34aSNan Zhou { 286d055a34aSNan Zhou sessionOut = performBasicAuth(ipAddress, authHeader); 28725b54dbaSEd Tanous } 288d055a34aSNan Zhou } 289d055a34aSNan Zhou if (sessionOut != nullptr) 290d055a34aSNan Zhou { 291d055a34aSNan Zhou return sessionOut; 292d055a34aSNan Zhou } 293d055a34aSNan Zhou 294d055a34aSNan Zhou return nullptr; 295d055a34aSNan Zhou } 296d055a34aSNan Zhou 297d055a34aSNan Zhou } // namespace authentication 298d055a34aSNan Zhou } // namespace crow 299