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 35d055a34aSNan Zhou #ifdef BMCWEB_ENABLE_BASIC_AUTHENTICATION 36d055a34aSNan Zhou static std::shared_ptr<persistent_data::UserSession> 37d055a34aSNan Zhou performBasicAuth(const boost::asio::ip::address& clientIp, 38d055a34aSNan Zhou std::string_view authHeader) 39d055a34aSNan Zhou { 4062598e31SEd Tanous BMCWEB_LOG_DEBUG("[AuthMiddleware] Basic authentication"); 41d055a34aSNan Zhou 4211ba3979SEd Tanous if (!authHeader.starts_with("Basic ")) 43d055a34aSNan Zhou { 44d055a34aSNan Zhou return nullptr; 45d055a34aSNan Zhou } 46d055a34aSNan Zhou 47d055a34aSNan Zhou std::string_view param = authHeader.substr(strlen("Basic ")); 48d055a34aSNan Zhou std::string authData; 49d055a34aSNan Zhou 50d055a34aSNan Zhou if (!crow::utility::base64Decode(param, authData)) 51d055a34aSNan Zhou { 52d055a34aSNan Zhou return nullptr; 53d055a34aSNan Zhou } 54d055a34aSNan Zhou std::size_t separator = authData.find(':'); 55d055a34aSNan Zhou if (separator == std::string::npos) 56d055a34aSNan Zhou { 57d055a34aSNan Zhou return nullptr; 58d055a34aSNan Zhou } 59d055a34aSNan Zhou 60d055a34aSNan Zhou std::string user = authData.substr(0, separator); 61d055a34aSNan Zhou separator += 1; 62d055a34aSNan Zhou if (separator > authData.size()) 63d055a34aSNan Zhou { 64d055a34aSNan Zhou return nullptr; 65d055a34aSNan Zhou } 66d055a34aSNan Zhou std::string pass = authData.substr(separator); 67d055a34aSNan Zhou 6862598e31SEd Tanous BMCWEB_LOG_DEBUG("[AuthMiddleware] Authenticating user: {}", user); 6962598e31SEd Tanous BMCWEB_LOG_DEBUG("[AuthMiddleware] User IPAddress: {}", 7062598e31SEd Tanous clientIp.to_string()); 71d055a34aSNan Zhou 72d055a34aSNan Zhou int pamrc = pamAuthenticateUser(user, pass); 73d055a34aSNan Zhou bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD; 74d055a34aSNan Zhou if ((pamrc != PAM_SUCCESS) && !isConfigureSelfOnly) 75d055a34aSNan Zhou { 76d055a34aSNan Zhou return nullptr; 77d055a34aSNan Zhou } 78d055a34aSNan Zhou 79d055a34aSNan Zhou // TODO(ed) generateUserSession is a little expensive for basic 80d055a34aSNan Zhou // auth, as it generates some random identifiers that will never be 81d055a34aSNan Zhou // used. This should have a "fast" path for when user tokens aren't 82d055a34aSNan Zhou // needed. 83d055a34aSNan Zhou // This whole flow needs to be revisited anyway, as we can't be 84d055a34aSNan Zhou // calling directly into pam for every request 85d055a34aSNan Zhou return persistent_data::SessionStore::getInstance().generateUserSession( 86bb759e3aSEd Tanous user, clientIp, std::nullopt, 87d055a34aSNan Zhou persistent_data::PersistenceType::SINGLE_REQUEST, isConfigureSelfOnly); 88d055a34aSNan Zhou } 89d055a34aSNan Zhou #endif 90d055a34aSNan Zhou 91d055a34aSNan Zhou #ifdef BMCWEB_ENABLE_SESSION_AUTHENTICATION 92d055a34aSNan Zhou static std::shared_ptr<persistent_data::UserSession> 93d055a34aSNan Zhou performTokenAuth(std::string_view authHeader) 94d055a34aSNan Zhou { 9562598e31SEd Tanous BMCWEB_LOG_DEBUG("[AuthMiddleware] Token authentication"); 9611ba3979SEd Tanous if (!authHeader.starts_with("Token ")) 97d055a34aSNan Zhou { 98d055a34aSNan Zhou return nullptr; 99d055a34aSNan Zhou } 100d055a34aSNan Zhou std::string_view token = authHeader.substr(strlen("Token ")); 101d055a34aSNan Zhou auto sessionOut = 102d055a34aSNan Zhou persistent_data::SessionStore::getInstance().loginSessionByToken(token); 103d055a34aSNan Zhou return sessionOut; 104d055a34aSNan Zhou } 105d055a34aSNan Zhou #endif 106d055a34aSNan Zhou 107d055a34aSNan Zhou #ifdef BMCWEB_ENABLE_XTOKEN_AUTHENTICATION 108d055a34aSNan Zhou static std::shared_ptr<persistent_data::UserSession> 109d055a34aSNan Zhou performXtokenAuth(const boost::beast::http::header<true>& reqHeader) 110d055a34aSNan Zhou { 11162598e31SEd Tanous BMCWEB_LOG_DEBUG("[AuthMiddleware] X-Auth-Token authentication"); 112d055a34aSNan Zhou 113d055a34aSNan Zhou std::string_view token = reqHeader["X-Auth-Token"]; 114d055a34aSNan Zhou if (token.empty()) 115d055a34aSNan Zhou { 116d055a34aSNan Zhou return nullptr; 117d055a34aSNan Zhou } 118d055a34aSNan Zhou auto sessionOut = 119d055a34aSNan Zhou persistent_data::SessionStore::getInstance().loginSessionByToken(token); 120d055a34aSNan Zhou return sessionOut; 121d055a34aSNan Zhou } 122d055a34aSNan Zhou #endif 123d055a34aSNan Zhou 124d055a34aSNan Zhou #ifdef BMCWEB_ENABLE_COOKIE_AUTHENTICATION 125d055a34aSNan Zhou static std::shared_ptr<persistent_data::UserSession> 1261d869608SEd Tanous performCookieAuth(boost::beast::http::verb method [[maybe_unused]], 127d055a34aSNan Zhou const boost::beast::http::header<true>& reqHeader) 128d055a34aSNan Zhou { 129*9f217c26SEd Tanous using headers = boost::beast::http::header<true>; 130*9f217c26SEd Tanous std::pair<headers::const_iterator, headers::const_iterator> cookies = 131*9f217c26SEd Tanous reqHeader.equal_range(boost::beast::http::field::cookie); 132d055a34aSNan Zhou 133*9f217c26SEd Tanous for (auto it = cookies.first; it != cookies.second; it++) 134d055a34aSNan Zhou { 135*9f217c26SEd Tanous std::string_view cookieValue = it->value(); 136*9f217c26SEd Tanous BMCWEB_LOG_DEBUG("Checking cookie {}", cookieValue); 137d055a34aSNan Zhou auto startIndex = cookieValue.find("SESSION="); 138d055a34aSNan Zhou if (startIndex == std::string::npos) 139d055a34aSNan Zhou { 140*9f217c26SEd Tanous BMCWEB_LOG_DEBUG( 141*9f217c26SEd Tanous "Cookie was present, but didn't look like a session {}", 142*9f217c26SEd Tanous cookieValue); 143*9f217c26SEd Tanous continue; 144d055a34aSNan Zhou } 145d055a34aSNan Zhou startIndex += sizeof("SESSION=") - 1; 146d055a34aSNan Zhou auto endIndex = cookieValue.find(';', startIndex); 147d055a34aSNan Zhou if (endIndex == std::string::npos) 148d055a34aSNan Zhou { 149d055a34aSNan Zhou endIndex = cookieValue.size(); 150d055a34aSNan Zhou } 15189492a15SPatrick Williams std::string_view authKey = cookieValue.substr(startIndex, 15289492a15SPatrick Williams endIndex - startIndex); 153d055a34aSNan Zhou 154d055a34aSNan Zhou std::shared_ptr<persistent_data::UserSession> sessionOut = 155d055a34aSNan Zhou persistent_data::SessionStore::getInstance().loginSessionByToken( 156d055a34aSNan Zhou authKey); 157d055a34aSNan Zhou if (sessionOut == nullptr) 158d055a34aSNan Zhou { 159d055a34aSNan Zhou return nullptr; 160d055a34aSNan Zhou } 161c3b3ad03SEd Tanous sessionOut->cookieAuth = true; 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 178*9f217c26SEd Tanous if (!crow::utility::constantTimeStringCompare( 179*9f217c26SEd Tanous csrf, sessionOut->csrfToken)) 180d055a34aSNan Zhou { 181d055a34aSNan Zhou return nullptr; 182d055a34aSNan Zhou } 183d055a34aSNan Zhou } 184d055a34aSNan Zhou #endif 185d055a34aSNan Zhou return sessionOut; 186d055a34aSNan Zhou } 187*9f217c26SEd Tanous return nullptr; 188*9f217c26SEd Tanous } 189d055a34aSNan Zhou #endif 190d055a34aSNan Zhou 191d055a34aSNan Zhou #ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION 192d055a34aSNan Zhou static std::shared_ptr<persistent_data::UserSession> 193d055a34aSNan Zhou performTLSAuth(Response& res, 194d055a34aSNan Zhou const boost::beast::http::header<true>& reqHeader, 195d055a34aSNan Zhou const std::weak_ptr<persistent_data::UserSession>& session) 196d055a34aSNan Zhou { 197d055a34aSNan Zhou if (auto sp = session.lock()) 198d055a34aSNan Zhou { 199d055a34aSNan Zhou // set cookie only if this is req from the browser. 200d055a34aSNan Zhou if (reqHeader["User-Agent"].empty()) 201d055a34aSNan Zhou { 20262598e31SEd Tanous BMCWEB_LOG_DEBUG(" TLS session: {} will be used for this request.", 20362598e31SEd Tanous sp->uniqueId); 204d055a34aSNan Zhou return sp; 205d055a34aSNan Zhou } 206d055a34aSNan Zhou // TODO: change this to not switch to cookie auth 207994fd86aSEd Tanous res.addHeader(boost::beast::http::field::set_cookie, 208d055a34aSNan Zhou "XSRF-TOKEN=" + sp->csrfToken + 209994fd86aSEd Tanous "; SameSite=Strict; Secure"); 210994fd86aSEd Tanous res.addHeader(boost::beast::http::field::set_cookie, 211994fd86aSEd Tanous "SESSION=" + sp->sessionToken + 212994fd86aSEd Tanous "; SameSite=Strict; Secure; HttpOnly"); 213994fd86aSEd Tanous res.addHeader(boost::beast::http::field::set_cookie, 214d055a34aSNan Zhou "IsAuthenticated=true; Secure"); 21562598e31SEd Tanous BMCWEB_LOG_DEBUG( 21662598e31SEd Tanous " TLS session: {} with cookie will be used for this request.", 21762598e31SEd Tanous sp->uniqueId); 218d055a34aSNan Zhou return sp; 219d055a34aSNan Zhou } 220d055a34aSNan Zhou return nullptr; 221d055a34aSNan Zhou } 222d055a34aSNan Zhou #endif 223d055a34aSNan Zhou 224d055a34aSNan Zhou // checks if request can be forwarded without authentication 225d055a34aSNan Zhou [[maybe_unused]] static bool isOnAllowlist(std::string_view url, 226d055a34aSNan Zhou boost::beast::http::verb method) 227d055a34aSNan Zhou { 228d055a34aSNan Zhou if (boost::beast::http::verb::get == method) 229d055a34aSNan Zhou { 230d055a34aSNan Zhou if (url == "/redfish/v1" || url == "/redfish/v1/" || 231d055a34aSNan Zhou url == "/redfish" || url == "/redfish/" || 232d055a34aSNan Zhou url == "/redfish/v1/odata" || url == "/redfish/v1/odata/") 233d055a34aSNan Zhou { 234d055a34aSNan Zhou return true; 235d055a34aSNan Zhou } 236d055a34aSNan Zhou if (crow::webroutes::routes.find(std::string(url)) != 237d055a34aSNan Zhou crow::webroutes::routes.end()) 238d055a34aSNan Zhou { 239d055a34aSNan Zhou return true; 240d055a34aSNan Zhou } 241d055a34aSNan Zhou } 242d055a34aSNan Zhou 243d055a34aSNan Zhou // it's allowed to POST on session collection & login without 244d055a34aSNan Zhou // authentication 245d055a34aSNan Zhou if (boost::beast::http::verb::post == method) 246d055a34aSNan Zhou { 247d055a34aSNan Zhou if ((url == "/redfish/v1/SessionService/Sessions") || 248d055a34aSNan Zhou (url == "/redfish/v1/SessionService/Sessions/") || 249d055a34aSNan Zhou (url == "/redfish/v1/SessionService/Sessions/Members") || 250d055a34aSNan Zhou (url == "/redfish/v1/SessionService/Sessions/Members/") || 251d055a34aSNan Zhou (url == "/login")) 252d055a34aSNan Zhou { 253d055a34aSNan Zhou return true; 254d055a34aSNan Zhou } 255d055a34aSNan Zhou } 256d055a34aSNan Zhou 257d055a34aSNan Zhou return false; 258d055a34aSNan Zhou } 259d055a34aSNan Zhou 260d055a34aSNan Zhou [[maybe_unused]] static std::shared_ptr<persistent_data::UserSession> 261d055a34aSNan Zhou authenticate( 26202cad96eSEd Tanous const boost::asio::ip::address& ipAddress [[maybe_unused]], 2633acced2cSNan Zhou Response& res [[maybe_unused]], 2643acced2cSNan Zhou boost::beast::http::verb method [[maybe_unused]], 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"]; 29262598e31SEd Tanous 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