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 [[maybe_unused]], 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 = cookieValue.substr(startIndex, 150 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 sessionOut->cookieAuth = true; 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: {} will be used for this request.", 199 sp->uniqueId); 200 return sp; 201 } 202 // TODO: change this to not switch to cookie auth 203 res.addHeader(boost::beast::http::field::set_cookie, 204 "XSRF-TOKEN=" + sp->csrfToken + 205 "; SameSite=Strict; Secure"); 206 res.addHeader(boost::beast::http::field::set_cookie, 207 "SESSION=" + sp->sessionToken + 208 "; SameSite=Strict; Secure; HttpOnly"); 209 res.addHeader(boost::beast::http::field::set_cookie, 210 "IsAuthenticated=true; Secure"); 211 BMCWEB_LOG_DEBUG( 212 " TLS session: {} with cookie will be used for this request.", 213 sp->uniqueId); 214 return sp; 215 } 216 return nullptr; 217 } 218 #endif 219 220 // checks if request can be forwarded without authentication 221 [[maybe_unused]] static bool isOnAllowlist(std::string_view url, 222 boost::beast::http::verb method) 223 { 224 if (boost::beast::http::verb::get == method) 225 { 226 if (url == "/redfish/v1" || url == "/redfish/v1/" || 227 url == "/redfish" || url == "/redfish/" || 228 url == "/redfish/v1/odata" || url == "/redfish/v1/odata/") 229 { 230 return true; 231 } 232 if (crow::webroutes::routes.find(std::string(url)) != 233 crow::webroutes::routes.end()) 234 { 235 return true; 236 } 237 } 238 239 // it's allowed to POST on session collection & login without 240 // authentication 241 if (boost::beast::http::verb::post == method) 242 { 243 if ((url == "/redfish/v1/SessionService/Sessions") || 244 (url == "/redfish/v1/SessionService/Sessions/") || 245 (url == "/redfish/v1/SessionService/Sessions/Members") || 246 (url == "/redfish/v1/SessionService/Sessions/Members/") || 247 (url == "/login")) 248 { 249 return true; 250 } 251 } 252 253 return false; 254 } 255 256 [[maybe_unused]] static std::shared_ptr<persistent_data::UserSession> 257 authenticate( 258 const boost::asio::ip::address& ipAddress [[maybe_unused]], 259 Response& res [[maybe_unused]], 260 boost::beast::http::verb method [[maybe_unused]], 261 const boost::beast::http::header<true>& reqHeader, 262 [[maybe_unused]] const std::shared_ptr<persistent_data::UserSession>& 263 session) 264 { 265 const persistent_data::AuthConfigMethods& authMethodsConfig = 266 persistent_data::SessionStore::getInstance().getAuthMethodsConfig(); 267 268 std::shared_ptr<persistent_data::UserSession> sessionOut = nullptr; 269 #ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION 270 if (authMethodsConfig.tls) 271 { 272 sessionOut = performTLSAuth(res, reqHeader, session); 273 } 274 #endif 275 #ifdef BMCWEB_ENABLE_XTOKEN_AUTHENTICATION 276 if (sessionOut == nullptr && authMethodsConfig.xtoken) 277 { 278 sessionOut = performXtokenAuth(reqHeader); 279 } 280 #endif 281 #ifdef BMCWEB_ENABLE_COOKIE_AUTHENTICATION 282 if (sessionOut == nullptr && authMethodsConfig.cookie) 283 { 284 sessionOut = performCookieAuth(method, reqHeader); 285 } 286 #endif 287 std::string_view authHeader = reqHeader["Authorization"]; 288 BMCWEB_LOG_DEBUG("authHeader={}", authHeader); 289 290 if (sessionOut == nullptr && authMethodsConfig.sessionToken) 291 { 292 #ifdef BMCWEB_ENABLE_SESSION_AUTHENTICATION 293 sessionOut = performTokenAuth(authHeader); 294 #endif 295 } 296 if (sessionOut == nullptr && authMethodsConfig.basic) 297 { 298 #ifdef BMCWEB_ENABLE_BASIC_AUTHENTICATION 299 sessionOut = performBasicAuth(ipAddress, authHeader); 300 #endif 301 } 302 if (sessionOut != nullptr) 303 { 304 return sessionOut; 305 } 306 307 return nullptr; 308 } 309 310 } // namespace authentication 311 } // namespace crow 312