blob: 24dbb7b4c6fbea05f602b18b59416c8161809d3b [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>
16
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 Tanous52cc1122020-07-18 13:51:21 -070037static const 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 Tanous52cc1122020-07-18 13:51:21 -070082static const 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 Tanous52cc1122020-07-18 13:51:21 -070093static const 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 Tanous52cc1122020-07-18 13:51:21 -0700108static const 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 Tanous52cc1122020-07-18 13:51:21 -0700133 const 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 Tanous52cc1122020-07-18 13:51:21 -0700166static const std::shared_ptr<persistent_data::UserSession>
James Feist6964c982020-07-28 16:10:23 -0700167 performTLSAuth(const crow::Request& req, Response& res,
Ed Tanous52cc1122020-07-18 13:51:21 -0700168 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 }
179 else
180 {
181 std::string_view cookieValue = req.getHeaderValue("Cookie");
182 if (cookieValue.empty() ||
183 cookieValue.find("SESSION=") == std::string::npos)
184 {
185 // TODO: change this to not switch to cookie auth
186 res.addHeader(
187 "Set-Cookie",
188 "XSRF-TOKEN=" + sp->csrfToken +
189 "; Secure\r\nSet-Cookie: SESSION=" + sp->sessionToken +
190 "; Secure; HttpOnly\r\nSet-Cookie: "
191 "IsAuthenticated=true; Secure");
192 BMCWEB_LOG_DEBUG
193 << " TLS session: " << sp->uniqueId
194 << " with cookie will be used for this request.";
195 return sp;
196 }
197 }
198 }
James Feist6964c982020-07-28 16:10:23 -0700199 return nullptr;
200}
Alexander Filippov96457602020-09-29 14:19:38 +0300201#endif
James Feist6964c982020-07-28 16:10:23 -0700202
James Feist3909dc82020-04-03 10:58:55 -0700203// checks if request can be forwarded without authentication
204static bool isOnWhitelist(const crow::Request& req)
205{
206 // it's allowed to GET root node without authentication
Ed Tanousb41187f2019-10-24 16:30:02 -0700207 if (boost::beast::http::verb::get == req.method())
James Feist3909dc82020-04-03 10:58:55 -0700208 {
209 if (req.url == "/redfish/v1" || req.url == "/redfish/v1/" ||
210 req.url == "/redfish" || req.url == "/redfish/" ||
211 req.url == "/redfish/v1/odata" || req.url == "/redfish/v1/odata/")
212 {
213 return true;
214 }
Ed Tanousd4d25792020-09-29 15:15:03 -0700215 if (crow::webroutes::routes.find(std::string(req.url)) !=
216 crow::webroutes::routes.end())
James Feist3909dc82020-04-03 10:58:55 -0700217 {
218 return true;
219 }
220 }
221
222 // it's allowed to POST on session collection & login without
223 // authentication
Ed Tanousb41187f2019-10-24 16:30:02 -0700224 if (boost::beast::http::verb::post == req.method())
James Feist3909dc82020-04-03 10:58:55 -0700225 {
226 if ((req.url == "/redfish/v1/SessionService/Sessions") ||
227 (req.url == "/redfish/v1/SessionService/Sessions/") ||
228 (req.url == "/login"))
229 {
230 return true;
231 }
232 }
233
234 return false;
235}
236
Alexander Filippov96457602020-09-29 14:19:38 +0300237static void authenticate(
238 crow::Request& req, Response& res,
239 [[maybe_unused]] std::weak_ptr<persistent_data::UserSession> session)
James Feist3909dc82020-04-03 10:58:55 -0700240{
241 if (isOnWhitelist(req))
242 {
243 return;
244 }
245
Ed Tanous52cc1122020-07-18 13:51:21 -0700246 const persistent_data::AuthConfigMethods& authMethodsConfig =
247 persistent_data::SessionStore::getInstance().getAuthMethodsConfig();
James Feist3909dc82020-04-03 10:58:55 -0700248
Alexander Filippov96457602020-09-29 14:19:38 +0300249#ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
James Feist6964c982020-07-28 16:10:23 -0700250 if (req.session == nullptr && authMethodsConfig.tls)
251 {
252 req.session = performTLSAuth(req, res, session);
253 }
Alexander Filippov96457602020-09-29 14:19:38 +0300254#endif
James Feist3909dc82020-04-03 10:58:55 -0700255 if (req.session == nullptr && authMethodsConfig.xtoken)
256 {
257 req.session = performXtokenAuth(req);
258 }
259 if (req.session == nullptr && authMethodsConfig.cookie)
260 {
261 req.session = performCookieAuth(req);
262 }
263 if (req.session == nullptr)
264 {
265 std::string_view authHeader = req.getHeaderValue("Authorization");
266 if (!authHeader.empty())
267 {
268 // Reject any kind of auth other than basic or token
269 if (boost::starts_with(authHeader, "Token ") &&
270 authMethodsConfig.sessionToken)
271 {
272 req.session = performTokenAuth(authHeader);
273 }
274 else if (boost::starts_with(authHeader, "Basic ") &&
275 authMethodsConfig.basic)
276 {
277 req.session = performBasicAuth(authHeader);
278 }
279 }
280 }
281
282 if (req.session == nullptr)
283 {
284 BMCWEB_LOG_WARNING << "[AuthMiddleware] authorization failed";
285
286 // If it's a browser connecting, don't send the HTTP authenticate
287 // header, to avoid possible CSRF attacks with basic auth
288 if (http_helpers::requestPrefersHtml(req))
289 {
290 res.result(boost::beast::http::status::temporary_redirect);
291 res.addHeader("Location",
292 "/#/login?next=" + http_helpers::urlEncode(req.url));
293 }
294 else
295 {
296 res.result(boost::beast::http::status::unauthorized);
297 // only send the WWW-authenticate header if this isn't a xhr
298 // from the browser. most scripts,
299 if (req.getHeaderValue("User-Agent").empty())
300 {
301 res.addHeader("WWW-Authenticate", "Basic");
302 }
303 }
304
305 res.end();
306 return;
307 }
308}
309
310} // namespace authorization
311} // namespace crow