blob: 1fd1b1232ee445bb18c5502f838e0764b792e1fb [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
Ed Tanous52cc1122020-07-18 13:51:21 -0700165static const std::shared_ptr<persistent_data::UserSession>
James Feist6964c982020-07-28 16:10:23 -0700166 performTLSAuth(const crow::Request& req, Response& res,
Ed Tanous52cc1122020-07-18 13:51:21 -0700167 std::weak_ptr<persistent_data::UserSession> session)
James Feist6964c982020-07-28 16:10:23 -0700168{
169#ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
170 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 }
199#endif
200 return nullptr;
201}
202
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 }
215 else if (crow::webroutes::routes.find(std::string(req.url)) !=
216 crow::webroutes::routes.end())
217 {
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
Ed Tanous52cc1122020-07-18 13:51:21 -0700237static void authenticate(crow::Request& req, Response& res,
238 std::weak_ptr<persistent_data::UserSession> session)
James Feist3909dc82020-04-03 10:58:55 -0700239{
240 if (isOnWhitelist(req))
241 {
242 return;
243 }
244
Ed Tanous52cc1122020-07-18 13:51:21 -0700245 const persistent_data::AuthConfigMethods& authMethodsConfig =
246 persistent_data::SessionStore::getInstance().getAuthMethodsConfig();
James Feist3909dc82020-04-03 10:58:55 -0700247
James Feist6964c982020-07-28 16:10:23 -0700248 if (req.session == nullptr && authMethodsConfig.tls)
249 {
250 req.session = performTLSAuth(req, res, session);
251 }
James Feist3909dc82020-04-03 10:58:55 -0700252 if (req.session == nullptr && authMethodsConfig.xtoken)
253 {
254 req.session = performXtokenAuth(req);
255 }
256 if (req.session == nullptr && authMethodsConfig.cookie)
257 {
258 req.session = performCookieAuth(req);
259 }
260 if (req.session == nullptr)
261 {
262 std::string_view authHeader = req.getHeaderValue("Authorization");
263 if (!authHeader.empty())
264 {
265 // Reject any kind of auth other than basic or token
266 if (boost::starts_with(authHeader, "Token ") &&
267 authMethodsConfig.sessionToken)
268 {
269 req.session = performTokenAuth(authHeader);
270 }
271 else if (boost::starts_with(authHeader, "Basic ") &&
272 authMethodsConfig.basic)
273 {
274 req.session = performBasicAuth(authHeader);
275 }
276 }
277 }
278
279 if (req.session == nullptr)
280 {
281 BMCWEB_LOG_WARNING << "[AuthMiddleware] authorization failed";
282
283 // If it's a browser connecting, don't send the HTTP authenticate
284 // header, to avoid possible CSRF attacks with basic auth
285 if (http_helpers::requestPrefersHtml(req))
286 {
287 res.result(boost::beast::http::status::temporary_redirect);
288 res.addHeader("Location",
289 "/#/login?next=" + http_helpers::urlEncode(req.url));
290 }
291 else
292 {
293 res.result(boost::beast::http::status::unauthorized);
294 // only send the WWW-authenticate header if this isn't a xhr
295 // from the browser. most scripts,
296 if (req.getHeaderValue("User-Agent").empty())
297 {
298 res.addHeader("WWW-Authenticate", "Basic");
299 }
300 }
301
302 res.end();
303 return;
304 }
305}
306
307} // namespace authorization
308} // namespace crow