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 2225b54dbaSEd Tanous inline std::shared_ptr<persistent_data::UserSession> 23d055a34aSNan Zhou performBasicAuth(const boost::asio::ip::address& clientIp, 24d055a34aSNan Zhou std::string_view authHeader) 25d055a34aSNan Zhou { 2662598e31SEd Tanous BMCWEB_LOG_DEBUG("[AuthMiddleware] Basic authentication"); 27d055a34aSNan Zhou 2811ba3979SEd Tanous if (!authHeader.starts_with("Basic ")) 29d055a34aSNan Zhou { 30d055a34aSNan Zhou return nullptr; 31d055a34aSNan Zhou } 32d055a34aSNan Zhou 33d055a34aSNan Zhou std::string_view param = authHeader.substr(strlen("Basic ")); 34d055a34aSNan Zhou std::string authData; 35d055a34aSNan Zhou 36d055a34aSNan Zhou if (!crow::utility::base64Decode(param, authData)) 37d055a34aSNan Zhou { 38d055a34aSNan Zhou return nullptr; 39d055a34aSNan Zhou } 40d055a34aSNan Zhou std::size_t separator = authData.find(':'); 41d055a34aSNan Zhou if (separator == std::string::npos) 42d055a34aSNan Zhou { 43d055a34aSNan Zhou return nullptr; 44d055a34aSNan Zhou } 45d055a34aSNan Zhou 46d055a34aSNan Zhou std::string user = authData.substr(0, separator); 47d055a34aSNan Zhou separator += 1; 48d055a34aSNan Zhou if (separator > authData.size()) 49d055a34aSNan Zhou { 50d055a34aSNan Zhou return nullptr; 51d055a34aSNan Zhou } 52d055a34aSNan Zhou std::string pass = authData.substr(separator); 53d055a34aSNan Zhou 5462598e31SEd Tanous BMCWEB_LOG_DEBUG("[AuthMiddleware] Authenticating user: {}", user); 5562598e31SEd Tanous BMCWEB_LOG_DEBUG("[AuthMiddleware] User IPAddress: {}", 5662598e31SEd Tanous clientIp.to_string()); 57d055a34aSNan Zhou 58d055a34aSNan Zhou int pamrc = pamAuthenticateUser(user, pass); 59d055a34aSNan Zhou bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD; 60d055a34aSNan Zhou if ((pamrc != PAM_SUCCESS) && !isConfigureSelfOnly) 61d055a34aSNan Zhou { 62d055a34aSNan Zhou return nullptr; 63d055a34aSNan Zhou } 64d055a34aSNan Zhou 65*89cda63dSEd Tanous // Attempt to locate an existing Basic Auth session from the same ip address 66*89cda63dSEd Tanous // and user 67*89cda63dSEd Tanous for (auto& session : 68*89cda63dSEd Tanous persistent_data::SessionStore::getInstance().getSessions()) 69*89cda63dSEd Tanous { 70*89cda63dSEd Tanous if (session->sessionType != persistent_data::SessionType::Basic) 71*89cda63dSEd Tanous { 72*89cda63dSEd Tanous continue; 73*89cda63dSEd Tanous } 74*89cda63dSEd Tanous if (session->clientIp != redfish::ip_util::toString(clientIp)) 75*89cda63dSEd Tanous { 76*89cda63dSEd Tanous continue; 77*89cda63dSEd Tanous } 78*89cda63dSEd Tanous if (session->username != user) 79*89cda63dSEd Tanous { 80*89cda63dSEd Tanous continue; 81*89cda63dSEd Tanous } 82*89cda63dSEd Tanous return session; 83*89cda63dSEd Tanous } 84*89cda63dSEd Tanous 85d055a34aSNan Zhou return persistent_data::SessionStore::getInstance().generateUserSession( 86*89cda63dSEd Tanous user, clientIp, std::nullopt, persistent_data::SessionType::Basic, 87*89cda63dSEd Tanous isConfigureSelfOnly); 88d055a34aSNan Zhou } 89d055a34aSNan Zhou 9025b54dbaSEd Tanous inline std::shared_ptr<persistent_data::UserSession> 91d055a34aSNan Zhou performTokenAuth(std::string_view authHeader) 92d055a34aSNan Zhou { 9362598e31SEd Tanous BMCWEB_LOG_DEBUG("[AuthMiddleware] Token authentication"); 9411ba3979SEd Tanous if (!authHeader.starts_with("Token ")) 95d055a34aSNan Zhou { 96d055a34aSNan Zhou return nullptr; 97d055a34aSNan Zhou } 98d055a34aSNan Zhou std::string_view token = authHeader.substr(strlen("Token ")); 99d055a34aSNan Zhou auto sessionOut = 100d055a34aSNan Zhou persistent_data::SessionStore::getInstance().loginSessionByToken(token); 101d055a34aSNan Zhou return sessionOut; 102d055a34aSNan Zhou } 103d055a34aSNan Zhou 10425b54dbaSEd Tanous inline std::shared_ptr<persistent_data::UserSession> 105d055a34aSNan Zhou performXtokenAuth(const boost::beast::http::header<true>& reqHeader) 106d055a34aSNan Zhou { 10762598e31SEd Tanous BMCWEB_LOG_DEBUG("[AuthMiddleware] X-Auth-Token authentication"); 108d055a34aSNan Zhou 109d055a34aSNan Zhou std::string_view token = reqHeader["X-Auth-Token"]; 110d055a34aSNan Zhou if (token.empty()) 111d055a34aSNan Zhou { 112d055a34aSNan Zhou return nullptr; 113d055a34aSNan Zhou } 114d055a34aSNan Zhou auto sessionOut = 115d055a34aSNan Zhou persistent_data::SessionStore::getInstance().loginSessionByToken(token); 116d055a34aSNan Zhou return sessionOut; 117d055a34aSNan Zhou } 118d055a34aSNan Zhou 11925b54dbaSEd Tanous inline std::shared_ptr<persistent_data::UserSession> 1201d869608SEd Tanous performCookieAuth(boost::beast::http::verb method [[maybe_unused]], 121d055a34aSNan Zhou const boost::beast::http::header<true>& reqHeader) 122d055a34aSNan Zhou { 1239f217c26SEd Tanous using headers = boost::beast::http::header<true>; 1249f217c26SEd Tanous std::pair<headers::const_iterator, headers::const_iterator> cookies = 1259f217c26SEd Tanous reqHeader.equal_range(boost::beast::http::field::cookie); 126d055a34aSNan Zhou 1279f217c26SEd Tanous for (auto it = cookies.first; it != cookies.second; it++) 128d055a34aSNan Zhou { 1299f217c26SEd Tanous std::string_view cookieValue = it->value(); 1309f217c26SEd Tanous BMCWEB_LOG_DEBUG("Checking cookie {}", cookieValue); 131d055a34aSNan Zhou auto startIndex = cookieValue.find("SESSION="); 132d055a34aSNan Zhou if (startIndex == std::string::npos) 133d055a34aSNan Zhou { 1349f217c26SEd Tanous BMCWEB_LOG_DEBUG( 1359f217c26SEd Tanous "Cookie was present, but didn't look like a session {}", 1369f217c26SEd Tanous cookieValue); 1379f217c26SEd Tanous continue; 138d055a34aSNan Zhou } 139d055a34aSNan Zhou startIndex += sizeof("SESSION=") - 1; 140d055a34aSNan Zhou auto endIndex = cookieValue.find(';', startIndex); 141d055a34aSNan Zhou if (endIndex == std::string::npos) 142d055a34aSNan Zhou { 143d055a34aSNan Zhou endIndex = cookieValue.size(); 144d055a34aSNan Zhou } 14589492a15SPatrick Williams std::string_view authKey = cookieValue.substr(startIndex, 14689492a15SPatrick Williams endIndex - startIndex); 147d055a34aSNan Zhou 148d055a34aSNan Zhou std::shared_ptr<persistent_data::UserSession> sessionOut = 149d055a34aSNan Zhou persistent_data::SessionStore::getInstance().loginSessionByToken( 150d055a34aSNan Zhou authKey); 151d055a34aSNan Zhou if (sessionOut == nullptr) 152d055a34aSNan Zhou { 153d055a34aSNan Zhou return nullptr; 154d055a34aSNan Zhou } 155c3b3ad03SEd Tanous sessionOut->cookieAuth = true; 15625b54dbaSEd Tanous 157576db695SEd Tanous if constexpr (!BMCWEB_INSECURE_DISABLE_CSRF) 15825b54dbaSEd Tanous { 159d055a34aSNan Zhou // RFC7231 defines methods that need csrf protection 160d055a34aSNan Zhou if (method != boost::beast::http::verb::get) 161d055a34aSNan Zhou { 162d055a34aSNan Zhou std::string_view csrf = reqHeader["X-XSRF-TOKEN"]; 163d055a34aSNan Zhou // Make sure both tokens are filled 164d055a34aSNan Zhou if (csrf.empty() || sessionOut->csrfToken.empty()) 165d055a34aSNan Zhou { 166d055a34aSNan Zhou return nullptr; 167d055a34aSNan Zhou } 168d055a34aSNan Zhou 169d055a34aSNan Zhou if (csrf.size() != persistent_data::sessionTokenSize) 170d055a34aSNan Zhou { 171d055a34aSNan Zhou return nullptr; 172d055a34aSNan Zhou } 173d055a34aSNan Zhou // Reject if csrf token not available 1749f217c26SEd Tanous if (!crow::utility::constantTimeStringCompare( 1759f217c26SEd Tanous csrf, sessionOut->csrfToken)) 176d055a34aSNan Zhou { 177d055a34aSNan Zhou return nullptr; 178d055a34aSNan Zhou } 179d055a34aSNan Zhou } 18025b54dbaSEd Tanous } 1813eaecac3SEd Tanous return sessionOut; 182d055a34aSNan Zhou } 1839f217c26SEd Tanous return nullptr; 1849f217c26SEd Tanous } 185d055a34aSNan Zhou 18625b54dbaSEd Tanous inline std::shared_ptr<persistent_data::UserSession> 187d055a34aSNan Zhou performTLSAuth(Response& res, 188d055a34aSNan Zhou const boost::beast::http::header<true>& reqHeader, 189d055a34aSNan Zhou const std::weak_ptr<persistent_data::UserSession>& session) 190d055a34aSNan Zhou { 191d055a34aSNan Zhou if (auto sp = session.lock()) 192d055a34aSNan Zhou { 193d055a34aSNan Zhou // set cookie only if this is req from the browser. 194d055a34aSNan Zhou if (reqHeader["User-Agent"].empty()) 195d055a34aSNan Zhou { 19662598e31SEd Tanous BMCWEB_LOG_DEBUG(" TLS session: {} will be used for this request.", 19762598e31SEd Tanous sp->uniqueId); 198d055a34aSNan Zhou return sp; 199d055a34aSNan Zhou } 200d055a34aSNan Zhou // TODO: change this to not switch to cookie auth 20129aab242SPaul Fertser bmcweb::setSessionCookies(res, *sp); 202994fd86aSEd Tanous res.addHeader(boost::beast::http::field::set_cookie, 203d055a34aSNan Zhou "IsAuthenticated=true; Secure"); 20462598e31SEd Tanous BMCWEB_LOG_DEBUG( 20562598e31SEd Tanous " TLS session: {} with cookie will be used for this request.", 20662598e31SEd Tanous sp->uniqueId); 207d055a34aSNan Zhou return sp; 208d055a34aSNan Zhou } 209d055a34aSNan Zhou return nullptr; 210d055a34aSNan Zhou } 211d055a34aSNan Zhou 212d055a34aSNan Zhou // checks if request can be forwarded without authentication 21325b54dbaSEd Tanous inline bool isOnAllowlist(std::string_view url, boost::beast::http::verb method) 214d055a34aSNan Zhou { 21538221509SEd Tanous // Handle the case where the router registers routes as both ending with / 21638221509SEd Tanous // and not. 2178f5df132SVinceChang6637 if (url.ends_with('/') && url != "/") 21838221509SEd Tanous { 21938221509SEd Tanous url.remove_suffix(1); 22038221509SEd Tanous } 221d055a34aSNan Zhou if (boost::beast::http::verb::get == method) 222d055a34aSNan Zhou { 22338221509SEd Tanous if ((url == "/redfish") || // 22438221509SEd Tanous (url == "/redfish/v1") || // 22538221509SEd Tanous (url == "/redfish/v1/odata") || // 22638221509SEd Tanous (url == "/redfish/v1/$metadata")) 227d055a34aSNan Zhou { 228d055a34aSNan Zhou return true; 229d055a34aSNan Zhou } 230d055a34aSNan Zhou if (crow::webroutes::routes.find(std::string(url)) != 231d055a34aSNan Zhou crow::webroutes::routes.end()) 232d055a34aSNan Zhou { 233d055a34aSNan Zhou return true; 234d055a34aSNan Zhou } 235d055a34aSNan Zhou } 236d055a34aSNan Zhou 237d055a34aSNan Zhou // it's allowed to POST on session collection & login without 238d055a34aSNan Zhou // authentication 239d055a34aSNan Zhou if (boost::beast::http::verb::post == method) 240d055a34aSNan Zhou { 241d055a34aSNan Zhou if ((url == "/redfish/v1/SessionService/Sessions") || 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 25225b54dbaSEd Tanous inline std::shared_ptr<persistent_data::UserSession> authenticate( 25302cad96eSEd Tanous const boost::asio::ip::address& ipAddress [[maybe_unused]], 2543acced2cSNan Zhou Response& res [[maybe_unused]], 2553acced2cSNan Zhou boost::beast::http::verb method [[maybe_unused]], 256d055a34aSNan Zhou const boost::beast::http::header<true>& reqHeader, 257d055a34aSNan Zhou [[maybe_unused]] const std::shared_ptr<persistent_data::UserSession>& 258d055a34aSNan Zhou session) 259d055a34aSNan Zhou { 260d055a34aSNan Zhou const persistent_data::AuthConfigMethods& authMethodsConfig = 261d055a34aSNan Zhou persistent_data::SessionStore::getInstance().getAuthMethodsConfig(); 262d055a34aSNan Zhou 263d055a34aSNan Zhou std::shared_ptr<persistent_data::UserSession> sessionOut = nullptr; 26425b54dbaSEd Tanous if constexpr (BMCWEB_MUTUAL_TLS_AUTH) 26525b54dbaSEd Tanous { 266d055a34aSNan Zhou if (authMethodsConfig.tls) 267d055a34aSNan Zhou { 268d055a34aSNan Zhou sessionOut = performTLSAuth(res, reqHeader, session); 269d055a34aSNan Zhou } 27025b54dbaSEd Tanous } 27125b54dbaSEd Tanous if constexpr (BMCWEB_XTOKEN_AUTH) 27225b54dbaSEd Tanous { 273d055a34aSNan Zhou if (sessionOut == nullptr && authMethodsConfig.xtoken) 274d055a34aSNan Zhou { 275d055a34aSNan Zhou sessionOut = performXtokenAuth(reqHeader); 276d055a34aSNan Zhou } 27725b54dbaSEd Tanous } 27825b54dbaSEd Tanous if constexpr (BMCWEB_COOKIE_AUTH) 27925b54dbaSEd Tanous { 280d055a34aSNan Zhou if (sessionOut == nullptr && authMethodsConfig.cookie) 281d055a34aSNan Zhou { 282d055a34aSNan Zhou sessionOut = performCookieAuth(method, reqHeader); 283d055a34aSNan Zhou } 28425b54dbaSEd Tanous } 285d055a34aSNan Zhou std::string_view authHeader = reqHeader["Authorization"]; 28662598e31SEd Tanous BMCWEB_LOG_DEBUG("authHeader={}", authHeader); 28725b54dbaSEd Tanous if constexpr (BMCWEB_SESSION_AUTH) 28825b54dbaSEd Tanous { 289d055a34aSNan Zhou if (sessionOut == nullptr && authMethodsConfig.sessionToken) 290d055a34aSNan Zhou { 291d055a34aSNan Zhou sessionOut = performTokenAuth(authHeader); 292d055a34aSNan Zhou } 29325b54dbaSEd Tanous } 29425b54dbaSEd Tanous if constexpr (BMCWEB_BASIC_AUTH) 29525b54dbaSEd Tanous { 296d055a34aSNan Zhou if (sessionOut == nullptr && authMethodsConfig.basic) 297d055a34aSNan Zhou { 298d055a34aSNan Zhou sessionOut = performBasicAuth(ipAddress, authHeader); 29925b54dbaSEd Tanous } 300d055a34aSNan Zhou } 301d055a34aSNan Zhou if (sessionOut != nullptr) 302d055a34aSNan Zhou { 303d055a34aSNan Zhou return sessionOut; 304d055a34aSNan Zhou } 305d055a34aSNan Zhou 306d055a34aSNan Zhou return nullptr; 307d055a34aSNan Zhou } 308d055a34aSNan Zhou 309d055a34aSNan Zhou } // namespace authentication 310d055a34aSNan Zhou } // namespace crow 311