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