blob: 07540d42b0ebb8db355dbe6ba1e3caeb1bd3dafe [file] [log] [blame]
Ed Tanousf9273472017-02-28 16:05:13 -08001#pragma once
2
Ed Tanous911ac312017-08-15 09:37:42 -07003#include <pam_authenticate.hpp>
Ed Tanousba9f9a62017-10-11 16:40:35 -07004#include <persistent_data_middleware.hpp>
Ed Tanous911ac312017-08-15 09:37:42 -07005#include <webassets.hpp>
6#include <random>
7#include <crow/app.h>
Borawski.Lukasz9d8fd302018-01-05 14:56:09 +01008#include <crow/http_codes.h>
Ed Tanousf9273472017-02-28 16:05:13 -08009#include <crow/http_request.h>
10#include <crow/http_response.h>
Ed Tanousba9f9a62017-10-11 16:40:35 -070011#include <boost/bimap.hpp>
Ed Tanous4758d5b2017-06-06 15:28:13 -070012#include <boost/container/flat_set.hpp>
Borawski.Lukasz9d8fd302018-01-05 14:56:09 +010013
Ed Tanous99923322017-03-03 14:21:24 -080014namespace crow {
Ed Tanousb4d29f42017-03-24 16:39:25 -070015
Ed Tanous911ac312017-08-15 09:37:42 -070016namespace TokenAuthorization {
Ed Tanousb4d29f42017-03-24 16:39:25 -070017
Ed Tanous911ac312017-08-15 09:37:42 -070018class Middleware {
Ed Tanous3dac7492017-08-02 13:46:20 -070019 public:
Borawski.Lukasz9d8fd302018-01-05 14:56:09 +010020 struct context {
21 const crow::PersistentData::UserSession* session;
22 };
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010023
24 void before_handle(crow::request& req, response& res, context& ctx) {
Ed Tanousbae064e2018-03-22 15:44:39 -070025 if (is_on_whitelist(req)) {
26 return;
27 }
28
Ed Tanous1ea9f062018-03-27 17:45:20 -070029 ctx.session = perform_xtoken_auth(req);
30
31 if (ctx.session == nullptr) {
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010032 ctx.session = perform_cookie_auth(req);
Ed Tanous1ea9f062018-03-27 17:45:20 -070033 }
34
35 const std::string& auth_header = req.get_header_value("Authorization");
36 // Reject any kind of auth other than basic or token
37 if (ctx.session == nullptr && boost::starts_with(auth_header, "Token ")) {
38 ctx.session = perform_token_auth(auth_header);
39 }
40
41 if (ctx.session == nullptr && boost::starts_with(auth_header, "Basic ")) {
42 ctx.session = perform_basic_auth(auth_header);
Borawski.Lukasz9d8fd302018-01-05 14:56:09 +010043 }
44
Ed Tanousbae064e2018-03-22 15:44:39 -070045 if (ctx.session == nullptr) {
Borawski.Lukasz9d8fd302018-01-05 14:56:09 +010046 CROW_LOG_WARNING << "[AuthMiddleware] authorization failed";
47 res.code = static_cast<int>(HttpRespCode::UNAUTHORIZED);
48 res.add_header("WWW-Authenticate", "Basic");
Ed Tanousf3d847c2017-06-12 16:01:42 -070049 res.end();
Borawski.Lukasz9d8fd302018-01-05 14:56:09 +010050 return;
51 }
Ed Tanousf9273472017-02-28 16:05:13 -080052
Borawski.Lukasz9d8fd302018-01-05 14:56:09 +010053 // TODO get user privileges here and propagate it via MW context
54 // else let the request continue unharmed
55 }
Ed Tanousf3d847c2017-06-12 16:01:42 -070056
Borawski.Lukasz9d8fd302018-01-05 14:56:09 +010057 template <typename AllContext>
58 void after_handle(request& req, response& res, context& ctx,
59 AllContext& allctx) {
60 // TODO(ed) THis should really be handled by the persistent data middleware,
61 // but because it is upstream, it doesn't have access to the session
62 // information. Should the data middleware persist the current user
63 // session?
64 if (ctx.session != nullptr &&
65 ctx.session->persistence ==
66 crow::PersistentData::PersistenceType::SINGLE_REQUEST) {
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010067 PersistentData::session_store->remove_session(ctx.session);
Ed Tanousf3d847c2017-06-12 16:01:42 -070068 }
69 }
70
Borawski.Lukasz9d8fd302018-01-05 14:56:09 +010071 private:
72 const crow::PersistentData::UserSession* perform_basic_auth(
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010073 const std::string& auth_header) const {
Borawski.Lukasz9d8fd302018-01-05 14:56:09 +010074 CROW_LOG_DEBUG << "[AuthMiddleware] Basic authentication";
75
76 std::string auth_data;
77 std::string param = auth_header.substr(strlen("Basic "));
78 if (!crow::utility::base64_decode(param, auth_data)) {
79 return nullptr;
80 }
81 std::size_t separator = auth_data.find(':');
82 if (separator == std::string::npos) {
83 return nullptr;
84 }
85
86 std::string user = auth_data.substr(0, separator);
87 separator += 1;
88 if (separator > auth_data.size()) {
89 return nullptr;
90 }
91 std::string pass = auth_data.substr(separator);
92
93 CROW_LOG_DEBUG << "[AuthMiddleware] Authenticating user: " << user;
94
95 if (!pam_authenticate_user(user, pass)) {
96 return nullptr;
97 }
98
99 // TODO(ed) generate_user_session is a little expensive for basic
100 // auth, as it generates some random identifiers that will never be
101 // used. This should have a "fast" path for when user tokens aren't
102 // needed.
103 // This whole flow needs to be revisited anyway, as we can't be
104 // calling directly into pam for every request
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100105 return &(PersistentData::session_store->generate_user_session(
Borawski.Lukasz9d8fd302018-01-05 14:56:09 +0100106 user, crow::PersistentData::PersistenceType::SINGLE_REQUEST));
Ed Tanousf3d847c2017-06-12 16:01:42 -0700107 }
Ed Tanous8041f312017-04-03 09:47:01 -0700108
Borawski.Lukasz9d8fd302018-01-05 14:56:09 +0100109 const crow::PersistentData::UserSession* perform_token_auth(
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100110 const std::string& auth_header) const {
Borawski.Lukasz9d8fd302018-01-05 14:56:09 +0100111 CROW_LOG_DEBUG << "[AuthMiddleware] Token authentication";
112
113 std::string token = auth_header.substr(strlen("Token "));
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100114 auto session = PersistentData::session_store->login_session_by_token(token);
Borawski.Lukasz9d8fd302018-01-05 14:56:09 +0100115 return session;
116 }
117
118 const crow::PersistentData::UserSession* perform_xtoken_auth(
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100119 const crow::request& req) const {
Borawski.Lukasz9d8fd302018-01-05 14:56:09 +0100120 CROW_LOG_DEBUG << "[AuthMiddleware] X-Auth-Token authentication";
121
Ed Tanous1ea9f062018-03-27 17:45:20 -0700122 const std::string& token = req.get_header_value("X-Auth-Token");
123 if (token.empty()) {
124 return nullptr;
125 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100126 auto session = PersistentData::session_store->login_session_by_token(token);
Borawski.Lukasz9d8fd302018-01-05 14:56:09 +0100127 return session;
128 }
129
130 const crow::PersistentData::UserSession* perform_cookie_auth(
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100131 const crow::request& req) const {
Borawski.Lukasz9d8fd302018-01-05 14:56:09 +0100132 CROW_LOG_DEBUG << "[AuthMiddleware] Cookie authentication";
133
134 auto& cookie_value = req.get_header_value("Cookie");
Ed Tanous1ea9f062018-03-27 17:45:20 -0700135 if (cookie_value.empty()) {
136 return nullptr;
137 }
Borawski.Lukasz9d8fd302018-01-05 14:56:09 +0100138
139 auto start_index = cookie_value.find("SESSION=");
140 if (start_index == std::string::npos) {
141 return nullptr;
142 }
Ed Tanous41ff64d2018-01-30 13:13:38 -0800143 start_index += sizeof("SESSION=") - 1;
Borawski.Lukasz9d8fd302018-01-05 14:56:09 +0100144 auto end_index = cookie_value.find(";", start_index);
145 if (end_index == std::string::npos) {
146 end_index = cookie_value.size();
147 }
148 std::string auth_key =
149 cookie_value.substr(start_index, end_index - start_index);
150
151 const crow::PersistentData::UserSession* session =
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100152 PersistentData::session_store->login_session_by_token(auth_key);
Borawski.Lukasz9d8fd302018-01-05 14:56:09 +0100153 if (session == nullptr) {
154 return nullptr;
155 }
156
157 // RFC7231 defines methods that need csrf protection
158 if (req.method != "GET"_method) {
159 const std::string& csrf = req.get_header_value("X-XSRF-TOKEN");
160 // Make sure both tokens are filled
161 if (csrf.empty() || session->csrf_token.empty()) {
162 return nullptr;
163 }
164 // Reject if csrf token not available
165 if (csrf != session->csrf_token) {
166 return nullptr;
167 }
168 }
169 return session;
170 }
171
172 // checks if request can be forwarded without authentication
173 bool is_on_whitelist(const crow::request& req) const {
174 // it's allowed to GET root node without authentication
175 if ("GET"_method == req.method) {
Ed Tanousaa2e59c2018-04-12 12:17:20 -0700176 CROW_LOG_DEBUG << "TESTING ROUTE " << req.url;
177 if (req.url == "/redfish/v1" || req.url == "/redfish/v1/") {
Borawski.Lukasz9d8fd302018-01-05 14:56:09 +0100178 return true;
179 } else if (crow::webassets::routes.find(req.url) !=
180 crow::webassets::routes.end()) {
181 return true;
182 }
183 }
184
185 // it's allowed to POST on session collection & login without authentication
186 if ("POST"_method == req.method) {
Ed Tanousaa2e59c2018-04-12 12:17:20 -0700187 if ((req.url == "/redfish/v1/SessionService/Sessions" ||
188 req.url == "/redfish/v1/SessionService/Sessions/") ||
Borawski.Lukasz9d8fd302018-01-05 14:56:09 +0100189 (req.url == "/login") || (req.url == "/logout")) {
190 return true;
191 }
192 }
193
194 return false;
195 }
Ed Tanous99923322017-03-03 14:21:24 -0800196};
Ed Tanousf3d847c2017-06-12 16:01:42 -0700197
Ed Tanousba9f9a62017-10-11 16:40:35 -0700198// TODO(ed) see if there is a better way to allow middlewares to request
199// routes.
Ed Tanous911ac312017-08-15 09:37:42 -0700200// Possibly an init function on first construction?
201template <typename... Middlewares>
202void request_routes(Crow<Middlewares...>& app) {
Ed Tanousba9f9a62017-10-11 16:40:35 -0700203 static_assert(
204 black_magic::contains<PersistentData::Middleware, Middlewares...>::value,
205 "TokenAuthorization middleware must be enabled in app to use "
206 "auth routes");
Ed Tanous911ac312017-08-15 09:37:42 -0700207 CROW_ROUTE(app, "/login")
208 .methods(
209 "POST"_method)([&](const crow::request& req, crow::response& res) {
210 std::string content_type;
211 auto content_type_it = req.headers.find("content-type");
212 if (content_type_it != req.headers.end()) {
213 content_type = content_type_it->second;
214 boost::algorithm::to_lower(content_type);
215 }
Ed Tanousdb024a52018-03-06 12:50:34 -0800216 const std::string* username;
217 const std::string* password;
Ed Tanous911ac312017-08-15 09:37:42 -0700218 bool looks_like_ibm = false;
Ed Tanousdb024a52018-03-06 12:50:34 -0800219
Ed Tanousdb024a52018-03-06 12:50:34 -0800220 // This object needs to be declared at this scope so the strings within
221 // it are not destroyed before we can use them
222 nlohmann::json login_credentials;
Ed Tanous911ac312017-08-15 09:37:42 -0700223 // Check if auth was provided by a payload
224 if (content_type == "application/json") {
Ed Tanousdb024a52018-03-06 12:50:34 -0800225 login_credentials = nlohmann::json::parse(req.body, nullptr, false);
Ed Tanousba9f9a62017-10-11 16:40:35 -0700226 if (login_credentials.is_discarded()) {
Ed Tanous911ac312017-08-15 09:37:42 -0700227 res.code = 400;
228 res.end();
229 return;
230 }
Ed Tanousba9f9a62017-10-11 16:40:35 -0700231 // check for username/password in the root object
232 // THis method is how intel APIs authenticate
233 auto user_it = login_credentials.find("username");
234 auto pass_it = login_credentials.find("password");
235 if (user_it != login_credentials.end() &&
236 pass_it != login_credentials.end()) {
Ed Tanousdb024a52018-03-06 12:50:34 -0800237 username = user_it->get_ptr<const std::string*>();
238 password = pass_it->get_ptr<const std::string*>();
Ed Tanousba9f9a62017-10-11 16:40:35 -0700239 } else {
240 // Openbmc appears to push a data object that contains the same
241 // keys (username and password), attempt to use that
242 auto data_it = login_credentials.find("data");
243 if (data_it != login_credentials.end()) {
244 // Some apis produce an array of value ["username",
245 // "password"]
246 if (data_it->is_array()) {
247 if (data_it->size() == 2) {
Ed Tanousdb024a52018-03-06 12:50:34 -0800248 username = (*data_it)[0].get_ptr<const std::string*>();
249 password = (*data_it)[1].get_ptr<const std::string*>();
Ed Tanousba9f9a62017-10-11 16:40:35 -0700250 looks_like_ibm = true;
251 }
252 } else if (data_it->is_object()) {
253 auto user_it = data_it->find("username");
254 auto pass_it = data_it->find("password");
255 if (user_it != data_it->end() && pass_it != data_it->end()) {
Ed Tanousdb024a52018-03-06 12:50:34 -0800256 username = user_it->get_ptr<const std::string*>();
257 password = pass_it->get_ptr<const std::string*>();
Ed Tanousba9f9a62017-10-11 16:40:35 -0700258 }
259 }
260 }
261 }
Ed Tanous911ac312017-08-15 09:37:42 -0700262 } else {
263 // check if auth was provided as a query string
264 auto user_it = req.headers.find("username");
265 auto pass_it = req.headers.find("password");
266 if (user_it != req.headers.end() && pass_it != req.headers.end()) {
Ed Tanousdb024a52018-03-06 12:50:34 -0800267 username = &user_it->second;
268 password = &pass_it->second;
Ed Tanous911ac312017-08-15 09:37:42 -0700269 }
270 }
271
Ed Tanousdb024a52018-03-06 12:50:34 -0800272 if (username != nullptr && !username->empty() && password != nullptr &&
273 !password->empty()) {
274 if (!pam_authenticate_user(*username, *password)) {
Borawski.Lukasz9d8fd302018-01-05 14:56:09 +0100275 res.code = res.code = static_cast<int>(HttpRespCode::UNAUTHORIZED);
Ed Tanous911ac312017-08-15 09:37:42 -0700276 } else {
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100277 auto& session =
Ed Tanousdb024a52018-03-06 12:50:34 -0800278 PersistentData::session_store->generate_user_session(*username);
Ed Tanous911ac312017-08-15 09:37:42 -0700279
Ed Tanousba9f9a62017-10-11 16:40:35 -0700280 if (looks_like_ibm) {
281 // IBM requires a very specific login structure, and doesn't
282 // actually look at the status code. TODO(ed).... Fix that
283 // upstream
Ed Tanousbae064e2018-03-22 15:44:39 -0700284 res.json_value = {{"data", "User '" + *username + "' logged in"},
285 {"message", "200 OK"},
286 {"status", "ok"}};
Borawski.Lukasz9d8fd302018-01-05 14:56:09 +0100287 res.add_header("Set-Cookie", "XSRF-TOKEN=" + session.csrf_token);
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100288 res.add_header("Set-Cookie", "SESSION=" + session.session_token +
289 "; Secure; HttpOnly");
Ed Tanousba9f9a62017-10-11 16:40:35 -0700290 } else {
291 // if content type is json, assume json token
Ed Tanousbae064e2018-03-22 15:44:39 -0700292 res.json_value = {{"token", session.session_token}};
Ed Tanous911ac312017-08-15 09:37:42 -0700293 }
294 }
295
296 } else {
Borawski.Lukasz9d8fd302018-01-05 14:56:09 +0100297 res.code = static_cast<int>(HttpRespCode::BAD_REQUEST);
Ed Tanous911ac312017-08-15 09:37:42 -0700298 }
299 res.end();
300 });
Borawski.Lukasz9d8fd302018-01-05 14:56:09 +0100301
302 CROW_ROUTE(app, "/logout")
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100303 .methods("POST"_method)(
304 [&](const crow::request& req, crow::response& res) {
305 auto& session =
306 app.template get_context<TokenAuthorization::Middleware>(req)
307 .session;
308 if (session != nullptr) {
309 PersistentData::session_store->remove_session(session);
310 }
311 res.code = static_cast<int>(HttpRespCode::OK);
312 res.end();
313 return;
Borawski.Lukasz9d8fd302018-01-05 14:56:09 +0100314
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100315 });
Ed Tanous911ac312017-08-15 09:37:42 -0700316}
Ed Tanousdb024a52018-03-06 12:50:34 -0800317} // namespace TokenAuthorization
Ed Tanousba9f9a62017-10-11 16:40:35 -0700318} // namespace crow