blob: c078ede23a693ba66d0ffe09ee378942ed93a767 [file] [log] [blame]
James Feist3909dc82020-04-03 10:58:55 -07001#pragma once
2
3#include "webroutes.hpp"
4
Ed Tanous04e438c2020-10-03 08:06:26 -07005#include <app.hpp>
James Feist3909dc82020-04-03 10:58:55 -07006#include <boost/algorithm/string/predicate.hpp>
7#include <boost/container/flat_set.hpp>
Ed Tanous04e438c2020-10-03 08:06:26 -07008#include <common.hpp>
9#include <http_request.hpp>
10#include <http_response.hpp>
James Feist3909dc82020-04-03 10:58:55 -070011#include <http_utility.hpp>
12#include <pam_authenticate.hpp>
James Feist3909dc82020-04-03 10:58:55 -070013
14#include <random>
Ed Tanousb5a76932020-09-29 16:16:58 -070015#include <utility>
James Feist3909dc82020-04-03 10:58:55 -070016
17namespace crow
18{
19
20namespace authorization
21{
22
23static void cleanupTempSession(Request& req)
24{
25 // TODO(ed) THis should really be handled by the persistent data
26 // middleware, but because it is upstream, it doesn't have access to the
27 // session information. Should the data middleware persist the current
28 // user session?
29 if (req.session != nullptr &&
30 req.session->persistence ==
Ed Tanous52cc1122020-07-18 13:51:21 -070031 persistent_data::PersistenceType::SINGLE_REQUEST)
James Feist3909dc82020-04-03 10:58:55 -070032 {
33 persistent_data::SessionStore::getInstance().removeSession(req.session);
34 }
35}
36
Alan Kuof16f6262020-12-08 19:29:59 +080037#ifdef BMCWEB_ENABLE_BASIC_AUTHENTICATION
Ed Tanous3174e4d2020-10-07 11:41:22 -070038static std::shared_ptr<persistent_data::UserSession>
Sunitha Harishc0ea7ae2020-10-30 02:37:30 -050039 performBasicAuth(const boost::asio::ip::address& clientIp,
Ed Tanous81ce6092020-12-17 16:54:55 +000040 std::string_view authHeader)
James Feist3909dc82020-04-03 10:58:55 -070041{
42 BMCWEB_LOG_DEBUG << "[AuthMiddleware] Basic authentication";
43
44 std::string authData;
Ed Tanous81ce6092020-12-17 16:54:55 +000045 std::string_view param = authHeader.substr(strlen("Basic "));
James Feist3909dc82020-04-03 10:58:55 -070046 if (!crow::utility::base64Decode(param, authData))
47 {
48 return nullptr;
49 }
50 std::size_t separator = authData.find(':');
51 if (separator == std::string::npos)
52 {
53 return nullptr;
54 }
55
56 std::string user = authData.substr(0, separator);
57 separator += 1;
58 if (separator > authData.size())
59 {
60 return nullptr;
61 }
62 std::string pass = authData.substr(separator);
63
64 BMCWEB_LOG_DEBUG << "[AuthMiddleware] Authenticating user: " << user;
Sunitha Harishc0ea7ae2020-10-30 02:37:30 -050065 BMCWEB_LOG_DEBUG << "[AuthMiddleware] User IPAddress: "
66 << clientIp.to_string();
James Feist3909dc82020-04-03 10:58:55 -070067
68 int pamrc = pamAuthenticateUser(user, pass);
69 bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD;
70 if ((pamrc != PAM_SUCCESS) && !isConfigureSelfOnly)
71 {
72 return nullptr;
73 }
74
75 // TODO(ed) generateUserSession is a little expensive for basic
76 // auth, as it generates some random identifiers that will never be
77 // used. This should have a "fast" path for when user tokens aren't
78 // needed.
79 // This whole flow needs to be revisited anyway, as we can't be
80 // calling directly into pam for every request
Sunitha Harishd3239222021-02-24 15:33:29 +053081 std::string unsupportedClientId = "";
James Feist3909dc82020-04-03 10:58:55 -070082 return persistent_data::SessionStore::getInstance().generateUserSession(
Sunitha Harishd3239222021-02-24 15:33:29 +053083 user, clientIp.to_string(), unsupportedClientId,
84 persistent_data::PersistenceType::SINGLE_REQUEST, isConfigureSelfOnly);
James Feist3909dc82020-04-03 10:58:55 -070085}
Alan Kuof16f6262020-12-08 19:29:59 +080086#endif
James Feist3909dc82020-04-03 10:58:55 -070087
Alan Kuof16f6262020-12-08 19:29:59 +080088#ifdef BMCWEB_ENABLE_SESSION_AUTHENTICATION
Ed Tanous3174e4d2020-10-07 11:41:22 -070089static 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{
92 BMCWEB_LOG_DEBUG << "[AuthMiddleware] Token authentication";
93
Ed Tanous81ce6092020-12-17 16:54:55 +000094 std::string_view token = authHeader.substr(strlen("Token "));
James Feist3909dc82020-04-03 10:58:55 -070095 auto session =
96 persistent_data::SessionStore::getInstance().loginSessionByToken(token);
97 return session;
98}
Alan Kuof16f6262020-12-08 19:29:59 +080099#endif
James Feist3909dc82020-04-03 10:58:55 -0700100
Alan Kuof16f6262020-12-08 19:29:59 +0800101#ifdef BMCWEB_ENABLE_XTOKEN_AUTHENTICATION
Ed Tanous3174e4d2020-10-07 11:41:22 -0700102static std::shared_ptr<persistent_data::UserSession>
James Feist3909dc82020-04-03 10:58:55 -0700103 performXtokenAuth(const crow::Request& req)
104{
105 BMCWEB_LOG_DEBUG << "[AuthMiddleware] X-Auth-Token authentication";
106
107 std::string_view token = req.getHeaderValue("X-Auth-Token");
108 if (token.empty())
109 {
110 return nullptr;
111 }
112 auto session =
113 persistent_data::SessionStore::getInstance().loginSessionByToken(token);
114 return session;
115}
Alan Kuof16f6262020-12-08 19:29:59 +0800116#endif
James Feist3909dc82020-04-03 10:58:55 -0700117
Alan Kuof16f6262020-12-08 19:29:59 +0800118#ifdef BMCWEB_ENABLE_COOKIE_AUTHENTICATION
Ed Tanous3174e4d2020-10-07 11:41:22 -0700119static std::shared_ptr<persistent_data::UserSession>
James Feist3909dc82020-04-03 10:58:55 -0700120 performCookieAuth(const crow::Request& req)
121{
122 BMCWEB_LOG_DEBUG << "[AuthMiddleware] Cookie authentication";
123
124 std::string_view cookieValue = req.getHeaderValue("Cookie");
125 if (cookieValue.empty())
126 {
127 return nullptr;
128 }
129
130 auto startIndex = cookieValue.find("SESSION=");
131 if (startIndex == std::string::npos)
132 {
133 return nullptr;
134 }
135 startIndex += sizeof("SESSION=") - 1;
Ed Tanous81ce6092020-12-17 16:54:55 +0000136 auto endIndex = cookieValue.find(';', startIndex);
James Feist3909dc82020-04-03 10:58:55 -0700137 if (endIndex == std::string::npos)
138 {
139 endIndex = cookieValue.size();
140 }
141 std::string_view authKey =
142 cookieValue.substr(startIndex, endIndex - startIndex);
143
Ed Tanous3174e4d2020-10-07 11:41:22 -0700144 std::shared_ptr<persistent_data::UserSession> session =
James Feist3909dc82020-04-03 10:58:55 -0700145 persistent_data::SessionStore::getInstance().loginSessionByToken(
146 authKey);
147 if (session == nullptr)
148 {
149 return nullptr;
150 }
151#ifndef BMCWEB_INSECURE_DISABLE_CSRF_PREVENTION
152 // RFC7231 defines methods that need csrf protection
Ed Tanousb41187f2019-10-24 16:30:02 -0700153 if (req.method() != boost::beast::http::verb::get)
James Feist3909dc82020-04-03 10:58:55 -0700154 {
155 std::string_view csrf = req.getHeaderValue("X-XSRF-TOKEN");
156 // Make sure both tokens are filled
157 if (csrf.empty() || session->csrfToken.empty())
158 {
159 return nullptr;
160 }
161
Ed Tanous52cc1122020-07-18 13:51:21 -0700162 if (csrf.size() != persistent_data::sessionTokenSize)
James Feist3909dc82020-04-03 10:58:55 -0700163 {
164 return nullptr;
165 }
166 // Reject if csrf token not available
167 if (!crow::utility::constantTimeStringCompare(csrf, session->csrfToken))
168 {
169 return nullptr;
170 }
171 }
172#endif
173 return session;
174}
Alan Kuof16f6262020-12-08 19:29:59 +0800175#endif
James Feist3909dc82020-04-03 10:58:55 -0700176
Alexander Filippov96457602020-09-29 14:19:38 +0300177#ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
Ed Tanous3174e4d2020-10-07 11:41:22 -0700178static std::shared_ptr<persistent_data::UserSession>
James Feist6964c982020-07-28 16:10:23 -0700179 performTLSAuth(const crow::Request& req, Response& res,
Ed Tanousb5a76932020-09-29 16:16:58 -0700180 const std::weak_ptr<persistent_data::UserSession>& session)
James Feist6964c982020-07-28 16:10:23 -0700181{
James Feist6964c982020-07-28 16:10:23 -0700182 if (auto sp = session.lock())
183 {
184 // set cookie only if this is req from the browser.
185 if (req.getHeaderValue("User-Agent").empty())
186 {
187 BMCWEB_LOG_DEBUG << " TLS session: " << sp->uniqueId
188 << " will be used for this request.";
189 return sp;
190 }
Ed Tanous3174e4d2020-10-07 11:41:22 -0700191 std::string_view cookieValue = req.getHeaderValue("Cookie");
192 if (cookieValue.empty() ||
193 cookieValue.find("SESSION=") == std::string::npos)
James Feist6964c982020-07-28 16:10:23 -0700194 {
Ed Tanous3174e4d2020-10-07 11:41:22 -0700195 // TODO: change this to not switch to cookie auth
Gunnar Mills636be392021-03-15 12:47:07 -0500196 res.addHeader(
197 "Set-Cookie",
198 "XSRF-TOKEN=" + sp->csrfToken +
199 "; SameSite=Strict; Secure\r\nSet-Cookie: SESSION=" +
200 sp->sessionToken +
201 "; SameSite=Strict; Secure; HttpOnly\r\nSet-Cookie: "
202 "IsAuthenticated=true; Secure");
Ed Tanous3174e4d2020-10-07 11:41:22 -0700203 BMCWEB_LOG_DEBUG << " TLS session: " << sp->uniqueId
204 << " with cookie will be used for this request.";
205 return sp;
James Feist6964c982020-07-28 16:10:23 -0700206 }
207 }
James Feist6964c982020-07-28 16:10:23 -0700208 return nullptr;
209}
Alexander Filippov96457602020-09-29 14:19:38 +0300210#endif
James Feist6964c982020-07-28 16:10:23 -0700211
James Feist3909dc82020-04-03 10:58:55 -0700212// checks if request can be forwarded without authentication
213static bool isOnWhitelist(const crow::Request& req)
214{
215 // it's allowed to GET root node without authentication
Ed Tanousb41187f2019-10-24 16:30:02 -0700216 if (boost::beast::http::verb::get == req.method())
James Feist3909dc82020-04-03 10:58:55 -0700217 {
218 if (req.url == "/redfish/v1" || req.url == "/redfish/v1/" ||
219 req.url == "/redfish" || req.url == "/redfish/" ||
220 req.url == "/redfish/v1/odata" || req.url == "/redfish/v1/odata/")
221 {
222 return true;
223 }
Ed Tanousd4d25792020-09-29 15:15:03 -0700224 if (crow::webroutes::routes.find(std::string(req.url)) !=
225 crow::webroutes::routes.end())
James Feist3909dc82020-04-03 10:58:55 -0700226 {
227 return true;
228 }
229 }
230
231 // it's allowed to POST on session collection & login without
232 // authentication
Ed Tanousb41187f2019-10-24 16:30:02 -0700233 if (boost::beast::http::verb::post == req.method())
James Feist3909dc82020-04-03 10:58:55 -0700234 {
235 if ((req.url == "/redfish/v1/SessionService/Sessions") ||
236 (req.url == "/redfish/v1/SessionService/Sessions/") ||
237 (req.url == "/login"))
238 {
239 return true;
240 }
241 }
242
243 return false;
244}
245
Alexander Filippov96457602020-09-29 14:19:38 +0300246static void authenticate(
247 crow::Request& req, Response& res,
Ed Tanousf23b7292020-10-15 09:41:17 -0700248 [[maybe_unused]] const std::weak_ptr<persistent_data::UserSession>& session)
James Feist3909dc82020-04-03 10:58:55 -0700249{
250 if (isOnWhitelist(req))
251 {
252 return;
253 }
254
Ed Tanous52cc1122020-07-18 13:51:21 -0700255 const persistent_data::AuthConfigMethods& authMethodsConfig =
256 persistent_data::SessionStore::getInstance().getAuthMethodsConfig();
James Feist3909dc82020-04-03 10:58:55 -0700257
Alexander Filippov96457602020-09-29 14:19:38 +0300258#ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
James Feist6964c982020-07-28 16:10:23 -0700259 if (req.session == nullptr && authMethodsConfig.tls)
260 {
Ed Tanousf23b7292020-10-15 09:41:17 -0700261 req.session = performTLSAuth(req, res, session);
James Feist6964c982020-07-28 16:10:23 -0700262 }
Alexander Filippov96457602020-09-29 14:19:38 +0300263#endif
Alan Kuof16f6262020-12-08 19:29:59 +0800264#ifdef BMCWEB_ENABLE_XTOKEN_AUTHENTICATION
James Feist3909dc82020-04-03 10:58:55 -0700265 if (req.session == nullptr && authMethodsConfig.xtoken)
266 {
267 req.session = performXtokenAuth(req);
268 }
Alan Kuof16f6262020-12-08 19:29:59 +0800269#endif
270#ifdef BMCWEB_ENABLE_COOKIE_AUTHENTICATION
James Feist3909dc82020-04-03 10:58:55 -0700271 if (req.session == nullptr && authMethodsConfig.cookie)
272 {
273 req.session = performCookieAuth(req);
274 }
Alan Kuof16f6262020-12-08 19:29:59 +0800275#endif
James Feist3909dc82020-04-03 10:58:55 -0700276 if (req.session == nullptr)
277 {
278 std::string_view authHeader = req.getHeaderValue("Authorization");
279 if (!authHeader.empty())
280 {
281 // Reject any kind of auth other than basic or token
282 if (boost::starts_with(authHeader, "Token ") &&
283 authMethodsConfig.sessionToken)
284 {
Alan Kuof16f6262020-12-08 19:29:59 +0800285#ifdef BMCWEB_ENABLE_SESSION_AUTHENTICATION
James Feist3909dc82020-04-03 10:58:55 -0700286 req.session = performTokenAuth(authHeader);
Alan Kuof16f6262020-12-08 19:29:59 +0800287#endif
James Feist3909dc82020-04-03 10:58:55 -0700288 }
289 else if (boost::starts_with(authHeader, "Basic ") &&
290 authMethodsConfig.basic)
291 {
Alan Kuof16f6262020-12-08 19:29:59 +0800292#ifdef BMCWEB_ENABLE_BASIC_AUTHENTICATION
Sunitha Harishc0ea7ae2020-10-30 02:37:30 -0500293 req.session = performBasicAuth(req.ipAddress, authHeader);
Alan Kuof16f6262020-12-08 19:29:59 +0800294#endif
James Feist3909dc82020-04-03 10:58:55 -0700295 }
296 }
297 }
298
299 if (req.session == nullptr)
300 {
301 BMCWEB_LOG_WARNING << "[AuthMiddleware] authorization failed";
302
303 // If it's a browser connecting, don't send the HTTP authenticate
304 // header, to avoid possible CSRF attacks with basic auth
305 if (http_helpers::requestPrefersHtml(req))
306 {
307 res.result(boost::beast::http::status::temporary_redirect);
308 res.addHeader("Location",
309 "/#/login?next=" + http_helpers::urlEncode(req.url));
310 }
311 else
312 {
313 res.result(boost::beast::http::status::unauthorized);
314 // only send the WWW-authenticate header if this isn't a xhr
315 // from the browser. most scripts,
316 if (req.getHeaderValue("User-Agent").empty())
317 {
318 res.addHeader("WWW-Authenticate", "Basic");
319 }
320 }
321
322 res.end();
323 return;
324 }
325}
326
327} // namespace authorization
328} // namespace crow