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 using headers = boost::beast::http::header<true>; 130 std::pair<headers::const_iterator, headers::const_iterator> cookies = 131 reqHeader.equal_range(boost::beast::http::field::cookie); 132 133 for (auto it = cookies.first; it != cookies.second; it++) 134 { 135 std::string_view cookieValue = it->value(); 136 BMCWEB_LOG_DEBUG("Checking cookie {}", cookieValue); 137 auto startIndex = cookieValue.find("SESSION="); 138 if (startIndex == std::string::npos) 139 { 140 BMCWEB_LOG_DEBUG( 141 "Cookie was present, but didn't look like a session {}", 142 cookieValue); 143 continue; 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 = cookieValue.substr(startIndex, 152 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 sessionOut->cookieAuth = true; 162 #ifndef BMCWEB_INSECURE_DISABLE_CSRF_PREVENTION 163 // RFC7231 defines methods that need csrf protection 164 if (method != boost::beast::http::verb::get) 165 { 166 std::string_view csrf = reqHeader["X-XSRF-TOKEN"]; 167 // Make sure both tokens are filled 168 if (csrf.empty() || sessionOut->csrfToken.empty()) 169 { 170 return nullptr; 171 } 172 173 if (csrf.size() != persistent_data::sessionTokenSize) 174 { 175 return nullptr; 176 } 177 // Reject if csrf token not available 178 if (!crow::utility::constantTimeStringCompare( 179 csrf, sessionOut->csrfToken)) 180 { 181 return nullptr; 182 } 183 } 184 #endif 185 return sessionOut; 186 } 187 return nullptr; 188 } 189 #endif 190 191 #ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION 192 static std::shared_ptr<persistent_data::UserSession> 193 performTLSAuth(Response& res, 194 const boost::beast::http::header<true>& reqHeader, 195 const std::weak_ptr<persistent_data::UserSession>& session) 196 { 197 if (auto sp = session.lock()) 198 { 199 // set cookie only if this is req from the browser. 200 if (reqHeader["User-Agent"].empty()) 201 { 202 BMCWEB_LOG_DEBUG(" TLS session: {} will be used for this request.", 203 sp->uniqueId); 204 return sp; 205 } 206 // TODO: change this to not switch to cookie auth 207 res.addHeader(boost::beast::http::field::set_cookie, 208 "XSRF-TOKEN=" + sp->csrfToken + 209 "; SameSite=Strict; Secure"); 210 res.addHeader(boost::beast::http::field::set_cookie, 211 "SESSION=" + sp->sessionToken + 212 "; SameSite=Strict; Secure; HttpOnly"); 213 res.addHeader(boost::beast::http::field::set_cookie, 214 "IsAuthenticated=true; Secure"); 215 BMCWEB_LOG_DEBUG( 216 " TLS session: {} with cookie will be used for this request.", 217 sp->uniqueId); 218 return sp; 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