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 inline std::shared_ptr<persistent_data::UserSession> 36 performBasicAuth(const boost::asio::ip::address& clientIp, 37 std::string_view authHeader) 38 { 39 BMCWEB_LOG_DEBUG("[AuthMiddleware] Basic authentication"); 40 41 if (!authHeader.starts_with("Basic ")) 42 { 43 return nullptr; 44 } 45 46 std::string_view param = authHeader.substr(strlen("Basic ")); 47 std::string authData; 48 49 if (!crow::utility::base64Decode(param, authData)) 50 { 51 return nullptr; 52 } 53 std::size_t separator = authData.find(':'); 54 if (separator == std::string::npos) 55 { 56 return nullptr; 57 } 58 59 std::string user = authData.substr(0, separator); 60 separator += 1; 61 if (separator > authData.size()) 62 { 63 return nullptr; 64 } 65 std::string pass = authData.substr(separator); 66 67 BMCWEB_LOG_DEBUG("[AuthMiddleware] Authenticating user: {}", user); 68 BMCWEB_LOG_DEBUG("[AuthMiddleware] User IPAddress: {}", 69 clientIp.to_string()); 70 71 int pamrc = pamAuthenticateUser(user, pass); 72 bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD; 73 if ((pamrc != PAM_SUCCESS) && !isConfigureSelfOnly) 74 { 75 return nullptr; 76 } 77 78 // TODO(ed) generateUserSession is a little expensive for basic 79 // auth, as it generates some random identifiers that will never be 80 // used. This should have a "fast" path for when user tokens aren't 81 // needed. 82 // This whole flow needs to be revisited anyway, as we can't be 83 // calling directly into pam for every request 84 return persistent_data::SessionStore::getInstance().generateUserSession( 85 user, clientIp, std::nullopt, 86 persistent_data::PersistenceType::SINGLE_REQUEST, isConfigureSelfOnly); 87 } 88 89 inline std::shared_ptr<persistent_data::UserSession> 90 performTokenAuth(std::string_view authHeader) 91 { 92 BMCWEB_LOG_DEBUG("[AuthMiddleware] Token authentication"); 93 if (!authHeader.starts_with("Token ")) 94 { 95 return nullptr; 96 } 97 std::string_view token = authHeader.substr(strlen("Token ")); 98 auto sessionOut = 99 persistent_data::SessionStore::getInstance().loginSessionByToken(token); 100 return sessionOut; 101 } 102 103 inline std::shared_ptr<persistent_data::UserSession> 104 performXtokenAuth(const boost::beast::http::header<true>& reqHeader) 105 { 106 BMCWEB_LOG_DEBUG("[AuthMiddleware] X-Auth-Token authentication"); 107 108 std::string_view token = reqHeader["X-Auth-Token"]; 109 if (token.empty()) 110 { 111 return nullptr; 112 } 113 auto sessionOut = 114 persistent_data::SessionStore::getInstance().loginSessionByToken(token); 115 return sessionOut; 116 } 117 118 inline std::shared_ptr<persistent_data::UserSession> 119 performCookieAuth(boost::beast::http::verb method [[maybe_unused]], 120 const boost::beast::http::header<true>& reqHeader) 121 { 122 using headers = boost::beast::http::header<true>; 123 std::pair<headers::const_iterator, headers::const_iterator> cookies = 124 reqHeader.equal_range(boost::beast::http::field::cookie); 125 126 for (auto it = cookies.first; it != cookies.second; it++) 127 { 128 std::string_view cookieValue = it->value(); 129 BMCWEB_LOG_DEBUG("Checking cookie {}", cookieValue); 130 auto startIndex = cookieValue.find("SESSION="); 131 if (startIndex == std::string::npos) 132 { 133 BMCWEB_LOG_DEBUG( 134 "Cookie was present, but didn't look like a session {}", 135 cookieValue); 136 continue; 137 } 138 startIndex += sizeof("SESSION=") - 1; 139 auto endIndex = cookieValue.find(';', startIndex); 140 if (endIndex == std::string::npos) 141 { 142 endIndex = cookieValue.size(); 143 } 144 std::string_view authKey = cookieValue.substr(startIndex, 145 endIndex - startIndex); 146 147 std::shared_ptr<persistent_data::UserSession> sessionOut = 148 persistent_data::SessionStore::getInstance().loginSessionByToken( 149 authKey); 150 if (sessionOut == nullptr) 151 { 152 return nullptr; 153 } 154 sessionOut->cookieAuth = true; 155 156 if constexpr (BMCWEB_INSECURE_DISABLE_CSRF) 157 { 158 // RFC7231 defines methods that need csrf protection 159 if (method != boost::beast::http::verb::get) 160 { 161 std::string_view csrf = reqHeader["X-XSRF-TOKEN"]; 162 // Make sure both tokens are filled 163 if (csrf.empty() || sessionOut->csrfToken.empty()) 164 { 165 return nullptr; 166 } 167 168 if (csrf.size() != persistent_data::sessionTokenSize) 169 { 170 return nullptr; 171 } 172 // Reject if csrf token not available 173 if (!crow::utility::constantTimeStringCompare( 174 csrf, sessionOut->csrfToken)) 175 { 176 return nullptr; 177 } 178 } 179 } 180 return sessionOut; 181 } 182 return nullptr; 183 } 184 185 inline std::shared_ptr<persistent_data::UserSession> 186 performTLSAuth(Response& res, 187 const boost::beast::http::header<true>& reqHeader, 188 const std::weak_ptr<persistent_data::UserSession>& session) 189 { 190 if (auto sp = session.lock()) 191 { 192 // set cookie only if this is req from the browser. 193 if (reqHeader["User-Agent"].empty()) 194 { 195 BMCWEB_LOG_DEBUG(" TLS session: {} will be used for this request.", 196 sp->uniqueId); 197 return sp; 198 } 199 // TODO: change this to not switch to cookie auth 200 res.addHeader(boost::beast::http::field::set_cookie, 201 "XSRF-TOKEN=" + sp->csrfToken + 202 "; SameSite=Strict; Secure"); 203 res.addHeader(boost::beast::http::field::set_cookie, 204 "SESSION=" + sp->sessionToken + 205 "; SameSite=Strict; Secure; HttpOnly"); 206 res.addHeader(boost::beast::http::field::set_cookie, 207 "IsAuthenticated=true; Secure"); 208 BMCWEB_LOG_DEBUG( 209 " TLS session: {} with cookie will be used for this request.", 210 sp->uniqueId); 211 return sp; 212 } 213 return nullptr; 214 } 215 216 // checks if request can be forwarded without authentication 217 inline bool isOnAllowlist(std::string_view url, boost::beast::http::verb method) 218 { 219 if (boost::beast::http::verb::get == method) 220 { 221 if (url == "/redfish/v1" || url == "/redfish/v1/" || 222 url == "/redfish" || url == "/redfish/" || 223 url == "/redfish/v1/odata" || url == "/redfish/v1/odata/") 224 { 225 return true; 226 } 227 if (crow::webroutes::routes.find(std::string(url)) != 228 crow::webroutes::routes.end()) 229 { 230 return true; 231 } 232 } 233 234 // it's allowed to POST on session collection & login without 235 // authentication 236 if (boost::beast::http::verb::post == method) 237 { 238 if ((url == "/redfish/v1/SessionService/Sessions") || 239 (url == "/redfish/v1/SessionService/Sessions/") || 240 (url == "/redfish/v1/SessionService/Sessions/Members") || 241 (url == "/redfish/v1/SessionService/Sessions/Members/") || 242 (url == "/login")) 243 { 244 return true; 245 } 246 } 247 248 return false; 249 } 250 251 inline std::shared_ptr<persistent_data::UserSession> authenticate( 252 const boost::asio::ip::address& ipAddress [[maybe_unused]], 253 Response& res [[maybe_unused]], 254 boost::beast::http::verb method [[maybe_unused]], 255 const boost::beast::http::header<true>& reqHeader, 256 [[maybe_unused]] const std::shared_ptr<persistent_data::UserSession>& 257 session) 258 { 259 const persistent_data::AuthConfigMethods& authMethodsConfig = 260 persistent_data::SessionStore::getInstance().getAuthMethodsConfig(); 261 262 std::shared_ptr<persistent_data::UserSession> sessionOut = nullptr; 263 if constexpr (BMCWEB_MUTUAL_TLS_AUTH) 264 { 265 if (authMethodsConfig.tls) 266 { 267 sessionOut = performTLSAuth(res, reqHeader, session); 268 } 269 } 270 if constexpr (BMCWEB_XTOKEN_AUTH) 271 { 272 if (sessionOut == nullptr && authMethodsConfig.xtoken) 273 { 274 sessionOut = performXtokenAuth(reqHeader); 275 } 276 } 277 if constexpr (BMCWEB_COOKIE_AUTH) 278 { 279 if (sessionOut == nullptr && authMethodsConfig.cookie) 280 { 281 sessionOut = performCookieAuth(method, reqHeader); 282 } 283 } 284 std::string_view authHeader = reqHeader["Authorization"]; 285 BMCWEB_LOG_DEBUG("authHeader={}", authHeader); 286 if constexpr (BMCWEB_SESSION_AUTH) 287 { 288 if (sessionOut == nullptr && authMethodsConfig.sessionToken) 289 { 290 sessionOut = performTokenAuth(authHeader); 291 } 292 } 293 if constexpr (BMCWEB_BASIC_AUTH) 294 { 295 if (sessionOut == nullptr && authMethodsConfig.basic) 296 { 297 sessionOut = performBasicAuth(ipAddress, authHeader); 298 } 299 } 300 if (sessionOut != nullptr) 301 { 302 return sessionOut; 303 } 304 305 return nullptr; 306 } 307 308 } // namespace authentication 309 } // namespace crow 310