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