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 std::string unsupportedClientId; 88 return persistent_data::SessionStore::getInstance().generateUserSession( 89 user, clientIp, unsupportedClientId, 90 persistent_data::PersistenceType::SINGLE_REQUEST, isConfigureSelfOnly); 91 } 92 #endif 93 94 #ifdef BMCWEB_ENABLE_SESSION_AUTHENTICATION 95 static std::shared_ptr<persistent_data::UserSession> 96 performTokenAuth(std::string_view authHeader) 97 { 98 BMCWEB_LOG_DEBUG << "[AuthMiddleware] Token authentication"; 99 if (!authHeader.starts_with("Token ")) 100 { 101 return nullptr; 102 } 103 std::string_view token = authHeader.substr(strlen("Token ")); 104 auto sessionOut = 105 persistent_data::SessionStore::getInstance().loginSessionByToken(token); 106 return sessionOut; 107 } 108 #endif 109 110 #ifdef BMCWEB_ENABLE_XTOKEN_AUTHENTICATION 111 static std::shared_ptr<persistent_data::UserSession> 112 performXtokenAuth(const boost::beast::http::header<true>& reqHeader) 113 { 114 BMCWEB_LOG_DEBUG << "[AuthMiddleware] X-Auth-Token authentication"; 115 116 std::string_view token = reqHeader["X-Auth-Token"]; 117 if (token.empty()) 118 { 119 return nullptr; 120 } 121 auto sessionOut = 122 persistent_data::SessionStore::getInstance().loginSessionByToken(token); 123 return sessionOut; 124 } 125 #endif 126 127 #ifdef BMCWEB_ENABLE_COOKIE_AUTHENTICATION 128 static std::shared_ptr<persistent_data::UserSession> 129 performCookieAuth(boost::beast::http::verb method, 130 const boost::beast::http::header<true>& reqHeader) 131 { 132 BMCWEB_LOG_DEBUG << "[AuthMiddleware] Cookie authentication"; 133 134 std::string_view cookieValue = reqHeader["Cookie"]; 135 if (cookieValue.empty()) 136 { 137 return nullptr; 138 } 139 140 auto startIndex = cookieValue.find("SESSION="); 141 if (startIndex == std::string::npos) 142 { 143 return nullptr; 144 } 145 startIndex += sizeof("SESSION=") - 1; 146 auto endIndex = cookieValue.find(';', startIndex); 147 if (endIndex == std::string::npos) 148 { 149 endIndex = cookieValue.size(); 150 } 151 std::string_view authKey = 152 cookieValue.substr(startIndex, endIndex - startIndex); 153 154 std::shared_ptr<persistent_data::UserSession> sessionOut = 155 persistent_data::SessionStore::getInstance().loginSessionByToken( 156 authKey); 157 if (sessionOut == nullptr) 158 { 159 return nullptr; 160 } 161 #ifndef BMCWEB_INSECURE_DISABLE_CSRF_PREVENTION 162 // RFC7231 defines methods that need csrf protection 163 if (method != boost::beast::http::verb::get) 164 { 165 std::string_view csrf = reqHeader["X-XSRF-TOKEN"]; 166 // Make sure both tokens are filled 167 if (csrf.empty() || sessionOut->csrfToken.empty()) 168 { 169 return nullptr; 170 } 171 172 if (csrf.size() != persistent_data::sessionTokenSize) 173 { 174 return nullptr; 175 } 176 // Reject if csrf token not available 177 if (!crow::utility::constantTimeStringCompare(csrf, 178 sessionOut->csrfToken)) 179 { 180 return nullptr; 181 } 182 } 183 #endif 184 return sessionOut; 185 } 186 #endif 187 188 #ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION 189 static std::shared_ptr<persistent_data::UserSession> 190 performTLSAuth(Response& res, 191 const boost::beast::http::header<true>& reqHeader, 192 const std::weak_ptr<persistent_data::UserSession>& session) 193 { 194 if (auto sp = session.lock()) 195 { 196 // set cookie only if this is req from the browser. 197 if (reqHeader["User-Agent"].empty()) 198 { 199 BMCWEB_LOG_DEBUG << " TLS session: " << sp->uniqueId 200 << " will be used for this request."; 201 return sp; 202 } 203 std::string_view cookieValue = reqHeader["Cookie"]; 204 if (cookieValue.empty() || 205 cookieValue.find("SESSION=") == std::string::npos) 206 { 207 // TODO: change this to not switch to cookie auth 208 res.addHeader( 209 "Set-Cookie", 210 "XSRF-TOKEN=" + sp->csrfToken + 211 "; SameSite=Strict; Secure\r\nSet-Cookie: SESSION=" + 212 sp->sessionToken + 213 "; SameSite=Strict; Secure; HttpOnly\r\nSet-Cookie: " 214 "IsAuthenticated=true; Secure"); 215 BMCWEB_LOG_DEBUG << " TLS session: " << sp->uniqueId 216 << " with cookie will be used for this request."; 217 return sp; 218 } 219 } 220 return nullptr; 221 } 222 #endif 223 224 // checks if request can be forwarded without authentication 225 [[maybe_unused]] static bool isOnAllowlist(std::string_view url, 226 boost::beast::http::verb method) 227 { 228 if (boost::beast::http::verb::get == method) 229 { 230 if (url == "/redfish/v1" || url == "/redfish/v1/" || 231 url == "/redfish" || url == "/redfish/" || 232 url == "/redfish/v1/odata" || url == "/redfish/v1/odata/") 233 { 234 return true; 235 } 236 if (crow::webroutes::routes.find(std::string(url)) != 237 crow::webroutes::routes.end()) 238 { 239 return true; 240 } 241 } 242 243 // it's allowed to POST on session collection & login without 244 // authentication 245 if (boost::beast::http::verb::post == method) 246 { 247 if ((url == "/redfish/v1/SessionService/Sessions") || 248 (url == "/redfish/v1/SessionService/Sessions/") || 249 (url == "/redfish/v1/SessionService/Sessions/Members") || 250 (url == "/redfish/v1/SessionService/Sessions/Members/") || 251 (url == "/login")) 252 { 253 return true; 254 } 255 } 256 257 return false; 258 } 259 260 [[maybe_unused]] static std::shared_ptr<persistent_data::UserSession> 261 authenticate( 262 const boost::asio::ip::address& ipAddress [[maybe_unused]], 263 Response& res [[maybe_unused]], 264 boost::beast::http::verb method [[maybe_unused]], 265 const boost::beast::http::header<true>& reqHeader, 266 [[maybe_unused]] const std::shared_ptr<persistent_data::UserSession>& 267 session) 268 { 269 const persistent_data::AuthConfigMethods& authMethodsConfig = 270 persistent_data::SessionStore::getInstance().getAuthMethodsConfig(); 271 272 std::shared_ptr<persistent_data::UserSession> sessionOut = nullptr; 273 #ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION 274 if (authMethodsConfig.tls) 275 { 276 sessionOut = performTLSAuth(res, reqHeader, session); 277 } 278 #endif 279 #ifdef BMCWEB_ENABLE_XTOKEN_AUTHENTICATION 280 if (sessionOut == nullptr && authMethodsConfig.xtoken) 281 { 282 sessionOut = performXtokenAuth(reqHeader); 283 } 284 #endif 285 #ifdef BMCWEB_ENABLE_COOKIE_AUTHENTICATION 286 if (sessionOut == nullptr && authMethodsConfig.cookie) 287 { 288 sessionOut = performCookieAuth(method, reqHeader); 289 } 290 #endif 291 std::string_view authHeader = reqHeader["Authorization"]; 292 BMCWEB_LOG_DEBUG << "authHeader=" << authHeader; 293 294 if (sessionOut == nullptr && authMethodsConfig.sessionToken) 295 { 296 #ifdef BMCWEB_ENABLE_SESSION_AUTHENTICATION 297 sessionOut = performTokenAuth(authHeader); 298 #endif 299 } 300 if (sessionOut == nullptr && authMethodsConfig.basic) 301 { 302 #ifdef BMCWEB_ENABLE_BASIC_AUTHENTICATION 303 sessionOut = performBasicAuth(ipAddress, authHeader); 304 #endif 305 } 306 if (sessionOut != nullptr) 307 { 308 return sessionOut; 309 } 310 311 return nullptr; 312 } 313 314 } // namespace authentication 315 } // namespace crow 316