1d055a34aSNan Zhou #pragma once 2d055a34aSNan Zhou 33ccb3adbSEd Tanous #include "forward_unauthorized.hpp" 43ccb3adbSEd Tanous #include "http_request.hpp" 53ccb3adbSEd Tanous #include "http_response.hpp" 63ccb3adbSEd Tanous #include "http_utility.hpp" 73ccb3adbSEd Tanous #include "pam_authenticate.hpp" 8d055a34aSNan Zhou #include "webroutes.hpp" 9d055a34aSNan Zhou 10d055a34aSNan Zhou #include <boost/container/flat_set.hpp> 11d055a34aSNan Zhou 12d055a34aSNan Zhou #include <random> 13d055a34aSNan Zhou #include <utility> 14d055a34aSNan Zhou 15d055a34aSNan Zhou namespace crow 16d055a34aSNan Zhou { 17d055a34aSNan Zhou 18d055a34aSNan Zhou namespace authentication 19d055a34aSNan Zhou { 20d055a34aSNan Zhou 21b1d736fcSEd Tanous inline void cleanupTempSession(const Request& req) 22d055a34aSNan Zhou { 23d055a34aSNan Zhou // TODO(ed) THis should really be handled by the persistent data 24d055a34aSNan Zhou // middleware, but because it is upstream, it doesn't have access to the 25d055a34aSNan Zhou // session information. Should the data middleware persist the current 26d055a34aSNan Zhou // user session? 27d055a34aSNan Zhou if (req.session != nullptr && 28d055a34aSNan Zhou req.session->persistence == 29d055a34aSNan Zhou persistent_data::PersistenceType::SINGLE_REQUEST) 30d055a34aSNan Zhou { 31d055a34aSNan Zhou persistent_data::SessionStore::getInstance().removeSession(req.session); 32d055a34aSNan Zhou } 33d055a34aSNan Zhou } 34d055a34aSNan Zhou 3525b54dbaSEd Tanous inline std::shared_ptr<persistent_data::UserSession> 36d055a34aSNan Zhou performBasicAuth(const boost::asio::ip::address& clientIp, 37d055a34aSNan Zhou std::string_view authHeader) 38d055a34aSNan Zhou { 3962598e31SEd Tanous BMCWEB_LOG_DEBUG("[AuthMiddleware] Basic authentication"); 40d055a34aSNan Zhou 4111ba3979SEd Tanous if (!authHeader.starts_with("Basic ")) 42d055a34aSNan Zhou { 43d055a34aSNan Zhou return nullptr; 44d055a34aSNan Zhou } 45d055a34aSNan Zhou 46d055a34aSNan Zhou std::string_view param = authHeader.substr(strlen("Basic ")); 47d055a34aSNan Zhou std::string authData; 48d055a34aSNan Zhou 49d055a34aSNan Zhou if (!crow::utility::base64Decode(param, authData)) 50d055a34aSNan Zhou { 51d055a34aSNan Zhou return nullptr; 52d055a34aSNan Zhou } 53d055a34aSNan Zhou std::size_t separator = authData.find(':'); 54d055a34aSNan Zhou if (separator == std::string::npos) 55d055a34aSNan Zhou { 56d055a34aSNan Zhou return nullptr; 57d055a34aSNan Zhou } 58d055a34aSNan Zhou 59d055a34aSNan Zhou std::string user = authData.substr(0, separator); 60d055a34aSNan Zhou separator += 1; 61d055a34aSNan Zhou if (separator > authData.size()) 62d055a34aSNan Zhou { 63d055a34aSNan Zhou return nullptr; 64d055a34aSNan Zhou } 65d055a34aSNan Zhou std::string pass = authData.substr(separator); 66d055a34aSNan Zhou 6762598e31SEd Tanous BMCWEB_LOG_DEBUG("[AuthMiddleware] Authenticating user: {}", user); 6862598e31SEd Tanous BMCWEB_LOG_DEBUG("[AuthMiddleware] User IPAddress: {}", 6962598e31SEd Tanous clientIp.to_string()); 70d055a34aSNan Zhou 71d055a34aSNan Zhou int pamrc = pamAuthenticateUser(user, pass); 72d055a34aSNan Zhou bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD; 73d055a34aSNan Zhou if ((pamrc != PAM_SUCCESS) && !isConfigureSelfOnly) 74d055a34aSNan Zhou { 75d055a34aSNan Zhou return nullptr; 76d055a34aSNan Zhou } 77d055a34aSNan Zhou 78d055a34aSNan Zhou // TODO(ed) generateUserSession is a little expensive for basic 79d055a34aSNan Zhou // auth, as it generates some random identifiers that will never be 80d055a34aSNan Zhou // used. This should have a "fast" path for when user tokens aren't 81d055a34aSNan Zhou // needed. 82d055a34aSNan Zhou // This whole flow needs to be revisited anyway, as we can't be 83d055a34aSNan Zhou // calling directly into pam for every request 84d055a34aSNan Zhou return persistent_data::SessionStore::getInstance().generateUserSession( 85bb759e3aSEd Tanous user, clientIp, std::nullopt, 86d055a34aSNan Zhou persistent_data::PersistenceType::SINGLE_REQUEST, 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 } 14489492a15SPatrick Williams std::string_view authKey = cookieValue.substr(startIndex, 14589492a15SPatrick Williams 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 156*576db695SEd 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 1739f217c26SEd Tanous if (!crow::utility::constantTimeStringCompare( 1749f217c26SEd Tanous csrf, 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 18525b54dbaSEd Tanous inline std::shared_ptr<persistent_data::UserSession> 186d055a34aSNan Zhou performTLSAuth(Response& res, 187d055a34aSNan Zhou const boost::beast::http::header<true>& reqHeader, 188d055a34aSNan Zhou const std::weak_ptr<persistent_data::UserSession>& session) 189d055a34aSNan Zhou { 190d055a34aSNan Zhou if (auto sp = session.lock()) 191d055a34aSNan Zhou { 192d055a34aSNan Zhou // set cookie only if this is req from the browser. 193d055a34aSNan Zhou if (reqHeader["User-Agent"].empty()) 194d055a34aSNan Zhou { 19562598e31SEd Tanous BMCWEB_LOG_DEBUG(" TLS session: {} will be used for this request.", 19662598e31SEd Tanous sp->uniqueId); 197d055a34aSNan Zhou return sp; 198d055a34aSNan Zhou } 199d055a34aSNan Zhou // TODO: change this to not switch to cookie auth 200994fd86aSEd Tanous res.addHeader(boost::beast::http::field::set_cookie, 201d055a34aSNan Zhou "XSRF-TOKEN=" + sp->csrfToken + 202994fd86aSEd Tanous "; SameSite=Strict; Secure"); 203994fd86aSEd Tanous res.addHeader(boost::beast::http::field::set_cookie, 204994fd86aSEd Tanous "SESSION=" + sp->sessionToken + 205994fd86aSEd Tanous "; SameSite=Strict; Secure; HttpOnly"); 206994fd86aSEd Tanous res.addHeader(boost::beast::http::field::set_cookie, 207d055a34aSNan Zhou "IsAuthenticated=true; Secure"); 20862598e31SEd Tanous BMCWEB_LOG_DEBUG( 20962598e31SEd Tanous " TLS session: {} with cookie will be used for this request.", 21062598e31SEd Tanous sp->uniqueId); 211d055a34aSNan Zhou return sp; 212d055a34aSNan Zhou } 213d055a34aSNan Zhou return nullptr; 214d055a34aSNan Zhou } 215d055a34aSNan Zhou 216d055a34aSNan Zhou // checks if request can be forwarded without authentication 21725b54dbaSEd Tanous inline bool isOnAllowlist(std::string_view url, boost::beast::http::verb method) 218d055a34aSNan Zhou { 219d055a34aSNan Zhou if (boost::beast::http::verb::get == method) 220d055a34aSNan Zhou { 221d055a34aSNan Zhou if (url == "/redfish/v1" || url == "/redfish/v1/" || 222d055a34aSNan Zhou url == "/redfish" || url == "/redfish/" || 223d055a34aSNan Zhou url == "/redfish/v1/odata" || url == "/redfish/v1/odata/") 224d055a34aSNan Zhou { 225d055a34aSNan Zhou return true; 226d055a34aSNan Zhou } 227d055a34aSNan Zhou if (crow::webroutes::routes.find(std::string(url)) != 228d055a34aSNan Zhou crow::webroutes::routes.end()) 229d055a34aSNan Zhou { 230d055a34aSNan Zhou return true; 231d055a34aSNan Zhou } 232d055a34aSNan Zhou } 233d055a34aSNan Zhou 234d055a34aSNan Zhou // it's allowed to POST on session collection & login without 235d055a34aSNan Zhou // authentication 236d055a34aSNan Zhou if (boost::beast::http::verb::post == method) 237d055a34aSNan Zhou { 238d055a34aSNan Zhou if ((url == "/redfish/v1/SessionService/Sessions") || 239d055a34aSNan Zhou (url == "/redfish/v1/SessionService/Sessions/") || 240d055a34aSNan Zhou (url == "/redfish/v1/SessionService/Sessions/Members") || 241d055a34aSNan Zhou (url == "/redfish/v1/SessionService/Sessions/Members/") || 242d055a34aSNan Zhou (url == "/login")) 243d055a34aSNan Zhou { 244d055a34aSNan Zhou return true; 245d055a34aSNan Zhou } 246d055a34aSNan Zhou } 247d055a34aSNan Zhou 248d055a34aSNan Zhou return false; 249d055a34aSNan Zhou } 250d055a34aSNan Zhou 25125b54dbaSEd Tanous inline std::shared_ptr<persistent_data::UserSession> authenticate( 25202cad96eSEd Tanous const boost::asio::ip::address& ipAddress [[maybe_unused]], 2533acced2cSNan Zhou Response& res [[maybe_unused]], 2543acced2cSNan Zhou boost::beast::http::verb method [[maybe_unused]], 255d055a34aSNan Zhou const boost::beast::http::header<true>& reqHeader, 256d055a34aSNan Zhou [[maybe_unused]] const std::shared_ptr<persistent_data::UserSession>& 257d055a34aSNan Zhou session) 258d055a34aSNan Zhou { 259d055a34aSNan Zhou const persistent_data::AuthConfigMethods& authMethodsConfig = 260d055a34aSNan Zhou persistent_data::SessionStore::getInstance().getAuthMethodsConfig(); 261d055a34aSNan Zhou 262d055a34aSNan Zhou std::shared_ptr<persistent_data::UserSession> sessionOut = nullptr; 26325b54dbaSEd Tanous if constexpr (BMCWEB_MUTUAL_TLS_AUTH) 26425b54dbaSEd Tanous { 265d055a34aSNan Zhou if (authMethodsConfig.tls) 266d055a34aSNan Zhou { 267d055a34aSNan Zhou sessionOut = performTLSAuth(res, reqHeader, session); 268d055a34aSNan Zhou } 26925b54dbaSEd Tanous } 27025b54dbaSEd Tanous if constexpr (BMCWEB_XTOKEN_AUTH) 27125b54dbaSEd Tanous { 272d055a34aSNan Zhou if (sessionOut == nullptr && authMethodsConfig.xtoken) 273d055a34aSNan Zhou { 274d055a34aSNan Zhou sessionOut = performXtokenAuth(reqHeader); 275d055a34aSNan Zhou } 27625b54dbaSEd Tanous } 27725b54dbaSEd Tanous if constexpr (BMCWEB_COOKIE_AUTH) 27825b54dbaSEd Tanous { 279d055a34aSNan Zhou if (sessionOut == nullptr && authMethodsConfig.cookie) 280d055a34aSNan Zhou { 281d055a34aSNan Zhou sessionOut = performCookieAuth(method, reqHeader); 282d055a34aSNan Zhou } 28325b54dbaSEd Tanous } 284d055a34aSNan Zhou std::string_view authHeader = reqHeader["Authorization"]; 28562598e31SEd Tanous BMCWEB_LOG_DEBUG("authHeader={}", authHeader); 28625b54dbaSEd Tanous if constexpr (BMCWEB_SESSION_AUTH) 28725b54dbaSEd Tanous { 288d055a34aSNan Zhou if (sessionOut == nullptr && authMethodsConfig.sessionToken) 289d055a34aSNan Zhou { 290d055a34aSNan Zhou sessionOut = performTokenAuth(authHeader); 291d055a34aSNan Zhou } 29225b54dbaSEd Tanous } 29325b54dbaSEd Tanous if constexpr (BMCWEB_BASIC_AUTH) 29425b54dbaSEd Tanous { 295d055a34aSNan Zhou if (sessionOut == nullptr && authMethodsConfig.basic) 296d055a34aSNan Zhou { 297d055a34aSNan Zhou sessionOut = performBasicAuth(ipAddress, authHeader); 29825b54dbaSEd Tanous } 299d055a34aSNan Zhou } 300d055a34aSNan Zhou if (sessionOut != nullptr) 301d055a34aSNan Zhou { 302d055a34aSNan Zhou return sessionOut; 303d055a34aSNan Zhou } 304d055a34aSNan Zhou 305d055a34aSNan Zhou return nullptr; 306d055a34aSNan Zhou } 307d055a34aSNan Zhou 308d055a34aSNan Zhou } // namespace authentication 309d055a34aSNan Zhou } // namespace crow 310