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