blob: 0f73e967cb78c855e019b79f289ea58e6a780e11 [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>
Sunitha Harishc0ea7ae2020-10-30 02:37:30 -050038 performBasicAuth(const boost::asio::ip::address& clientIp,
39 std::string_view auth_header)
James Feist3909dc82020-04-03 10:58:55 -070040{
41 BMCWEB_LOG_DEBUG << "[AuthMiddleware] Basic authentication";
42
43 std::string authData;
44 std::string_view param = auth_header.substr(strlen("Basic "));
45 if (!crow::utility::base64Decode(param, authData))
46 {
47 return nullptr;
48 }
49 std::size_t separator = authData.find(':');
50 if (separator == std::string::npos)
51 {
52 return nullptr;
53 }
54
55 std::string user = authData.substr(0, separator);
56 separator += 1;
57 if (separator > authData.size())
58 {
59 return nullptr;
60 }
61 std::string pass = authData.substr(separator);
62
63 BMCWEB_LOG_DEBUG << "[AuthMiddleware] Authenticating user: " << user;
Sunitha Harishc0ea7ae2020-10-30 02:37:30 -050064 BMCWEB_LOG_DEBUG << "[AuthMiddleware] User IPAddress: "
65 << clientIp.to_string();
James Feist3909dc82020-04-03 10:58:55 -070066
67 int pamrc = pamAuthenticateUser(user, pass);
68 bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD;
69 if ((pamrc != PAM_SUCCESS) && !isConfigureSelfOnly)
70 {
71 return nullptr;
72 }
73
74 // TODO(ed) generateUserSession is a little expensive for basic
75 // auth, as it generates some random identifiers that will never be
76 // used. This should have a "fast" path for when user tokens aren't
77 // needed.
78 // This whole flow needs to be revisited anyway, as we can't be
79 // calling directly into pam for every request
80 return persistent_data::SessionStore::getInstance().generateUserSession(
Ed Tanous52cc1122020-07-18 13:51:21 -070081 user, persistent_data::PersistenceType::SINGLE_REQUEST,
Sunitha Harishc0ea7ae2020-10-30 02:37:30 -050082 isConfigureSelfOnly, clientIp.to_string());
James Feist3909dc82020-04-03 10:58:55 -070083}
84
Ed Tanous3174e4d2020-10-07 11:41:22 -070085static std::shared_ptr<persistent_data::UserSession>
James Feist3909dc82020-04-03 10:58:55 -070086 performTokenAuth(std::string_view auth_header)
87{
88 BMCWEB_LOG_DEBUG << "[AuthMiddleware] Token authentication";
89
90 std::string_view token = auth_header.substr(strlen("Token "));
91 auto session =
92 persistent_data::SessionStore::getInstance().loginSessionByToken(token);
93 return session;
94}
95
Ed Tanous3174e4d2020-10-07 11:41:22 -070096static std::shared_ptr<persistent_data::UserSession>
James Feist3909dc82020-04-03 10:58:55 -070097 performXtokenAuth(const crow::Request& req)
98{
99 BMCWEB_LOG_DEBUG << "[AuthMiddleware] X-Auth-Token authentication";
100
101 std::string_view token = req.getHeaderValue("X-Auth-Token");
102 if (token.empty())
103 {
104 return nullptr;
105 }
106 auto session =
107 persistent_data::SessionStore::getInstance().loginSessionByToken(token);
108 return session;
109}
110
Ed Tanous3174e4d2020-10-07 11:41:22 -0700111static std::shared_ptr<persistent_data::UserSession>
James Feist3909dc82020-04-03 10:58:55 -0700112 performCookieAuth(const crow::Request& req)
113{
114 BMCWEB_LOG_DEBUG << "[AuthMiddleware] Cookie authentication";
115
116 std::string_view cookieValue = req.getHeaderValue("Cookie");
117 if (cookieValue.empty())
118 {
119 return nullptr;
120 }
121
122 auto startIndex = cookieValue.find("SESSION=");
123 if (startIndex == std::string::npos)
124 {
125 return nullptr;
126 }
127 startIndex += sizeof("SESSION=") - 1;
128 auto endIndex = cookieValue.find(";", startIndex);
129 if (endIndex == std::string::npos)
130 {
131 endIndex = cookieValue.size();
132 }
133 std::string_view authKey =
134 cookieValue.substr(startIndex, endIndex - startIndex);
135
Ed Tanous3174e4d2020-10-07 11:41:22 -0700136 std::shared_ptr<persistent_data::UserSession> session =
James Feist3909dc82020-04-03 10:58:55 -0700137 persistent_data::SessionStore::getInstance().loginSessionByToken(
138 authKey);
139 if (session == nullptr)
140 {
141 return nullptr;
142 }
143#ifndef BMCWEB_INSECURE_DISABLE_CSRF_PREVENTION
144 // RFC7231 defines methods that need csrf protection
Ed Tanousb41187f2019-10-24 16:30:02 -0700145 if (req.method() != boost::beast::http::verb::get)
James Feist3909dc82020-04-03 10:58:55 -0700146 {
147 std::string_view csrf = req.getHeaderValue("X-XSRF-TOKEN");
148 // Make sure both tokens are filled
149 if (csrf.empty() || session->csrfToken.empty())
150 {
151 return nullptr;
152 }
153
Ed Tanous52cc1122020-07-18 13:51:21 -0700154 if (csrf.size() != persistent_data::sessionTokenSize)
James Feist3909dc82020-04-03 10:58:55 -0700155 {
156 return nullptr;
157 }
158 // Reject if csrf token not available
159 if (!crow::utility::constantTimeStringCompare(csrf, session->csrfToken))
160 {
161 return nullptr;
162 }
163 }
164#endif
165 return session;
166}
167
Alexander Filippov96457602020-09-29 14:19:38 +0300168#ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
Ed Tanous3174e4d2020-10-07 11:41:22 -0700169static std::shared_ptr<persistent_data::UserSession>
James Feist6964c982020-07-28 16:10:23 -0700170 performTLSAuth(const crow::Request& req, Response& res,
Ed Tanousb5a76932020-09-29 16:16:58 -0700171 const std::weak_ptr<persistent_data::UserSession>& session)
James Feist6964c982020-07-28 16:10:23 -0700172{
James Feist6964c982020-07-28 16:10:23 -0700173 if (auto sp = session.lock())
174 {
175 // set cookie only if this is req from the browser.
176 if (req.getHeaderValue("User-Agent").empty())
177 {
178 BMCWEB_LOG_DEBUG << " TLS session: " << sp->uniqueId
179 << " will be used for this request.";
180 return sp;
181 }
Ed Tanous3174e4d2020-10-07 11:41:22 -0700182 std::string_view cookieValue = req.getHeaderValue("Cookie");
183 if (cookieValue.empty() ||
184 cookieValue.find("SESSION=") == std::string::npos)
James Feist6964c982020-07-28 16:10:23 -0700185 {
Ed Tanous3174e4d2020-10-07 11:41:22 -0700186 // TODO: change this to not switch to cookie auth
187 res.addHeader("Set-Cookie", "XSRF-TOKEN=" + sp->csrfToken +
188 "; Secure\r\nSet-Cookie: SESSION=" +
189 sp->sessionToken +
190 "; Secure; HttpOnly\r\nSet-Cookie: "
191 "IsAuthenticated=true; Secure");
192 BMCWEB_LOG_DEBUG << " TLS session: " << sp->uniqueId
193 << " with cookie will be used for this request.";
194 return sp;
James Feist6964c982020-07-28 16:10:23 -0700195 }
196 }
James Feist6964c982020-07-28 16:10:23 -0700197 return nullptr;
198}
Alexander Filippov96457602020-09-29 14:19:38 +0300199#endif
James Feist6964c982020-07-28 16:10:23 -0700200
James Feist3909dc82020-04-03 10:58:55 -0700201// checks if request can be forwarded without authentication
202static bool isOnWhitelist(const crow::Request& req)
203{
204 // it's allowed to GET root node without authentication
Ed Tanousb41187f2019-10-24 16:30:02 -0700205 if (boost::beast::http::verb::get == req.method())
James Feist3909dc82020-04-03 10:58:55 -0700206 {
207 if (req.url == "/redfish/v1" || req.url == "/redfish/v1/" ||
208 req.url == "/redfish" || req.url == "/redfish/" ||
209 req.url == "/redfish/v1/odata" || req.url == "/redfish/v1/odata/")
210 {
211 return true;
212 }
Ed Tanousd4d25792020-09-29 15:15:03 -0700213 if (crow::webroutes::routes.find(std::string(req.url)) !=
214 crow::webroutes::routes.end())
James Feist3909dc82020-04-03 10:58:55 -0700215 {
216 return true;
217 }
218 }
219
220 // it's allowed to POST on session collection & login without
221 // authentication
Ed Tanousb41187f2019-10-24 16:30:02 -0700222 if (boost::beast::http::verb::post == req.method())
James Feist3909dc82020-04-03 10:58:55 -0700223 {
224 if ((req.url == "/redfish/v1/SessionService/Sessions") ||
225 (req.url == "/redfish/v1/SessionService/Sessions/") ||
226 (req.url == "/login"))
227 {
228 return true;
229 }
230 }
231
232 return false;
233}
234
Alexander Filippov96457602020-09-29 14:19:38 +0300235static void authenticate(
236 crow::Request& req, Response& res,
Ed Tanousf23b7292020-10-15 09:41:17 -0700237 [[maybe_unused]] const std::weak_ptr<persistent_data::UserSession>& session)
James Feist3909dc82020-04-03 10:58:55 -0700238{
239 if (isOnWhitelist(req))
240 {
241 return;
242 }
243
Ed Tanous52cc1122020-07-18 13:51:21 -0700244 const persistent_data::AuthConfigMethods& authMethodsConfig =
245 persistent_data::SessionStore::getInstance().getAuthMethodsConfig();
James Feist3909dc82020-04-03 10:58:55 -0700246
Alexander Filippov96457602020-09-29 14:19:38 +0300247#ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
James Feist6964c982020-07-28 16:10:23 -0700248 if (req.session == nullptr && authMethodsConfig.tls)
249 {
Ed Tanousf23b7292020-10-15 09:41:17 -0700250 req.session = performTLSAuth(req, res, session);
James Feist6964c982020-07-28 16:10:23 -0700251 }
Alexander Filippov96457602020-09-29 14:19:38 +0300252#endif
James Feist3909dc82020-04-03 10:58:55 -0700253 if (req.session == nullptr && authMethodsConfig.xtoken)
254 {
255 req.session = performXtokenAuth(req);
256 }
257 if (req.session == nullptr && authMethodsConfig.cookie)
258 {
259 req.session = performCookieAuth(req);
260 }
261 if (req.session == nullptr)
262 {
263 std::string_view authHeader = req.getHeaderValue("Authorization");
264 if (!authHeader.empty())
265 {
266 // Reject any kind of auth other than basic or token
267 if (boost::starts_with(authHeader, "Token ") &&
268 authMethodsConfig.sessionToken)
269 {
270 req.session = performTokenAuth(authHeader);
271 }
272 else if (boost::starts_with(authHeader, "Basic ") &&
273 authMethodsConfig.basic)
274 {
Sunitha Harishc0ea7ae2020-10-30 02:37:30 -0500275 req.session = performBasicAuth(req.ipAddress, authHeader);
James Feist3909dc82020-04-03 10:58:55 -0700276 }
277 }
278 }
279
280 if (req.session == nullptr)
281 {
282 BMCWEB_LOG_WARNING << "[AuthMiddleware] authorization failed";
283
284 // If it's a browser connecting, don't send the HTTP authenticate
285 // header, to avoid possible CSRF attacks with basic auth
286 if (http_helpers::requestPrefersHtml(req))
287 {
288 res.result(boost::beast::http::status::temporary_redirect);
289 res.addHeader("Location",
290 "/#/login?next=" + http_helpers::urlEncode(req.url));
291 }
292 else
293 {
294 res.result(boost::beast::http::status::unauthorized);
295 // only send the WWW-authenticate header if this isn't a xhr
296 // from the browser. most scripts,
297 if (req.getHeaderValue("User-Agent").empty())
298 {
299 res.addHeader("WWW-Authenticate", "Basic");
300 }
301 }
302
303 res.end();
304 return;
305 }
306}
307
308} // namespace authorization
309} // namespace crow