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: " << 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(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 << " TLS session: " << sp->uniqueId 212 << " with cookie will be used for this request."; 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