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 // Handle the case where the router registers routes as both ending with / 220 // and not. 221 if (url.ends_with('/')) 222 { 223 url.remove_suffix(1); 224 } 225 if (boost::beast::http::verb::get == method) 226 { 227 if ((url == "/redfish") || // 228 (url == "/redfish/v1") || // 229 (url == "/redfish/v1/odata") || // 230 (url == "/redfish/v1/$metadata")) 231 { 232 return true; 233 } 234 if (crow::webroutes::routes.find(std::string(url)) != 235 crow::webroutes::routes.end()) 236 { 237 return true; 238 } 239 } 240 241 // it's allowed to POST on session collection & login without 242 // authentication 243 if (boost::beast::http::verb::post == method) 244 { 245 if ((url == "/redfish/v1/SessionService/Sessions") || 246 (url == "/redfish/v1/SessionService/Sessions/Members") || 247 (url == "/login")) 248 { 249 return true; 250 } 251 } 252 253 return false; 254 } 255 256 inline std::shared_ptr<persistent_data::UserSession> authenticate( 257 const boost::asio::ip::address& ipAddress [[maybe_unused]], 258 Response& res [[maybe_unused]], 259 boost::beast::http::verb method [[maybe_unused]], 260 const boost::beast::http::header<true>& reqHeader, 261 [[maybe_unused]] const std::shared_ptr<persistent_data::UserSession>& 262 session) 263 { 264 const persistent_data::AuthConfigMethods& authMethodsConfig = 265 persistent_data::SessionStore::getInstance().getAuthMethodsConfig(); 266 267 std::shared_ptr<persistent_data::UserSession> sessionOut = nullptr; 268 if constexpr (BMCWEB_MUTUAL_TLS_AUTH) 269 { 270 if (authMethodsConfig.tls) 271 { 272 sessionOut = performTLSAuth(res, reqHeader, session); 273 } 274 } 275 if constexpr (BMCWEB_XTOKEN_AUTH) 276 { 277 if (sessionOut == nullptr && authMethodsConfig.xtoken) 278 { 279 sessionOut = performXtokenAuth(reqHeader); 280 } 281 } 282 if constexpr (BMCWEB_COOKIE_AUTH) 283 { 284 if (sessionOut == nullptr && authMethodsConfig.cookie) 285 { 286 sessionOut = performCookieAuth(method, reqHeader); 287 } 288 } 289 std::string_view authHeader = reqHeader["Authorization"]; 290 BMCWEB_LOG_DEBUG("authHeader={}", authHeader); 291 if constexpr (BMCWEB_SESSION_AUTH) 292 { 293 if (sessionOut == nullptr && authMethodsConfig.sessionToken) 294 { 295 sessionOut = performTokenAuth(authHeader); 296 } 297 } 298 if constexpr (BMCWEB_BASIC_AUTH) 299 { 300 if (sessionOut == nullptr && authMethodsConfig.basic) 301 { 302 sessionOut = performBasicAuth(ipAddress, authHeader); 303 } 304 } 305 if (sessionOut != nullptr) 306 { 307 return sessionOut; 308 } 309 310 return nullptr; 311 } 312 313 } // namespace authentication 314 } // namespace crow 315