blob: e965508e0860b45a25fd576bcad79c02f57d92f0 [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
Ed Tanous3174e4d2020-10-07 11:41:22 -070037static std::shared_ptr<persistent_data::UserSession>
James Feist3909dc82020-04-03 10:58:55 -070038 performBasicAuth(std::string_view auth_header)
39{
40 BMCWEB_LOG_DEBUG << "[AuthMiddleware] Basic authentication";
41
42 std::string authData;
43 std::string_view param = auth_header.substr(strlen("Basic "));
44 if (!crow::utility::base64Decode(param, authData))
45 {
46 return nullptr;
47 }
48 std::size_t separator = authData.find(':');
49 if (separator == std::string::npos)
50 {
51 return nullptr;
52 }
53
54 std::string user = authData.substr(0, separator);
55 separator += 1;
56 if (separator > authData.size())
57 {
58 return nullptr;
59 }
60 std::string pass = authData.substr(separator);
61
62 BMCWEB_LOG_DEBUG << "[AuthMiddleware] Authenticating user: " << user;
63
64 int pamrc = pamAuthenticateUser(user, pass);
65 bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD;
66 if ((pamrc != PAM_SUCCESS) && !isConfigureSelfOnly)
67 {
68 return nullptr;
69 }
70
71 // TODO(ed) generateUserSession is a little expensive for basic
72 // auth, as it generates some random identifiers that will never be
73 // used. This should have a "fast" path for when user tokens aren't
74 // needed.
75 // This whole flow needs to be revisited anyway, as we can't be
76 // calling directly into pam for every request
77 return persistent_data::SessionStore::getInstance().generateUserSession(
Ed Tanous52cc1122020-07-18 13:51:21 -070078 user, persistent_data::PersistenceType::SINGLE_REQUEST,
James Feist3909dc82020-04-03 10:58:55 -070079 isConfigureSelfOnly);
80}
81
Ed Tanous3174e4d2020-10-07 11:41:22 -070082static std::shared_ptr<persistent_data::UserSession>
James Feist3909dc82020-04-03 10:58:55 -070083 performTokenAuth(std::string_view auth_header)
84{
85 BMCWEB_LOG_DEBUG << "[AuthMiddleware] Token authentication";
86
87 std::string_view token = auth_header.substr(strlen("Token "));
88 auto session =
89 persistent_data::SessionStore::getInstance().loginSessionByToken(token);
90 return session;
91}
92
Ed Tanous3174e4d2020-10-07 11:41:22 -070093static std::shared_ptr<persistent_data::UserSession>
James Feist3909dc82020-04-03 10:58:55 -070094 performXtokenAuth(const crow::Request& req)
95{
96 BMCWEB_LOG_DEBUG << "[AuthMiddleware] X-Auth-Token authentication";
97
98 std::string_view token = req.getHeaderValue("X-Auth-Token");
99 if (token.empty())
100 {
101 return nullptr;
102 }
103 auto session =
104 persistent_data::SessionStore::getInstance().loginSessionByToken(token);
105 return session;
106}
107
Ed Tanous3174e4d2020-10-07 11:41:22 -0700108static std::shared_ptr<persistent_data::UserSession>
James Feist3909dc82020-04-03 10:58:55 -0700109 performCookieAuth(const crow::Request& req)
110{
111 BMCWEB_LOG_DEBUG << "[AuthMiddleware] Cookie authentication";
112
113 std::string_view cookieValue = req.getHeaderValue("Cookie");
114 if (cookieValue.empty())
115 {
116 return nullptr;
117 }
118
119 auto startIndex = cookieValue.find("SESSION=");
120 if (startIndex == std::string::npos)
121 {
122 return nullptr;
123 }
124 startIndex += sizeof("SESSION=") - 1;
125 auto endIndex = cookieValue.find(";", startIndex);
126 if (endIndex == std::string::npos)
127 {
128 endIndex = cookieValue.size();
129 }
130 std::string_view authKey =
131 cookieValue.substr(startIndex, endIndex - startIndex);
132
Ed Tanous3174e4d2020-10-07 11:41:22 -0700133 std::shared_ptr<persistent_data::UserSession> session =
James Feist3909dc82020-04-03 10:58:55 -0700134 persistent_data::SessionStore::getInstance().loginSessionByToken(
135 authKey);
136 if (session == nullptr)
137 {
138 return nullptr;
139 }
140#ifndef BMCWEB_INSECURE_DISABLE_CSRF_PREVENTION
141 // RFC7231 defines methods that need csrf protection
Ed Tanousb41187f2019-10-24 16:30:02 -0700142 if (req.method() != boost::beast::http::verb::get)
James Feist3909dc82020-04-03 10:58:55 -0700143 {
144 std::string_view csrf = req.getHeaderValue("X-XSRF-TOKEN");
145 // Make sure both tokens are filled
146 if (csrf.empty() || session->csrfToken.empty())
147 {
148 return nullptr;
149 }
150
Ed Tanous52cc1122020-07-18 13:51:21 -0700151 if (csrf.size() != persistent_data::sessionTokenSize)
James Feist3909dc82020-04-03 10:58:55 -0700152 {
153 return nullptr;
154 }
155 // Reject if csrf token not available
156 if (!crow::utility::constantTimeStringCompare(csrf, session->csrfToken))
157 {
158 return nullptr;
159 }
160 }
161#endif
162 return session;
163}
164
Alexander Filippov96457602020-09-29 14:19:38 +0300165#ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
Ed Tanous3174e4d2020-10-07 11:41:22 -0700166static std::shared_ptr<persistent_data::UserSession>
James Feist6964c982020-07-28 16:10:23 -0700167 performTLSAuth(const crow::Request& req, Response& res,
Ed Tanousb5a76932020-09-29 16:16:58 -0700168 const std::weak_ptr<persistent_data::UserSession>& session)
James Feist6964c982020-07-28 16:10:23 -0700169{
James Feist6964c982020-07-28 16:10:23 -0700170 if (auto sp = session.lock())
171 {
172 // set cookie only if this is req from the browser.
173 if (req.getHeaderValue("User-Agent").empty())
174 {
175 BMCWEB_LOG_DEBUG << " TLS session: " << sp->uniqueId
176 << " will be used for this request.";
177 return sp;
178 }
Ed Tanous3174e4d2020-10-07 11:41:22 -0700179 std::string_view cookieValue = req.getHeaderValue("Cookie");
180 if (cookieValue.empty() ||
181 cookieValue.find("SESSION=") == std::string::npos)
James Feist6964c982020-07-28 16:10:23 -0700182 {
Ed Tanous3174e4d2020-10-07 11:41:22 -0700183 // TODO: change this to not switch to cookie auth
184 res.addHeader("Set-Cookie", "XSRF-TOKEN=" + sp->csrfToken +
185 "; Secure\r\nSet-Cookie: SESSION=" +
186 sp->sessionToken +
187 "; Secure; HttpOnly\r\nSet-Cookie: "
188 "IsAuthenticated=true; Secure");
189 BMCWEB_LOG_DEBUG << " TLS session: " << sp->uniqueId
190 << " with cookie will be used for this request.";
191 return sp;
James Feist6964c982020-07-28 16:10:23 -0700192 }
193 }
James Feist6964c982020-07-28 16:10:23 -0700194 return nullptr;
195}
Alexander Filippov96457602020-09-29 14:19:38 +0300196#endif
James Feist6964c982020-07-28 16:10:23 -0700197
James Feist3909dc82020-04-03 10:58:55 -0700198// checks if request can be forwarded without authentication
199static bool isOnWhitelist(const crow::Request& req)
200{
201 // it's allowed to GET root node without authentication
Ed Tanousb41187f2019-10-24 16:30:02 -0700202 if (boost::beast::http::verb::get == req.method())
James Feist3909dc82020-04-03 10:58:55 -0700203 {
204 if (req.url == "/redfish/v1" || req.url == "/redfish/v1/" ||
205 req.url == "/redfish" || req.url == "/redfish/" ||
206 req.url == "/redfish/v1/odata" || req.url == "/redfish/v1/odata/")
207 {
208 return true;
209 }
Ed Tanousd4d25792020-09-29 15:15:03 -0700210 if (crow::webroutes::routes.find(std::string(req.url)) !=
211 crow::webroutes::routes.end())
James Feist3909dc82020-04-03 10:58:55 -0700212 {
213 return true;
214 }
215 }
216
217 // it's allowed to POST on session collection & login without
218 // authentication
Ed Tanousb41187f2019-10-24 16:30:02 -0700219 if (boost::beast::http::verb::post == req.method())
James Feist3909dc82020-04-03 10:58:55 -0700220 {
221 if ((req.url == "/redfish/v1/SessionService/Sessions") ||
222 (req.url == "/redfish/v1/SessionService/Sessions/") ||
223 (req.url == "/login"))
224 {
225 return true;
226 }
227 }
228
229 return false;
230}
231
Alexander Filippov96457602020-09-29 14:19:38 +0300232static void authenticate(
233 crow::Request& req, Response& res,
Ed Tanousf23b7292020-10-15 09:41:17 -0700234 [[maybe_unused]] const std::weak_ptr<persistent_data::UserSession>& session)
James Feist3909dc82020-04-03 10:58:55 -0700235{
236 if (isOnWhitelist(req))
237 {
238 return;
239 }
240
Ed Tanous52cc1122020-07-18 13:51:21 -0700241 const persistent_data::AuthConfigMethods& authMethodsConfig =
242 persistent_data::SessionStore::getInstance().getAuthMethodsConfig();
James Feist3909dc82020-04-03 10:58:55 -0700243
Alexander Filippov96457602020-09-29 14:19:38 +0300244#ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
James Feist6964c982020-07-28 16:10:23 -0700245 if (req.session == nullptr && authMethodsConfig.tls)
246 {
Ed Tanousf23b7292020-10-15 09:41:17 -0700247 req.session = performTLSAuth(req, res, session);
James Feist6964c982020-07-28 16:10:23 -0700248 }
Alexander Filippov96457602020-09-29 14:19:38 +0300249#endif
James Feist3909dc82020-04-03 10:58:55 -0700250 if (req.session == nullptr && authMethodsConfig.xtoken)
251 {
252 req.session = performXtokenAuth(req);
253 }
254 if (req.session == nullptr && authMethodsConfig.cookie)
255 {
256 req.session = performCookieAuth(req);
257 }
258 if (req.session == nullptr)
259 {
260 std::string_view authHeader = req.getHeaderValue("Authorization");
261 if (!authHeader.empty())
262 {
263 // Reject any kind of auth other than basic or token
264 if (boost::starts_with(authHeader, "Token ") &&
265 authMethodsConfig.sessionToken)
266 {
267 req.session = performTokenAuth(authHeader);
268 }
269 else if (boost::starts_with(authHeader, "Basic ") &&
270 authMethodsConfig.basic)
271 {
272 req.session = performBasicAuth(authHeader);
273 }
274 }
275 }
276
277 if (req.session == nullptr)
278 {
279 BMCWEB_LOG_WARNING << "[AuthMiddleware] authorization failed";
280
281 // If it's a browser connecting, don't send the HTTP authenticate
282 // header, to avoid possible CSRF attacks with basic auth
283 if (http_helpers::requestPrefersHtml(req))
284 {
285 res.result(boost::beast::http::status::temporary_redirect);
286 res.addHeader("Location",
287 "/#/login?next=" + http_helpers::urlEncode(req.url));
288 }
289 else
290 {
291 res.result(boost::beast::http::status::unauthorized);
292 // only send the WWW-authenticate header if this isn't a xhr
293 // from the browser. most scripts,
294 if (req.getHeaderValue("User-Agent").empty())
295 {
296 res.addHeader("WWW-Authenticate", "Basic");
297 }
298 }
299
300 res.end();
301 return;
302 }
303}
304
305} // namespace authorization
306} // namespace crow