blob: e6e699cb92a62e7fadeeb2b777b7f2b60098b82a [file] [log] [blame]
James Feist3909dc82020-04-03 10:58:55 -07001#pragma once
2
Ed Tanous3ccb3ad2023-01-13 17:40:03 -08003#include "forward_unauthorized.hpp"
4#include "http_request.hpp"
5#include "http_response.hpp"
6#include "http_utility.hpp"
7#include "pam_authenticate.hpp"
James Feist3909dc82020-04-03 10:58:55 -07008#include "webroutes.hpp"
9
James Feist3909dc82020-04-03 10:58:55 -070010#include <boost/container/flat_set.hpp>
James Feist3909dc82020-04-03 10:58:55 -070011
12#include <random>
Ed Tanousb5a76932020-09-29 16:16:58 -070013#include <utility>
James Feist3909dc82020-04-03 10:58:55 -070014
15namespace crow
16{
17
Nan Zhoud055a342022-05-25 01:15:34 +000018namespace authentication
James Feist3909dc82020-04-03 10:58:55 -070019{
20
Ed Tanousb1d736f2023-02-10 12:24:00 -080021inline void cleanupTempSession(const Request& req)
James Feist3909dc82020-04-03 10:58:55 -070022{
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 ==
Ed Tanous52cc1122020-07-18 13:51:21 -070029 persistent_data::PersistenceType::SINGLE_REQUEST)
James Feist3909dc82020-04-03 10:58:55 -070030 {
31 persistent_data::SessionStore::getInstance().removeSession(req.session);
32 }
33}
34
Ed Tanous25b54db2024-04-17 15:40:31 -070035inline std::shared_ptr<persistent_data::UserSession>
Sunitha Harishc0ea7ae2020-10-30 02:37:30 -050036 performBasicAuth(const boost::asio::ip::address& clientIp,
Ed Tanous81ce6092020-12-17 16:54:55 +000037 std::string_view authHeader)
James Feist3909dc82020-04-03 10:58:55 -070038{
Ed Tanous62598e32023-07-17 17:06:25 -070039 BMCWEB_LOG_DEBUG("[AuthMiddleware] Basic authentication");
James Feist3909dc82020-04-03 10:58:55 -070040
Ed Tanous11ba3972022-07-11 09:50:41 -070041 if (!authHeader.starts_with("Basic "))
John Edward Broadbent97a056a2021-09-10 14:56:19 -070042 {
43 return nullptr;
44 }
45
Ed Tanous81ce6092020-12-17 16:54:55 +000046 std::string_view param = authHeader.substr(strlen("Basic "));
John Edward Broadbent97a056a2021-09-10 14:56:19 -070047 std::string authData;
48
James Feist3909dc82020-04-03 10:58:55 -070049 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
Ed Tanous62598e32023-07-17 17:06:25 -070067 BMCWEB_LOG_DEBUG("[AuthMiddleware] Authenticating user: {}", user);
68 BMCWEB_LOG_DEBUG("[AuthMiddleware] User IPAddress: {}",
69 clientIp.to_string());
James Feist3909dc82020-04-03 10:58:55 -070070
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(
Ed Tanousbb759e32022-08-02 17:07:54 -070085 user, clientIp, std::nullopt,
Sunitha Harishd3239222021-02-24 15:33:29 +053086 persistent_data::PersistenceType::SINGLE_REQUEST, isConfigureSelfOnly);
James Feist3909dc82020-04-03 10:58:55 -070087}
88
Ed Tanous25b54db2024-04-17 15:40:31 -070089inline std::shared_ptr<persistent_data::UserSession>
Ed Tanous81ce6092020-12-17 16:54:55 +000090 performTokenAuth(std::string_view authHeader)
James Feist3909dc82020-04-03 10:58:55 -070091{
Ed Tanous62598e32023-07-17 17:06:25 -070092 BMCWEB_LOG_DEBUG("[AuthMiddleware] Token authentication");
Ed Tanous11ba3972022-07-11 09:50:41 -070093 if (!authHeader.starts_with("Token "))
John Edward Broadbent97a056a2021-09-10 14:56:19 -070094 {
95 return nullptr;
96 }
Ed Tanous81ce6092020-12-17 16:54:55 +000097 std::string_view token = authHeader.substr(strlen("Token "));
John Edward Broadbent59b98b22021-07-13 15:36:32 -070098 auto sessionOut =
James Feist3909dc82020-04-03 10:58:55 -070099 persistent_data::SessionStore::getInstance().loginSessionByToken(token);
John Edward Broadbent59b98b22021-07-13 15:36:32 -0700100 return sessionOut;
James Feist3909dc82020-04-03 10:58:55 -0700101}
102
Ed Tanous25b54db2024-04-17 15:40:31 -0700103inline std::shared_ptr<persistent_data::UserSession>
John Edward Broadbent59b98b22021-07-13 15:36:32 -0700104 performXtokenAuth(const boost::beast::http::header<true>& reqHeader)
James Feist3909dc82020-04-03 10:58:55 -0700105{
Ed Tanous62598e32023-07-17 17:06:25 -0700106 BMCWEB_LOG_DEBUG("[AuthMiddleware] X-Auth-Token authentication");
James Feist3909dc82020-04-03 10:58:55 -0700107
John Edward Broadbent59b98b22021-07-13 15:36:32 -0700108 std::string_view token = reqHeader["X-Auth-Token"];
James Feist3909dc82020-04-03 10:58:55 -0700109 if (token.empty())
110 {
111 return nullptr;
112 }
John Edward Broadbent59b98b22021-07-13 15:36:32 -0700113 auto sessionOut =
James Feist3909dc82020-04-03 10:58:55 -0700114 persistent_data::SessionStore::getInstance().loginSessionByToken(token);
John Edward Broadbent59b98b22021-07-13 15:36:32 -0700115 return sessionOut;
James Feist3909dc82020-04-03 10:58:55 -0700116}
117
Ed Tanous25b54db2024-04-17 15:40:31 -0700118inline std::shared_ptr<persistent_data::UserSession>
Ed Tanous1d869602022-12-19 13:48:12 -0800119 performCookieAuth(boost::beast::http::verb method [[maybe_unused]],
John Edward Broadbent59b98b22021-07-13 15:36:32 -0700120 const boost::beast::http::header<true>& reqHeader)
James Feist3909dc82020-04-03 10:58:55 -0700121{
Ed Tanous9f217c22024-04-19 17:22:43 -0700122 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);
James Feist3909dc82020-04-03 10:58:55 -0700125
Ed Tanous9f217c22024-04-19 17:22:43 -0700126 for (auto it = cookies.first; it != cookies.second; it++)
James Feist3909dc82020-04-03 10:58:55 -0700127 {
Ed Tanous9f217c22024-04-19 17:22:43 -0700128 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);
James Feist3909dc82020-04-03 10:58:55 -0700146
Ed Tanous9f217c22024-04-19 17:22:43 -0700147 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;
James Feist3909dc82020-04-03 10:58:55 -0700155
Ed Tanous576db692024-05-10 16:37:56 -0700156 if constexpr (!BMCWEB_INSECURE_DISABLE_CSRF)
Ed Tanous25b54db2024-04-17 15:40:31 -0700157 {
158 // RFC7231 defines methods that need csrf protection
159 if (method != boost::beast::http::verb::get)
Ed Tanous9f217c22024-04-19 17:22:43 -0700160 {
Ed Tanous25b54db2024-04-17 15:40:31 -0700161 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 }
Ed Tanous9f217c22024-04-19 17:22:43 -0700178 }
James Feist3909dc82020-04-03 10:58:55 -0700179 }
Ed Tanous3eaecac2024-05-08 16:26:24 -0700180 return sessionOut;
Ed Tanous9f217c22024-04-19 17:22:43 -0700181 }
182 return nullptr;
James Feist3909dc82020-04-03 10:58:55 -0700183}
184
Ed Tanous25b54db2024-04-17 15:40:31 -0700185inline std::shared_ptr<persistent_data::UserSession>
John Edward Broadbent59b98b22021-07-13 15:36:32 -0700186 performTLSAuth(Response& res,
187 const boost::beast::http::header<true>& reqHeader,
Ed Tanousb5a76932020-09-29 16:16:58 -0700188 const std::weak_ptr<persistent_data::UserSession>& session)
James Feist6964c982020-07-28 16:10:23 -0700189{
James Feist6964c982020-07-28 16:10:23 -0700190 if (auto sp = session.lock())
191 {
192 // set cookie only if this is req from the browser.
John Edward Broadbent59b98b22021-07-13 15:36:32 -0700193 if (reqHeader["User-Agent"].empty())
James Feist6964c982020-07-28 16:10:23 -0700194 {
Ed Tanous62598e32023-07-17 17:06:25 -0700195 BMCWEB_LOG_DEBUG(" TLS session: {} will be used for this request.",
196 sp->uniqueId);
James Feist6964c982020-07-28 16:10:23 -0700197 return sp;
198 }
Karol Niczyjade2fe72022-04-27 18:04:58 +0200199 // TODO: change this to not switch to cookie auth
Ed Tanous994fd862023-06-06 13:37:03 -0700200 res.addHeader(boost::beast::http::field::set_cookie,
Karol Niczyjade2fe72022-04-27 18:04:58 +0200201 "XSRF-TOKEN=" + sp->csrfToken +
Ed Tanous994fd862023-06-06 13:37:03 -0700202 "; 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");
Ed Tanous62598e32023-07-17 17:06:25 -0700208 BMCWEB_LOG_DEBUG(
209 " TLS session: {} with cookie will be used for this request.",
210 sp->uniqueId);
Karol Niczyjade2fe72022-04-27 18:04:58 +0200211 return sp;
James Feist6964c982020-07-28 16:10:23 -0700212 }
James Feist6964c982020-07-28 16:10:23 -0700213 return nullptr;
214}
215
James Feist3909dc82020-04-03 10:58:55 -0700216// checks if request can be forwarded without authentication
Ed Tanous25b54db2024-04-17 15:40:31 -0700217inline bool isOnAllowlist(std::string_view url, boost::beast::http::verb method)
James Feist3909dc82020-04-03 10:58:55 -0700218{
Ed Tanous38221502024-06-03 12:19:38 -0700219 // Handle the case where the router registers routes as both ending with /
220 // and not.
VinceChang66378f5df132024-06-14 17:13:16 +0800221 if (url.ends_with('/') && url != "/")
Ed Tanous38221502024-06-03 12:19:38 -0700222 {
223 url.remove_suffix(1);
224 }
John Edward Broadbent59b98b22021-07-13 15:36:32 -0700225 if (boost::beast::http::verb::get == method)
James Feist3909dc82020-04-03 10:58:55 -0700226 {
Ed Tanous38221502024-06-03 12:19:38 -0700227 if ((url == "/redfish") || //
228 (url == "/redfish/v1") || //
229 (url == "/redfish/v1/odata") || //
230 (url == "/redfish/v1/$metadata"))
James Feist3909dc82020-04-03 10:58:55 -0700231 {
232 return true;
233 }
John Edward Broadbent59b98b22021-07-13 15:36:32 -0700234 if (crow::webroutes::routes.find(std::string(url)) !=
Ed Tanousd4d25792020-09-29 15:15:03 -0700235 crow::webroutes::routes.end())
James Feist3909dc82020-04-03 10:58:55 -0700236 {
237 return true;
238 }
239 }
240
241 // it's allowed to POST on session collection & login without
242 // authentication
John Edward Broadbent59b98b22021-07-13 15:36:32 -0700243 if (boost::beast::http::verb::post == method)
James Feist3909dc82020-04-03 10:58:55 -0700244 {
John Edward Broadbent59b98b22021-07-13 15:36:32 -0700245 if ((url == "/redfish/v1/SessionService/Sessions") ||
Ed Tanouse76cd862022-03-14 09:12:00 -0700246 (url == "/redfish/v1/SessionService/Sessions/Members") ||
John Edward Broadbent59b98b22021-07-13 15:36:32 -0700247 (url == "/login"))
James Feist3909dc82020-04-03 10:58:55 -0700248 {
249 return true;
250 }
251 }
252
253 return false;
254}
255
Ed Tanous25b54db2024-04-17 15:40:31 -0700256inline 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)
James Feist3909dc82020-04-03 10:58:55 -0700263{
Ed Tanous52cc1122020-07-18 13:51:21 -0700264 const persistent_data::AuthConfigMethods& authMethodsConfig =
265 persistent_data::SessionStore::getInstance().getAuthMethodsConfig();
James Feist3909dc82020-04-03 10:58:55 -0700266
John Edward Broadbent59b98b22021-07-13 15:36:32 -0700267 std::shared_ptr<persistent_data::UserSession> sessionOut = nullptr;
Ed Tanous25b54db2024-04-17 15:40:31 -0700268 if constexpr (BMCWEB_MUTUAL_TLS_AUTH)
James Feist6964c982020-07-28 16:10:23 -0700269 {
Ed Tanous25b54db2024-04-17 15:40:31 -0700270 if (authMethodsConfig.tls)
271 {
272 sessionOut = performTLSAuth(res, reqHeader, session);
273 }
James Feist6964c982020-07-28 16:10:23 -0700274 }
Ed Tanous25b54db2024-04-17 15:40:31 -0700275 if constexpr (BMCWEB_XTOKEN_AUTH)
James Feist3909dc82020-04-03 10:58:55 -0700276 {
Ed Tanous25b54db2024-04-17 15:40:31 -0700277 if (sessionOut == nullptr && authMethodsConfig.xtoken)
278 {
279 sessionOut = performXtokenAuth(reqHeader);
280 }
James Feist3909dc82020-04-03 10:58:55 -0700281 }
Ed Tanous25b54db2024-04-17 15:40:31 -0700282 if constexpr (BMCWEB_COOKIE_AUTH)
James Feist3909dc82020-04-03 10:58:55 -0700283 {
Ed Tanous25b54db2024-04-17 15:40:31 -0700284 if (sessionOut == nullptr && authMethodsConfig.cookie)
285 {
286 sessionOut = performCookieAuth(method, reqHeader);
287 }
James Feist3909dc82020-04-03 10:58:55 -0700288 }
John Edward Broadbent59b98b22021-07-13 15:36:32 -0700289 std::string_view authHeader = reqHeader["Authorization"];
Ed Tanous62598e32023-07-17 17:06:25 -0700290 BMCWEB_LOG_DEBUG("authHeader={}", authHeader);
Ed Tanous25b54db2024-04-17 15:40:31 -0700291 if constexpr (BMCWEB_SESSION_AUTH)
James Feist3909dc82020-04-03 10:58:55 -0700292 {
Ed Tanous25b54db2024-04-17 15:40:31 -0700293 if (sessionOut == nullptr && authMethodsConfig.sessionToken)
294 {
295 sessionOut = performTokenAuth(authHeader);
296 }
James Feist3909dc82020-04-03 10:58:55 -0700297 }
Ed Tanous25b54db2024-04-17 15:40:31 -0700298 if constexpr (BMCWEB_BASIC_AUTH)
John Edward Broadbent97a056a2021-09-10 14:56:19 -0700299 {
Ed Tanous25b54db2024-04-17 15:40:31 -0700300 if (sessionOut == nullptr && authMethodsConfig.basic)
301 {
302 sessionOut = performBasicAuth(ipAddress, authHeader);
303 }
John Edward Broadbent97a056a2021-09-10 14:56:19 -0700304 }
305 if (sessionOut != nullptr)
306 {
307 return sessionOut;
308 }
309
John Edward Broadbent59b98b22021-07-13 15:36:32 -0700310 return nullptr;
James Feist3909dc82020-04-03 10:58:55 -0700311}
312
Nan Zhoud055a342022-05-25 01:15:34 +0000313} // namespace authentication
James Feist3909dc82020-04-03 10:58:55 -0700314} // namespace crow