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