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 } 181 return nullptr; 182 } 183 184 inline std::shared_ptr<persistent_data::UserSession> 185 performTLSAuth(Response& res, 186 const boost::beast::http::header<true>& reqHeader, 187 const std::weak_ptr<persistent_data::UserSession>& session) 188 { 189 if (auto sp = session.lock()) 190 { 191 // set cookie only if this is req from the browser. 192 if (reqHeader["User-Agent"].empty()) 193 { 194 BMCWEB_LOG_DEBUG(" TLS session: {} will be used for this request.", 195 sp->uniqueId); 196 return sp; 197 } 198 // TODO: change this to not switch to cookie auth 199 res.addHeader(boost::beast::http::field::set_cookie, 200 "XSRF-TOKEN=" + sp->csrfToken + 201 "; SameSite=Strict; Secure"); 202 res.addHeader(boost::beast::http::field::set_cookie, 203 "SESSION=" + sp->sessionToken + 204 "; SameSite=Strict; Secure; HttpOnly"); 205 res.addHeader(boost::beast::http::field::set_cookie, 206 "IsAuthenticated=true; Secure"); 207 BMCWEB_LOG_DEBUG( 208 " TLS session: {} with cookie will be used for this request.", 209 sp->uniqueId); 210 return sp; 211 } 212 return nullptr; 213 } 214 215 // checks if request can be forwarded without authentication 216 inline bool isOnAllowlist(std::string_view url, boost::beast::http::verb method) 217 { 218 if (boost::beast::http::verb::get == method) 219 { 220 if (url == "/redfish/v1" || url == "/redfish/v1/" || 221 url == "/redfish" || url == "/redfish/" || 222 url == "/redfish/v1/odata" || url == "/redfish/v1/odata/") 223 { 224 return true; 225 } 226 if (crow::webroutes::routes.find(std::string(url)) != 227 crow::webroutes::routes.end()) 228 { 229 return true; 230 } 231 } 232 233 // it's allowed to POST on session collection & login without 234 // authentication 235 if (boost::beast::http::verb::post == method) 236 { 237 if ((url == "/redfish/v1/SessionService/Sessions") || 238 (url == "/redfish/v1/SessionService/Sessions/") || 239 (url == "/redfish/v1/SessionService/Sessions/Members") || 240 (url == "/redfish/v1/SessionService/Sessions/Members/") || 241 (url == "/login")) 242 { 243 return true; 244 } 245 } 246 247 return false; 248 } 249 250 inline std::shared_ptr<persistent_data::UserSession> authenticate( 251 const boost::asio::ip::address& ipAddress [[maybe_unused]], 252 Response& res [[maybe_unused]], 253 boost::beast::http::verb method [[maybe_unused]], 254 const boost::beast::http::header<true>& reqHeader, 255 [[maybe_unused]] const std::shared_ptr<persistent_data::UserSession>& 256 session) 257 { 258 const persistent_data::AuthConfigMethods& authMethodsConfig = 259 persistent_data::SessionStore::getInstance().getAuthMethodsConfig(); 260 261 std::shared_ptr<persistent_data::UserSession> sessionOut = nullptr; 262 if constexpr (BMCWEB_MUTUAL_TLS_AUTH) 263 { 264 if (authMethodsConfig.tls) 265 { 266 sessionOut = performTLSAuth(res, reqHeader, session); 267 } 268 } 269 if constexpr (BMCWEB_XTOKEN_AUTH) 270 { 271 if (sessionOut == nullptr && authMethodsConfig.xtoken) 272 { 273 sessionOut = performXtokenAuth(reqHeader); 274 } 275 } 276 if constexpr (BMCWEB_COOKIE_AUTH) 277 { 278 if (sessionOut == nullptr && authMethodsConfig.cookie) 279 { 280 sessionOut = performCookieAuth(method, reqHeader); 281 } 282 } 283 std::string_view authHeader = reqHeader["Authorization"]; 284 BMCWEB_LOG_DEBUG("authHeader={}", authHeader); 285 if constexpr (BMCWEB_SESSION_AUTH) 286 { 287 if (sessionOut == nullptr && authMethodsConfig.sessionToken) 288 { 289 sessionOut = performTokenAuth(authHeader); 290 } 291 } 292 if constexpr (BMCWEB_BASIC_AUTH) 293 { 294 if (sessionOut == nullptr && authMethodsConfig.basic) 295 { 296 sessionOut = performBasicAuth(ipAddress, authHeader); 297 } 298 } 299 if (sessionOut != nullptr) 300 { 301 return sessionOut; 302 } 303 304 return nullptr; 305 } 306 307 } // namespace authentication 308 } // namespace crow 309