1d055a34aSNan Zhou #pragma once
2d055a34aSNan Zhou
329aab242SPaul Fertser #include "cookies.hpp"
43ccb3adbSEd Tanous #include "forward_unauthorized.hpp"
53ccb3adbSEd Tanous #include "http_request.hpp"
63ccb3adbSEd Tanous #include "http_response.hpp"
73ccb3adbSEd Tanous #include "http_utility.hpp"
83ccb3adbSEd Tanous #include "pam_authenticate.hpp"
9d055a34aSNan Zhou #include "webroutes.hpp"
10d055a34aSNan Zhou
11d055a34aSNan Zhou #include <boost/container/flat_set.hpp>
12d055a34aSNan Zhou
13d055a34aSNan Zhou #include <random>
14d055a34aSNan Zhou #include <utility>
15d055a34aSNan Zhou
16d055a34aSNan Zhou namespace crow
17d055a34aSNan Zhou {
18d055a34aSNan Zhou
19d055a34aSNan Zhou namespace authentication
20d055a34aSNan Zhou {
21d055a34aSNan Zhou
performBasicAuth(const boost::asio::ip::address & clientIp,std::string_view authHeader)22bd79bce8SPatrick Williams inline std::shared_ptr<persistent_data::UserSession> performBasicAuth(
23bd79bce8SPatrick Williams const boost::asio::ip::address& clientIp, std::string_view authHeader)
24d055a34aSNan Zhou {
2562598e31SEd Tanous BMCWEB_LOG_DEBUG("[AuthMiddleware] Basic authentication");
26d055a34aSNan Zhou
2711ba3979SEd Tanous if (!authHeader.starts_with("Basic "))
28d055a34aSNan Zhou {
29d055a34aSNan Zhou return nullptr;
30d055a34aSNan Zhou }
31d055a34aSNan Zhou
32d055a34aSNan Zhou std::string_view param = authHeader.substr(strlen("Basic "));
33d055a34aSNan Zhou std::string authData;
34d055a34aSNan Zhou
35d055a34aSNan Zhou if (!crow::utility::base64Decode(param, authData))
36d055a34aSNan Zhou {
37d055a34aSNan Zhou return nullptr;
38d055a34aSNan Zhou }
39d055a34aSNan Zhou std::size_t separator = authData.find(':');
40d055a34aSNan Zhou if (separator == std::string::npos)
41d055a34aSNan Zhou {
42d055a34aSNan Zhou return nullptr;
43d055a34aSNan Zhou }
44d055a34aSNan Zhou
45d055a34aSNan Zhou std::string user = authData.substr(0, separator);
46d055a34aSNan Zhou separator += 1;
47d055a34aSNan Zhou if (separator > authData.size())
48d055a34aSNan Zhou {
49d055a34aSNan Zhou return nullptr;
50d055a34aSNan Zhou }
51d055a34aSNan Zhou std::string pass = authData.substr(separator);
52d055a34aSNan Zhou
5362598e31SEd Tanous BMCWEB_LOG_DEBUG("[AuthMiddleware] Authenticating user: {}", user);
5462598e31SEd Tanous BMCWEB_LOG_DEBUG("[AuthMiddleware] User IPAddress: {}",
5562598e31SEd Tanous clientIp.to_string());
56d055a34aSNan Zhou
57*2ccce1f3SRavi Teja int pamrc = pamAuthenticateUser(user, pass, std::nullopt);
58d055a34aSNan Zhou bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD;
59d055a34aSNan Zhou if ((pamrc != PAM_SUCCESS) && !isConfigureSelfOnly)
60d055a34aSNan Zhou {
61d055a34aSNan Zhou return nullptr;
62d055a34aSNan Zhou }
63d055a34aSNan Zhou
6489cda63dSEd Tanous // Attempt to locate an existing Basic Auth session from the same ip address
6589cda63dSEd Tanous // and user
6689cda63dSEd Tanous for (auto& session :
6789cda63dSEd Tanous persistent_data::SessionStore::getInstance().getSessions())
6889cda63dSEd Tanous {
6989cda63dSEd Tanous if (session->sessionType != persistent_data::SessionType::Basic)
7089cda63dSEd Tanous {
7189cda63dSEd Tanous continue;
7289cda63dSEd Tanous }
7389cda63dSEd Tanous if (session->clientIp != redfish::ip_util::toString(clientIp))
7489cda63dSEd Tanous {
7589cda63dSEd Tanous continue;
7689cda63dSEd Tanous }
7789cda63dSEd Tanous if (session->username != user)
7889cda63dSEd Tanous {
7989cda63dSEd Tanous continue;
8089cda63dSEd Tanous }
8189cda63dSEd Tanous return session;
8289cda63dSEd Tanous }
8389cda63dSEd Tanous
84d055a34aSNan Zhou return persistent_data::SessionStore::getInstance().generateUserSession(
8589cda63dSEd Tanous user, clientIp, std::nullopt, persistent_data::SessionType::Basic,
8689cda63dSEd Tanous isConfigureSelfOnly);
87d055a34aSNan Zhou }
88d055a34aSNan Zhou
8925b54dbaSEd Tanous inline std::shared_ptr<persistent_data::UserSession>
performTokenAuth(std::string_view authHeader)90d055a34aSNan Zhou performTokenAuth(std::string_view authHeader)
91d055a34aSNan Zhou {
9262598e31SEd Tanous BMCWEB_LOG_DEBUG("[AuthMiddleware] Token authentication");
9311ba3979SEd Tanous if (!authHeader.starts_with("Token "))
94d055a34aSNan Zhou {
95d055a34aSNan Zhou return nullptr;
96d055a34aSNan Zhou }
97d055a34aSNan Zhou std::string_view token = authHeader.substr(strlen("Token "));
98d055a34aSNan Zhou auto sessionOut =
99d055a34aSNan Zhou persistent_data::SessionStore::getInstance().loginSessionByToken(token);
100d055a34aSNan Zhou return sessionOut;
101d055a34aSNan Zhou }
102d055a34aSNan Zhou
10325b54dbaSEd Tanous inline std::shared_ptr<persistent_data::UserSession>
performXtokenAuth(const boost::beast::http::header<true> & reqHeader)104d055a34aSNan Zhou performXtokenAuth(const boost::beast::http::header<true>& reqHeader)
105d055a34aSNan Zhou {
10662598e31SEd Tanous BMCWEB_LOG_DEBUG("[AuthMiddleware] X-Auth-Token authentication");
107d055a34aSNan Zhou
108d055a34aSNan Zhou std::string_view token = reqHeader["X-Auth-Token"];
109d055a34aSNan Zhou if (token.empty())
110d055a34aSNan Zhou {
111d055a34aSNan Zhou return nullptr;
112d055a34aSNan Zhou }
113d055a34aSNan Zhou auto sessionOut =
114d055a34aSNan Zhou persistent_data::SessionStore::getInstance().loginSessionByToken(token);
115d055a34aSNan Zhou return sessionOut;
116d055a34aSNan Zhou }
117d055a34aSNan Zhou
11825b54dbaSEd Tanous inline std::shared_ptr<persistent_data::UserSession>
performCookieAuth(boost::beast::http::verb method,const boost::beast::http::header<true> & reqHeader)1191d869608SEd Tanous performCookieAuth(boost::beast::http::verb method [[maybe_unused]],
120d055a34aSNan Zhou const boost::beast::http::header<true>& reqHeader)
121d055a34aSNan Zhou {
1229f217c26SEd Tanous using headers = boost::beast::http::header<true>;
1239f217c26SEd Tanous std::pair<headers::const_iterator, headers::const_iterator> cookies =
1249f217c26SEd Tanous reqHeader.equal_range(boost::beast::http::field::cookie);
125d055a34aSNan Zhou
1269f217c26SEd Tanous for (auto it = cookies.first; it != cookies.second; it++)
127d055a34aSNan Zhou {
1289f217c26SEd Tanous std::string_view cookieValue = it->value();
1299f217c26SEd Tanous BMCWEB_LOG_DEBUG("Checking cookie {}", cookieValue);
130d055a34aSNan Zhou auto startIndex = cookieValue.find("SESSION=");
131d055a34aSNan Zhou if (startIndex == std::string::npos)
132d055a34aSNan Zhou {
1339f217c26SEd Tanous BMCWEB_LOG_DEBUG(
1349f217c26SEd Tanous "Cookie was present, but didn't look like a session {}",
1359f217c26SEd Tanous cookieValue);
1369f217c26SEd Tanous continue;
137d055a34aSNan Zhou }
138d055a34aSNan Zhou startIndex += sizeof("SESSION=") - 1;
139d055a34aSNan Zhou auto endIndex = cookieValue.find(';', startIndex);
140d055a34aSNan Zhou if (endIndex == std::string::npos)
141d055a34aSNan Zhou {
142d055a34aSNan Zhou endIndex = cookieValue.size();
143d055a34aSNan Zhou }
144bd79bce8SPatrick Williams std::string_view authKey =
145bd79bce8SPatrick Williams cookieValue.substr(startIndex, endIndex - startIndex);
146d055a34aSNan Zhou
147d055a34aSNan Zhou std::shared_ptr<persistent_data::UserSession> sessionOut =
148d055a34aSNan Zhou persistent_data::SessionStore::getInstance().loginSessionByToken(
149d055a34aSNan Zhou authKey);
150d055a34aSNan Zhou if (sessionOut == nullptr)
151d055a34aSNan Zhou {
152d055a34aSNan Zhou return nullptr;
153d055a34aSNan Zhou }
154c3b3ad03SEd Tanous sessionOut->cookieAuth = true;
15525b54dbaSEd Tanous
156576db695SEd Tanous if constexpr (!BMCWEB_INSECURE_DISABLE_CSRF)
15725b54dbaSEd Tanous {
158d055a34aSNan Zhou // RFC7231 defines methods that need csrf protection
159d055a34aSNan Zhou if (method != boost::beast::http::verb::get)
160d055a34aSNan Zhou {
161d055a34aSNan Zhou std::string_view csrf = reqHeader["X-XSRF-TOKEN"];
162d055a34aSNan Zhou // Make sure both tokens are filled
163d055a34aSNan Zhou if (csrf.empty() || sessionOut->csrfToken.empty())
164d055a34aSNan Zhou {
165d055a34aSNan Zhou return nullptr;
166d055a34aSNan Zhou }
167d055a34aSNan Zhou
168d055a34aSNan Zhou if (csrf.size() != persistent_data::sessionTokenSize)
169d055a34aSNan Zhou {
170d055a34aSNan Zhou return nullptr;
171d055a34aSNan Zhou }
172d055a34aSNan Zhou // Reject if csrf token not available
173724985ffSEd Tanous if (!bmcweb::constantTimeStringCompare(csrf,
174724985ffSEd Tanous sessionOut->csrfToken))
175d055a34aSNan Zhou {
176d055a34aSNan Zhou return nullptr;
177d055a34aSNan Zhou }
178d055a34aSNan Zhou }
17925b54dbaSEd Tanous }
1803eaecac3SEd Tanous return sessionOut;
181d055a34aSNan Zhou }
1829f217c26SEd Tanous return nullptr;
1839f217c26SEd Tanous }
184d055a34aSNan Zhou
performTLSAuth(Response & res,const std::shared_ptr<persistent_data::UserSession> & session)185bd79bce8SPatrick Williams inline std::shared_ptr<persistent_data::UserSession> performTLSAuth(
186bd79bce8SPatrick Williams Response& res, const std::shared_ptr<persistent_data::UserSession>& session)
187d055a34aSNan Zhou {
1883281bcf1SEd Tanous if (session != nullptr)
189d055a34aSNan Zhou {
190994fd86aSEd Tanous res.addHeader(boost::beast::http::field::set_cookie,
191d055a34aSNan Zhou "IsAuthenticated=true; Secure");
19262598e31SEd Tanous BMCWEB_LOG_DEBUG(
19362598e31SEd Tanous " TLS session: {} with cookie will be used for this request.",
1943281bcf1SEd Tanous session->uniqueId);
195d055a34aSNan Zhou }
1963281bcf1SEd Tanous
1973281bcf1SEd Tanous return session;
198d055a34aSNan Zhou }
199d055a34aSNan Zhou
200d055a34aSNan Zhou // checks if request can be forwarded without authentication
isOnAllowlist(std::string_view url,boost::beast::http::verb method)20125b54dbaSEd Tanous inline bool isOnAllowlist(std::string_view url, boost::beast::http::verb method)
202d055a34aSNan Zhou {
20338221509SEd Tanous // Handle the case where the router registers routes as both ending with /
20438221509SEd Tanous // and not.
2058f5df132SVinceChang6637 if (url.ends_with('/') && url != "/")
20638221509SEd Tanous {
20738221509SEd Tanous url.remove_suffix(1);
20838221509SEd Tanous }
209d055a34aSNan Zhou if (boost::beast::http::verb::get == method)
210d055a34aSNan Zhou {
21138221509SEd Tanous if ((url == "/redfish") || //
21238221509SEd Tanous (url == "/redfish/v1") || //
21338221509SEd Tanous (url == "/redfish/v1/odata") || //
21438221509SEd Tanous (url == "/redfish/v1/$metadata"))
215d055a34aSNan Zhou {
216d055a34aSNan Zhou return true;
217d055a34aSNan Zhou }
218d055a34aSNan Zhou if (crow::webroutes::routes.find(std::string(url)) !=
219d055a34aSNan Zhou crow::webroutes::routes.end())
220d055a34aSNan Zhou {
221d055a34aSNan Zhou return true;
222d055a34aSNan Zhou }
223d055a34aSNan Zhou }
224d055a34aSNan Zhou
225d055a34aSNan Zhou // it's allowed to POST on session collection & login without
226d055a34aSNan Zhou // authentication
227d055a34aSNan Zhou if (boost::beast::http::verb::post == method)
228d055a34aSNan Zhou {
229d055a34aSNan Zhou if ((url == "/redfish/v1/SessionService/Sessions") ||
230d055a34aSNan Zhou (url == "/redfish/v1/SessionService/Sessions/Members") ||
231d055a34aSNan Zhou (url == "/login"))
232d055a34aSNan Zhou {
233d055a34aSNan Zhou return true;
234d055a34aSNan Zhou }
235d055a34aSNan Zhou }
236d055a34aSNan Zhou
237d055a34aSNan Zhou return false;
238d055a34aSNan Zhou }
239d055a34aSNan Zhou
authenticate(const boost::asio::ip::address & ipAddress,Response & res,boost::beast::http::verb method,const boost::beast::http::header<true> & reqHeader,const std::shared_ptr<persistent_data::UserSession> & session)24025b54dbaSEd Tanous inline std::shared_ptr<persistent_data::UserSession> authenticate(
24102cad96eSEd Tanous const boost::asio::ip::address& ipAddress [[maybe_unused]],
2423acced2cSNan Zhou Response& res [[maybe_unused]],
2433acced2cSNan Zhou boost::beast::http::verb method [[maybe_unused]],
244d055a34aSNan Zhou const boost::beast::http::header<true>& reqHeader,
245d055a34aSNan Zhou [[maybe_unused]] const std::shared_ptr<persistent_data::UserSession>&
246d055a34aSNan Zhou session)
247d055a34aSNan Zhou {
248d055a34aSNan Zhou const persistent_data::AuthConfigMethods& authMethodsConfig =
249d055a34aSNan Zhou persistent_data::SessionStore::getInstance().getAuthMethodsConfig();
250d055a34aSNan Zhou
251d055a34aSNan Zhou std::shared_ptr<persistent_data::UserSession> sessionOut = nullptr;
25225b54dbaSEd Tanous if constexpr (BMCWEB_MUTUAL_TLS_AUTH)
25325b54dbaSEd Tanous {
254d055a34aSNan Zhou if (authMethodsConfig.tls)
255d055a34aSNan Zhou {
2563281bcf1SEd Tanous sessionOut = performTLSAuth(res, session);
257d055a34aSNan Zhou }
25825b54dbaSEd Tanous }
25925b54dbaSEd Tanous if constexpr (BMCWEB_XTOKEN_AUTH)
26025b54dbaSEd Tanous {
261d055a34aSNan Zhou if (sessionOut == nullptr && authMethodsConfig.xtoken)
262d055a34aSNan Zhou {
263d055a34aSNan Zhou sessionOut = performXtokenAuth(reqHeader);
264d055a34aSNan Zhou }
26525b54dbaSEd Tanous }
26625b54dbaSEd Tanous if constexpr (BMCWEB_COOKIE_AUTH)
26725b54dbaSEd Tanous {
268d055a34aSNan Zhou if (sessionOut == nullptr && authMethodsConfig.cookie)
269d055a34aSNan Zhou {
270d055a34aSNan Zhou sessionOut = performCookieAuth(method, reqHeader);
271d055a34aSNan Zhou }
27225b54dbaSEd Tanous }
273d055a34aSNan Zhou std::string_view authHeader = reqHeader["Authorization"];
27462598e31SEd Tanous BMCWEB_LOG_DEBUG("authHeader={}", authHeader);
27525b54dbaSEd Tanous if constexpr (BMCWEB_SESSION_AUTH)
27625b54dbaSEd Tanous {
277d055a34aSNan Zhou if (sessionOut == nullptr && authMethodsConfig.sessionToken)
278d055a34aSNan Zhou {
279d055a34aSNan Zhou sessionOut = performTokenAuth(authHeader);
280d055a34aSNan Zhou }
28125b54dbaSEd Tanous }
28225b54dbaSEd Tanous if constexpr (BMCWEB_BASIC_AUTH)
28325b54dbaSEd Tanous {
284d055a34aSNan Zhou if (sessionOut == nullptr && authMethodsConfig.basic)
285d055a34aSNan Zhou {
286d055a34aSNan Zhou sessionOut = performBasicAuth(ipAddress, authHeader);
28725b54dbaSEd Tanous }
288d055a34aSNan Zhou }
289d055a34aSNan Zhou if (sessionOut != nullptr)
290d055a34aSNan Zhou {
291d055a34aSNan Zhou return sessionOut;
292d055a34aSNan Zhou }
293d055a34aSNan Zhou
294d055a34aSNan Zhou return nullptr;
295d055a34aSNan Zhou }
296d055a34aSNan Zhou
297d055a34aSNan Zhou } // namespace authentication
298d055a34aSNan Zhou } // namespace crow
299