blob: 997e50434fe4aba0493854f245800ffe506af277 [file] [log] [blame]
James Feist3909dc82020-04-03 10:58:55 -07001#pragma once
2
3#include "webroutes.hpp"
4
5#include <app.h>
6#include <common.h>
7#include <http_request.h>
8#include <http_response.h>
9
10#include <boost/algorithm/string/predicate.hpp>
11#include <boost/container/flat_set.hpp>
12#include <http_utility.hpp>
13#include <pam_authenticate.hpp>
James Feist3909dc82020-04-03 10:58:55 -070014
15#include <random>
Ed Tanousb5a76932020-09-29 16:16:58 -070016#include <utility>
James Feist3909dc82020-04-03 10:58:55 -070017
18namespace crow
19{
20
21namespace authorization
22{
23
24static void cleanupTempSession(Request& req)
25{
26 // TODO(ed) THis should really be handled by the persistent data
27 // middleware, but because it is upstream, it doesn't have access to the
28 // session information. Should the data middleware persist the current
29 // user session?
30 if (req.session != nullptr &&
31 req.session->persistence ==
Ed Tanous52cc1122020-07-18 13:51:21 -070032 persistent_data::PersistenceType::SINGLE_REQUEST)
James Feist3909dc82020-04-03 10:58:55 -070033 {
34 persistent_data::SessionStore::getInstance().removeSession(req.session);
35 }
36}
37
Ed Tanous3174e4d2020-10-07 11:41:22 -070038static std::shared_ptr<persistent_data::UserSession>
James Feist3909dc82020-04-03 10:58:55 -070039 performBasicAuth(std::string_view auth_header)
40{
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;
64
65 int pamrc = pamAuthenticateUser(user, pass);
66 bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD;
67 if ((pamrc != PAM_SUCCESS) && !isConfigureSelfOnly)
68 {
69 return nullptr;
70 }
71
72 // TODO(ed) generateUserSession is a little expensive for basic
73 // auth, as it generates some random identifiers that will never be
74 // used. This should have a "fast" path for when user tokens aren't
75 // needed.
76 // This whole flow needs to be revisited anyway, as we can't be
77 // calling directly into pam for every request
78 return persistent_data::SessionStore::getInstance().generateUserSession(
Ed Tanous52cc1122020-07-18 13:51:21 -070079 user, persistent_data::PersistenceType::SINGLE_REQUEST,
James Feist3909dc82020-04-03 10:58:55 -070080 isConfigureSelfOnly);
81}
82
Ed Tanous3174e4d2020-10-07 11:41:22 -070083static std::shared_ptr<persistent_data::UserSession>
James Feist3909dc82020-04-03 10:58:55 -070084 performTokenAuth(std::string_view auth_header)
85{
86 BMCWEB_LOG_DEBUG << "[AuthMiddleware] Token authentication";
87
88 std::string_view token = auth_header.substr(strlen("Token "));
89 auto session =
90 persistent_data::SessionStore::getInstance().loginSessionByToken(token);
91 return session;
92}
93
Ed Tanous3174e4d2020-10-07 11:41:22 -070094static std::shared_ptr<persistent_data::UserSession>
James Feist3909dc82020-04-03 10:58:55 -070095 performXtokenAuth(const crow::Request& req)
96{
97 BMCWEB_LOG_DEBUG << "[AuthMiddleware] X-Auth-Token authentication";
98
99 std::string_view token = req.getHeaderValue("X-Auth-Token");
100 if (token.empty())
101 {
102 return nullptr;
103 }
104 auto session =
105 persistent_data::SessionStore::getInstance().loginSessionByToken(token);
106 return session;
107}
108
Ed Tanous3174e4d2020-10-07 11:41:22 -0700109static std::shared_ptr<persistent_data::UserSession>
James Feist3909dc82020-04-03 10:58:55 -0700110 performCookieAuth(const crow::Request& req)
111{
112 BMCWEB_LOG_DEBUG << "[AuthMiddleware] Cookie authentication";
113
114 std::string_view cookieValue = req.getHeaderValue("Cookie");
115 if (cookieValue.empty())
116 {
117 return nullptr;
118 }
119
120 auto startIndex = cookieValue.find("SESSION=");
121 if (startIndex == std::string::npos)
122 {
123 return nullptr;
124 }
125 startIndex += sizeof("SESSION=") - 1;
126 auto endIndex = cookieValue.find(";", startIndex);
127 if (endIndex == std::string::npos)
128 {
129 endIndex = cookieValue.size();
130 }
131 std::string_view authKey =
132 cookieValue.substr(startIndex, endIndex - startIndex);
133
Ed Tanous3174e4d2020-10-07 11:41:22 -0700134 std::shared_ptr<persistent_data::UserSession> session =
James Feist3909dc82020-04-03 10:58:55 -0700135 persistent_data::SessionStore::getInstance().loginSessionByToken(
136 authKey);
137 if (session == nullptr)
138 {
139 return nullptr;
140 }
141#ifndef BMCWEB_INSECURE_DISABLE_CSRF_PREVENTION
142 // RFC7231 defines methods that need csrf protection
Ed Tanousb41187f2019-10-24 16:30:02 -0700143 if (req.method() != boost::beast::http::verb::get)
James Feist3909dc82020-04-03 10:58:55 -0700144 {
145 std::string_view csrf = req.getHeaderValue("X-XSRF-TOKEN");
146 // Make sure both tokens are filled
147 if (csrf.empty() || session->csrfToken.empty())
148 {
149 return nullptr;
150 }
151
Ed Tanous52cc1122020-07-18 13:51:21 -0700152 if (csrf.size() != persistent_data::sessionTokenSize)
James Feist3909dc82020-04-03 10:58:55 -0700153 {
154 return nullptr;
155 }
156 // Reject if csrf token not available
157 if (!crow::utility::constantTimeStringCompare(csrf, session->csrfToken))
158 {
159 return nullptr;
160 }
161 }
162#endif
163 return session;
164}
165
Alexander Filippov96457602020-09-29 14:19:38 +0300166#ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
Ed Tanous3174e4d2020-10-07 11:41:22 -0700167static std::shared_ptr<persistent_data::UserSession>
James Feist6964c982020-07-28 16:10:23 -0700168 performTLSAuth(const crow::Request& req, Response& res,
Ed Tanousb5a76932020-09-29 16:16:58 -0700169 const std::weak_ptr<persistent_data::UserSession>& session)
James Feist6964c982020-07-28 16:10:23 -0700170{
James Feist6964c982020-07-28 16:10:23 -0700171 if (auto sp = session.lock())
172 {
173 // set cookie only if this is req from the browser.
174 if (req.getHeaderValue("User-Agent").empty())
175 {
176 BMCWEB_LOG_DEBUG << " TLS session: " << sp->uniqueId
177 << " will be used for this request.";
178 return sp;
179 }
Ed Tanous3174e4d2020-10-07 11:41:22 -0700180 std::string_view cookieValue = req.getHeaderValue("Cookie");
181 if (cookieValue.empty() ||
182 cookieValue.find("SESSION=") == std::string::npos)
James Feist6964c982020-07-28 16:10:23 -0700183 {
Ed Tanous3174e4d2020-10-07 11:41:22 -0700184 // TODO: change this to not switch to cookie auth
185 res.addHeader("Set-Cookie", "XSRF-TOKEN=" + sp->csrfToken +
186 "; Secure\r\nSet-Cookie: SESSION=" +
187 sp->sessionToken +
188 "; Secure; HttpOnly\r\nSet-Cookie: "
189 "IsAuthenticated=true; Secure");
190 BMCWEB_LOG_DEBUG << " TLS session: " << sp->uniqueId
191 << " with cookie will be used for this request.";
192 return sp;
James Feist6964c982020-07-28 16:10:23 -0700193 }
194 }
James Feist6964c982020-07-28 16:10:23 -0700195 return nullptr;
196}
Alexander Filippov96457602020-09-29 14:19:38 +0300197#endif
James Feist6964c982020-07-28 16:10:23 -0700198
James Feist3909dc82020-04-03 10:58:55 -0700199// checks if request can be forwarded without authentication
200static bool isOnWhitelist(const crow::Request& req)
201{
202 // it's allowed to GET root node without authentication
Ed Tanousb41187f2019-10-24 16:30:02 -0700203 if (boost::beast::http::verb::get == req.method())
James Feist3909dc82020-04-03 10:58:55 -0700204 {
205 if (req.url == "/redfish/v1" || req.url == "/redfish/v1/" ||
206 req.url == "/redfish" || req.url == "/redfish/" ||
207 req.url == "/redfish/v1/odata" || req.url == "/redfish/v1/odata/")
208 {
209 return true;
210 }
Ed Tanousd4d25792020-09-29 15:15:03 -0700211 if (crow::webroutes::routes.find(std::string(req.url)) !=
212 crow::webroutes::routes.end())
James Feist3909dc82020-04-03 10:58:55 -0700213 {
214 return true;
215 }
216 }
217
218 // it's allowed to POST on session collection & login without
219 // authentication
Ed Tanousb41187f2019-10-24 16:30:02 -0700220 if (boost::beast::http::verb::post == req.method())
James Feist3909dc82020-04-03 10:58:55 -0700221 {
222 if ((req.url == "/redfish/v1/SessionService/Sessions") ||
223 (req.url == "/redfish/v1/SessionService/Sessions/") ||
224 (req.url == "/login"))
225 {
226 return true;
227 }
228 }
229
230 return false;
231}
232
Alexander Filippov96457602020-09-29 14:19:38 +0300233static void authenticate(
234 crow::Request& req, Response& res,
235 [[maybe_unused]] std::weak_ptr<persistent_data::UserSession> session)
James Feist3909dc82020-04-03 10:58:55 -0700236{
237 if (isOnWhitelist(req))
238 {
239 return;
240 }
241
Ed Tanous52cc1122020-07-18 13:51:21 -0700242 const persistent_data::AuthConfigMethods& authMethodsConfig =
243 persistent_data::SessionStore::getInstance().getAuthMethodsConfig();
James Feist3909dc82020-04-03 10:58:55 -0700244
Alexander Filippov96457602020-09-29 14:19:38 +0300245#ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
James Feist6964c982020-07-28 16:10:23 -0700246 if (req.session == nullptr && authMethodsConfig.tls)
247 {
Ed Tanousb5a76932020-09-29 16:16:58 -0700248 req.session = performTLSAuth(req, res, std::move(session));
James Feist6964c982020-07-28 16:10:23 -0700249 }
Alexander Filippov96457602020-09-29 14:19:38 +0300250#endif
James Feist3909dc82020-04-03 10:58:55 -0700251 if (req.session == nullptr && authMethodsConfig.xtoken)
252 {
253 req.session = performXtokenAuth(req);
254 }
255 if (req.session == nullptr && authMethodsConfig.cookie)
256 {
257 req.session = performCookieAuth(req);
258 }
259 if (req.session == nullptr)
260 {
261 std::string_view authHeader = req.getHeaderValue("Authorization");
262 if (!authHeader.empty())
263 {
264 // Reject any kind of auth other than basic or token
265 if (boost::starts_with(authHeader, "Token ") &&
266 authMethodsConfig.sessionToken)
267 {
268 req.session = performTokenAuth(authHeader);
269 }
270 else if (boost::starts_with(authHeader, "Basic ") &&
271 authMethodsConfig.basic)
272 {
273 req.session = performBasicAuth(authHeader);
274 }
275 }
276 }
277
278 if (req.session == nullptr)
279 {
280 BMCWEB_LOG_WARNING << "[AuthMiddleware] authorization failed";
281
282 // If it's a browser connecting, don't send the HTTP authenticate
283 // header, to avoid possible CSRF attacks with basic auth
284 if (http_helpers::requestPrefersHtml(req))
285 {
286 res.result(boost::beast::http::status::temporary_redirect);
287 res.addHeader("Location",
288 "/#/login?next=" + http_helpers::urlEncode(req.url));
289 }
290 else
291 {
292 res.result(boost::beast::http::status::unauthorized);
293 // only send the WWW-authenticate header if this isn't a xhr
294 // from the browser. most scripts,
295 if (req.getHeaderValue("User-Agent").empty())
296 {
297 res.addHeader("WWW-Authenticate", "Basic");
298 }
299 }
300
301 res.end();
302 return;
303 }
304}
305
306} // namespace authorization
307} // namespace crow