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