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 std::shared_ptr<persistent_data::UserSession> performBasicAuth( 23 const boost::asio::ip::address& clientIp, std::string_view authHeader) 24 { 25 BMCWEB_LOG_DEBUG("[AuthMiddleware] Basic authentication"); 26 27 if (!authHeader.starts_with("Basic ")) 28 { 29 return nullptr; 30 } 31 32 std::string_view param = authHeader.substr(strlen("Basic ")); 33 std::string authData; 34 35 if (!crow::utility::base64Decode(param, authData)) 36 { 37 return nullptr; 38 } 39 std::size_t separator = authData.find(':'); 40 if (separator == std::string::npos) 41 { 42 return nullptr; 43 } 44 45 std::string user = authData.substr(0, separator); 46 separator += 1; 47 if (separator > authData.size()) 48 { 49 return nullptr; 50 } 51 std::string pass = authData.substr(separator); 52 53 BMCWEB_LOG_DEBUG("[AuthMiddleware] Authenticating user: {}", user); 54 BMCWEB_LOG_DEBUG("[AuthMiddleware] User IPAddress: {}", 55 clientIp.to_string()); 56 57 int pamrc = pamAuthenticateUser(user, pass, std::nullopt); 58 bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD; 59 if ((pamrc != PAM_SUCCESS) && !isConfigureSelfOnly) 60 { 61 return nullptr; 62 } 63 64 // Attempt to locate an existing Basic Auth session from the same ip address 65 // and user 66 for (auto& session : 67 persistent_data::SessionStore::getInstance().getSessions()) 68 { 69 if (session->sessionType != persistent_data::SessionType::Basic) 70 { 71 continue; 72 } 73 if (session->clientIp != redfish::ip_util::toString(clientIp)) 74 { 75 continue; 76 } 77 if (session->username != user) 78 { 79 continue; 80 } 81 return session; 82 } 83 84 return persistent_data::SessionStore::getInstance().generateUserSession( 85 user, clientIp, std::nullopt, persistent_data::SessionType::Basic, 86 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 = 145 cookieValue.substr(startIndex, 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 (!bmcweb::constantTimeStringCompare(csrf, 174 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> performTLSAuth( 186 Response& res, const std::shared_ptr<persistent_data::UserSession>& session) 187 { 188 if (session != nullptr) 189 { 190 res.addHeader(boost::beast::http::field::set_cookie, 191 "IsAuthenticated=true; Secure"); 192 BMCWEB_LOG_DEBUG( 193 " TLS session: {} with cookie will be used for this request.", 194 session->uniqueId); 195 } 196 197 return session; 198 } 199 200 // checks if request can be forwarded without authentication 201 inline bool isOnAllowlist(std::string_view url, boost::beast::http::verb method) 202 { 203 // Handle the case where the router registers routes as both ending with / 204 // and not. 205 if (url.ends_with('/') && url != "/") 206 { 207 url.remove_suffix(1); 208 } 209 if (boost::beast::http::verb::get == method) 210 { 211 if ((url == "/redfish") || // 212 (url == "/redfish/v1") || // 213 (url == "/redfish/v1/odata") || // 214 (url == "/redfish/v1/$metadata")) 215 { 216 return true; 217 } 218 if (crow::webroutes::routes.find(std::string(url)) != 219 crow::webroutes::routes.end()) 220 { 221 return true; 222 } 223 } 224 225 // it's allowed to POST on session collection & login without 226 // authentication 227 if (boost::beast::http::verb::post == method) 228 { 229 if ((url == "/redfish/v1/SessionService/Sessions") || 230 (url == "/redfish/v1/SessionService/Sessions/Members") || 231 (url == "/login")) 232 { 233 return true; 234 } 235 } 236 237 return false; 238 } 239 240 inline std::shared_ptr<persistent_data::UserSession> authenticate( 241 const boost::asio::ip::address& ipAddress [[maybe_unused]], 242 Response& res [[maybe_unused]], 243 boost::beast::http::verb method [[maybe_unused]], 244 const boost::beast::http::header<true>& reqHeader, 245 [[maybe_unused]] const std::shared_ptr<persistent_data::UserSession>& 246 session) 247 { 248 const persistent_data::AuthConfigMethods& authMethodsConfig = 249 persistent_data::SessionStore::getInstance().getAuthMethodsConfig(); 250 251 std::shared_ptr<persistent_data::UserSession> sessionOut = nullptr; 252 if constexpr (BMCWEB_MUTUAL_TLS_AUTH) 253 { 254 if (authMethodsConfig.tls) 255 { 256 sessionOut = performTLSAuth(res, session); 257 } 258 } 259 if constexpr (BMCWEB_XTOKEN_AUTH) 260 { 261 if (sessionOut == nullptr && authMethodsConfig.xtoken) 262 { 263 sessionOut = performXtokenAuth(reqHeader); 264 } 265 } 266 if constexpr (BMCWEB_COOKIE_AUTH) 267 { 268 if (sessionOut == nullptr && authMethodsConfig.cookie) 269 { 270 sessionOut = performCookieAuth(method, reqHeader); 271 } 272 } 273 std::string_view authHeader = reqHeader["Authorization"]; 274 BMCWEB_LOG_DEBUG("authHeader={}", authHeader); 275 if constexpr (BMCWEB_SESSION_AUTH) 276 { 277 if (sessionOut == nullptr && authMethodsConfig.sessionToken) 278 { 279 sessionOut = performTokenAuth(authHeader); 280 } 281 } 282 if constexpr (BMCWEB_BASIC_AUTH) 283 { 284 if (sessionOut == nullptr && authMethodsConfig.basic) 285 { 286 sessionOut = performBasicAuth(ipAddress, authHeader); 287 } 288 } 289 if (sessionOut != nullptr) 290 { 291 return sessionOut; 292 } 293 294 return nullptr; 295 } 296 297 } // namespace authentication 298 } // namespace crow 299