1 #pragma once 2 3 #include "cookies.hpp" 4 #include "forward_unauthorized.hpp" 5 #include "http_request.hpp" 6 #include "http_response.hpp" 7 #include "http_utility.hpp" 8 #include "pam_authenticate.hpp" 9 #include "webroutes.hpp" 10 11 #include <boost/container/flat_set.hpp> 12 13 #include <random> 14 #include <utility> 15 16 namespace crow 17 { 18 19 namespace authentication 20 { 21 22 inline void cleanupTempSession(const Request& req) 23 { 24 // TODO(ed) THis should really be handled by the persistent data 25 // middleware, but because it is upstream, it doesn't have access to the 26 // session information. Should the data middleware persist the current 27 // user session? 28 if (req.session != nullptr && 29 req.session->persistence == 30 persistent_data::PersistenceType::SINGLE_REQUEST) 31 { 32 persistent_data::SessionStore::getInstance().removeSession(req.session); 33 } 34 } 35 36 inline 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 90 inline std::shared_ptr<persistent_data::UserSession> 91 performTokenAuth(std::string_view authHeader) 92 { 93 BMCWEB_LOG_DEBUG("[AuthMiddleware] Token authentication"); 94 if (!authHeader.starts_with("Token ")) 95 { 96 return nullptr; 97 } 98 std::string_view token = authHeader.substr(strlen("Token ")); 99 auto sessionOut = 100 persistent_data::SessionStore::getInstance().loginSessionByToken(token); 101 return sessionOut; 102 } 103 104 inline std::shared_ptr<persistent_data::UserSession> 105 performXtokenAuth(const boost::beast::http::header<true>& reqHeader) 106 { 107 BMCWEB_LOG_DEBUG("[AuthMiddleware] X-Auth-Token authentication"); 108 109 std::string_view token = reqHeader["X-Auth-Token"]; 110 if (token.empty()) 111 { 112 return nullptr; 113 } 114 auto sessionOut = 115 persistent_data::SessionStore::getInstance().loginSessionByToken(token); 116 return sessionOut; 117 } 118 119 inline std::shared_ptr<persistent_data::UserSession> 120 performCookieAuth(boost::beast::http::verb method [[maybe_unused]], 121 const boost::beast::http::header<true>& reqHeader) 122 { 123 using headers = boost::beast::http::header<true>; 124 std::pair<headers::const_iterator, headers::const_iterator> cookies = 125 reqHeader.equal_range(boost::beast::http::field::cookie); 126 127 for (auto it = cookies.first; it != cookies.second; it++) 128 { 129 std::string_view cookieValue = it->value(); 130 BMCWEB_LOG_DEBUG("Checking cookie {}", cookieValue); 131 auto startIndex = cookieValue.find("SESSION="); 132 if (startIndex == std::string::npos) 133 { 134 BMCWEB_LOG_DEBUG( 135 "Cookie was present, but didn't look like a session {}", 136 cookieValue); 137 continue; 138 } 139 startIndex += sizeof("SESSION=") - 1; 140 auto endIndex = cookieValue.find(';', startIndex); 141 if (endIndex == std::string::npos) 142 { 143 endIndex = cookieValue.size(); 144 } 145 std::string_view authKey = cookieValue.substr(startIndex, 146 endIndex - startIndex); 147 148 std::shared_ptr<persistent_data::UserSession> sessionOut = 149 persistent_data::SessionStore::getInstance().loginSessionByToken( 150 authKey); 151 if (sessionOut == nullptr) 152 { 153 return nullptr; 154 } 155 sessionOut->cookieAuth = true; 156 157 if constexpr (!BMCWEB_INSECURE_DISABLE_CSRF) 158 { 159 // RFC7231 defines methods that need csrf protection 160 if (method != boost::beast::http::verb::get) 161 { 162 std::string_view csrf = reqHeader["X-XSRF-TOKEN"]; 163 // Make sure both tokens are filled 164 if (csrf.empty() || sessionOut->csrfToken.empty()) 165 { 166 return nullptr; 167 } 168 169 if (csrf.size() != persistent_data::sessionTokenSize) 170 { 171 return nullptr; 172 } 173 // Reject if csrf token not available 174 if (!crow::utility::constantTimeStringCompare( 175 csrf, sessionOut->csrfToken)) 176 { 177 return nullptr; 178 } 179 } 180 } 181 return sessionOut; 182 } 183 return nullptr; 184 } 185 186 inline std::shared_ptr<persistent_data::UserSession> 187 performTLSAuth(Response& res, 188 const boost::beast::http::header<true>& reqHeader, 189 const std::weak_ptr<persistent_data::UserSession>& session) 190 { 191 if (auto sp = session.lock()) 192 { 193 // set cookie only if this is req from the browser. 194 if (reqHeader["User-Agent"].empty()) 195 { 196 BMCWEB_LOG_DEBUG(" TLS session: {} will be used for this request.", 197 sp->uniqueId); 198 return sp; 199 } 200 // TODO: change this to not switch to cookie auth 201 bmcweb::setSessionCookies(res, *sp); 202 res.addHeader(boost::beast::http::field::set_cookie, 203 "IsAuthenticated=true; Secure"); 204 BMCWEB_LOG_DEBUG( 205 " TLS session: {} with cookie will be used for this request.", 206 sp->uniqueId); 207 return sp; 208 } 209 return nullptr; 210 } 211 212 // checks if request can be forwarded without authentication 213 inline bool isOnAllowlist(std::string_view url, boost::beast::http::verb method) 214 { 215 // Handle the case where the router registers routes as both ending with / 216 // and not. 217 if (url.ends_with('/') && url != "/") 218 { 219 url.remove_suffix(1); 220 } 221 if (boost::beast::http::verb::get == method) 222 { 223 if ((url == "/redfish") || // 224 (url == "/redfish/v1") || // 225 (url == "/redfish/v1/odata") || // 226 (url == "/redfish/v1/$metadata")) 227 { 228 return true; 229 } 230 if (crow::webroutes::routes.find(std::string(url)) != 231 crow::webroutes::routes.end()) 232 { 233 return true; 234 } 235 } 236 237 // it's allowed to POST on session collection & login without 238 // authentication 239 if (boost::beast::http::verb::post == method) 240 { 241 if ((url == "/redfish/v1/SessionService/Sessions") || 242 (url == "/redfish/v1/SessionService/Sessions/Members") || 243 (url == "/login")) 244 { 245 return true; 246 } 247 } 248 249 return false; 250 } 251 252 inline std::shared_ptr<persistent_data::UserSession> authenticate( 253 const boost::asio::ip::address& ipAddress [[maybe_unused]], 254 Response& res [[maybe_unused]], 255 boost::beast::http::verb method [[maybe_unused]], 256 const boost::beast::http::header<true>& reqHeader, 257 [[maybe_unused]] const std::shared_ptr<persistent_data::UserSession>& 258 session) 259 { 260 const persistent_data::AuthConfigMethods& authMethodsConfig = 261 persistent_data::SessionStore::getInstance().getAuthMethodsConfig(); 262 263 std::shared_ptr<persistent_data::UserSession> sessionOut = nullptr; 264 if constexpr (BMCWEB_MUTUAL_TLS_AUTH) 265 { 266 if (authMethodsConfig.tls) 267 { 268 sessionOut = performTLSAuth(res, reqHeader, session); 269 } 270 } 271 if constexpr (BMCWEB_XTOKEN_AUTH) 272 { 273 if (sessionOut == nullptr && authMethodsConfig.xtoken) 274 { 275 sessionOut = performXtokenAuth(reqHeader); 276 } 277 } 278 if constexpr (BMCWEB_COOKIE_AUTH) 279 { 280 if (sessionOut == nullptr && authMethodsConfig.cookie) 281 { 282 sessionOut = performCookieAuth(method, reqHeader); 283 } 284 } 285 std::string_view authHeader = reqHeader["Authorization"]; 286 BMCWEB_LOG_DEBUG("authHeader={}", authHeader); 287 if constexpr (BMCWEB_SESSION_AUTH) 288 { 289 if (sessionOut == nullptr && authMethodsConfig.sessionToken) 290 { 291 sessionOut = performTokenAuth(authHeader); 292 } 293 } 294 if constexpr (BMCWEB_BASIC_AUTH) 295 { 296 if (sessionOut == nullptr && authMethodsConfig.basic) 297 { 298 sessionOut = performBasicAuth(ipAddress, authHeader); 299 } 300 } 301 if (sessionOut != nullptr) 302 { 303 return sessionOut; 304 } 305 306 return nullptr; 307 } 308 309 } // namespace authentication 310 } // namespace crow 311