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