140e9b92eSEd Tanous // SPDX-License-Identifier: Apache-2.0
240e9b92eSEd Tanous // SPDX-FileCopyrightText: Copyright OpenBMC Authors
33909dc82SJames Feist #pragma once
43909dc82SJames Feist
53ccb3adbSEd Tanous #include "app.hpp"
6*d7857201SEd Tanous #include "async_resp.hpp"
729aab242SPaul Fertser #include "cookies.hpp"
83ccb3adbSEd Tanous #include "http_request.hpp"
93ccb3adbSEd Tanous #include "http_response.hpp"
10*d7857201SEd Tanous #include "logging.hpp"
11af4edf68SEd Tanous #include "multipart_parser.hpp"
123ccb3adbSEd Tanous #include "pam_authenticate.hpp"
13*d7857201SEd Tanous #include "sessions.hpp"
14af4edf68SEd Tanous
15*d7857201SEd Tanous #include <security/_pam_types.h>
163909dc82SJames Feist
17*d7857201SEd Tanous #include <boost/beast/http/fields.hpp>
18*d7857201SEd Tanous #include <boost/beast/http/status.hpp>
19*d7857201SEd Tanous #include <boost/beast/http/verb.hpp>
20*d7857201SEd Tanous #include <nlohmann/json.hpp>
21*d7857201SEd Tanous
22*d7857201SEd Tanous #include <memory>
23*d7857201SEd Tanous #include <optional>
24*d7857201SEd Tanous #include <string>
25*d7857201SEd Tanous #include <string_view>
263909dc82SJames Feist
273909dc82SJames Feist namespace crow
283909dc82SJames Feist {
293909dc82SJames Feist
303909dc82SJames Feist namespace login_routes
313909dc82SJames Feist {
323909dc82SJames Feist
handleLogin(const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)3346228e0eSEd Tanous inline void handleLogin(const crow::Request& req,
3446228e0eSEd Tanous const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
353909dc82SJames Feist {
3660719639SKrzysztof Grobelny MultipartParser parser;
373909dc82SJames Feist std::string_view contentType = req.getHeaderValue("content-type");
383909dc82SJames Feist std::string_view username;
393909dc82SJames Feist std::string_view password;
403909dc82SJames Feist
413909dc82SJames Feist // This object needs to be declared at this scope so the strings
423909dc82SJames Feist // within it are not destroyed before we can use them
433909dc82SJames Feist nlohmann::json loginCredentials;
443909dc82SJames Feist // Check if auth was provided by a payload
4511ba3979SEd Tanous if (contentType.starts_with("application/json"))
463909dc82SJames Feist {
4746228e0eSEd Tanous loginCredentials = nlohmann::json::parse(req.body(), nullptr, false);
483909dc82SJames Feist if (loginCredentials.is_discarded())
493909dc82SJames Feist {
5062598e31SEd Tanous BMCWEB_LOG_DEBUG("Bad json in request");
51002d39b4SEd Tanous asyncResp->res.result(boost::beast::http::status::bad_request);
523909dc82SJames Feist return;
533909dc82SJames Feist }
543909dc82SJames Feist
553909dc82SJames Feist // check for username/password in the root object
563909dc82SJames Feist // THis method is how intel APIs authenticate
57002d39b4SEd Tanous nlohmann::json::iterator userIt = loginCredentials.find("username");
58002d39b4SEd Tanous nlohmann::json::iterator passIt = loginCredentials.find("password");
593909dc82SJames Feist if (userIt != loginCredentials.end() &&
603909dc82SJames Feist passIt != loginCredentials.end())
613909dc82SJames Feist {
6246228e0eSEd Tanous const std::string* userStr = userIt->get_ptr<const std::string*>();
6346228e0eSEd Tanous const std::string* passStr = passIt->get_ptr<const std::string*>();
643909dc82SJames Feist if (userStr != nullptr && passStr != nullptr)
653909dc82SJames Feist {
663909dc82SJames Feist username = *userStr;
673909dc82SJames Feist password = *passStr;
683909dc82SJames Feist }
693909dc82SJames Feist }
703909dc82SJames Feist else
713909dc82SJames Feist {
723909dc82SJames Feist // Openbmc appears to push a data object that contains the
733909dc82SJames Feist // same keys (username and password), attempt to use that
743909dc82SJames Feist auto dataIt = loginCredentials.find("data");
753909dc82SJames Feist if (dataIt != loginCredentials.end())
763909dc82SJames Feist {
773909dc82SJames Feist // Some apis produce an array of value ["username",
783909dc82SJames Feist // "password"]
793909dc82SJames Feist if (dataIt->is_array())
803909dc82SJames Feist {
813909dc82SJames Feist if (dataIt->size() == 2)
823909dc82SJames Feist {
83002d39b4SEd Tanous nlohmann::json::iterator userIt2 = dataIt->begin();
8446228e0eSEd Tanous nlohmann::json::iterator passIt2 = dataIt->begin() + 1;
853909dc82SJames Feist if (userIt2 != dataIt->end() &&
863909dc82SJames Feist passIt2 != dataIt->end())
873909dc82SJames Feist {
883909dc82SJames Feist const std::string* userStr =
893909dc82SJames Feist userIt2->get_ptr<const std::string*>();
903909dc82SJames Feist const std::string* passStr =
913909dc82SJames Feist passIt2->get_ptr<const std::string*>();
92002d39b4SEd Tanous if (userStr != nullptr && passStr != nullptr)
933909dc82SJames Feist {
943909dc82SJames Feist username = *userStr;
953909dc82SJames Feist password = *passStr;
963909dc82SJames Feist }
973909dc82SJames Feist }
983909dc82SJames Feist }
993909dc82SJames Feist }
1003909dc82SJames Feist else if (dataIt->is_object())
1013909dc82SJames Feist {
10246228e0eSEd Tanous nlohmann::json::iterator userIt2 = dataIt->find("username");
10346228e0eSEd Tanous nlohmann::json::iterator passIt2 = dataIt->find("password");
10446228e0eSEd Tanous if (userIt2 != dataIt->end() && passIt2 != dataIt->end())
1053909dc82SJames Feist {
1063909dc82SJames Feist const std::string* userStr =
1073909dc82SJames Feist userIt2->get_ptr<const std::string*>();
1083909dc82SJames Feist const std::string* passStr =
1093909dc82SJames Feist passIt2->get_ptr<const std::string*>();
1103909dc82SJames Feist if (userStr != nullptr && passStr != nullptr)
1113909dc82SJames Feist {
1123909dc82SJames Feist username = *userStr;
1133909dc82SJames Feist password = *passStr;
1143909dc82SJames Feist }
1153909dc82SJames Feist }
1163909dc82SJames Feist }
1173909dc82SJames Feist }
1183909dc82SJames Feist }
1193909dc82SJames Feist }
12011ba3979SEd Tanous else if (contentType.starts_with("multipart/form-data"))
121af4edf68SEd Tanous {
122af4edf68SEd Tanous ParserError ec = parser.parse(req);
123af4edf68SEd Tanous if (ec != ParserError::PARSER_SUCCESS)
124af4edf68SEd Tanous {
125af4edf68SEd Tanous // handle error
12662598e31SEd Tanous BMCWEB_LOG_ERROR("MIME parse failed, ec : {}",
12762598e31SEd Tanous static_cast<int>(ec));
128002d39b4SEd Tanous asyncResp->res.result(boost::beast::http::status::bad_request);
129af4edf68SEd Tanous return;
130af4edf68SEd Tanous }
131af4edf68SEd Tanous
132af4edf68SEd Tanous for (const FormPart& formpart : parser.mime_fields)
133af4edf68SEd Tanous {
134af4edf68SEd Tanous boost::beast::http::fields::const_iterator it =
135af4edf68SEd Tanous formpart.fields.find("Content-Disposition");
136af4edf68SEd Tanous if (it == formpart.fields.end())
137af4edf68SEd Tanous {
13862598e31SEd Tanous BMCWEB_LOG_ERROR("Couldn't find Content-Disposition");
13946228e0eSEd Tanous asyncResp->res.result(boost::beast::http::status::bad_request);
140af4edf68SEd Tanous continue;
141af4edf68SEd Tanous }
142af4edf68SEd Tanous
14362598e31SEd Tanous BMCWEB_LOG_INFO("Parsing value {}", it->value());
144af4edf68SEd Tanous
145af4edf68SEd Tanous if (it->value() == "form-data; name=\"username\"")
146af4edf68SEd Tanous {
147af4edf68SEd Tanous username = formpart.content;
148af4edf68SEd Tanous }
149af4edf68SEd Tanous else if (it->value() == "form-data; name=\"password\"")
150af4edf68SEd Tanous {
151af4edf68SEd Tanous password = formpart.content;
152af4edf68SEd Tanous }
153af4edf68SEd Tanous else
154af4edf68SEd Tanous {
15562598e31SEd Tanous BMCWEB_LOG_INFO("Extra format, ignore it.{}", it->value());
156af4edf68SEd Tanous }
157af4edf68SEd Tanous }
158af4edf68SEd Tanous }
1593909dc82SJames Feist else
1603909dc82SJames Feist {
1613909dc82SJames Feist // check if auth was provided as a headers
1623909dc82SJames Feist username = req.getHeaderValue("username");
1633909dc82SJames Feist password = req.getHeaderValue("password");
1643909dc82SJames Feist }
1653909dc82SJames Feist
166e10f0176SEd Tanous if (!username.empty() && !password.empty())
167e10f0176SEd Tanous {
1682ccce1f3SRavi Teja int pamrc = pamAuthenticateUser(username, password, std::nullopt);
169e10f0176SEd Tanous bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD;
170e10f0176SEd Tanous if ((pamrc != PAM_SUCCESS) && !isConfigureSelfOnly)
171e10f0176SEd Tanous {
172e10f0176SEd Tanous asyncResp->res.result(boost::beast::http::status::unauthorized);
173e10f0176SEd Tanous }
174e10f0176SEd Tanous else
175e10f0176SEd Tanous {
17689cda63dSEd Tanous auto session =
17789cda63dSEd Tanous persistent_data::SessionStore::getInstance()
17889cda63dSEd Tanous .generateUserSession(username, req.ipAddress, std::nullopt,
17989cda63dSEd Tanous persistent_data::SessionType::Session,
180e10f0176SEd Tanous isConfigureSelfOnly);
181e10f0176SEd Tanous
18229aab242SPaul Fertser bmcweb::setSessionCookies(asyncResp->res, *session);
183e10f0176SEd Tanous
184e10f0176SEd Tanous // if content type is json, assume json token
185e10f0176SEd Tanous asyncResp->res.jsonValue["token"] = session->sessionToken;
186e10f0176SEd Tanous }
187e10f0176SEd Tanous }
188e10f0176SEd Tanous else
1893909dc82SJames Feist {
19062598e31SEd Tanous BMCWEB_LOG_DEBUG("Couldn't interpret password");
1918d1b46d7Szhanghch05 asyncResp->res.result(boost::beast::http::status::bad_request);
1923909dc82SJames Feist }
19346228e0eSEd Tanous }
1943909dc82SJames Feist
handleLogout(const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)19546228e0eSEd Tanous inline void handleLogout(const crow::Request& req,
19646228e0eSEd Tanous const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
19746228e0eSEd Tanous {
19855f79e6fSEd Tanous const auto& session = req.session;
1993909dc82SJames Feist if (session != nullptr)
2003909dc82SJames Feist {
201bd79bce8SPatrick Williams asyncResp->res.jsonValue["data"] =
202bd79bce8SPatrick Williams "User '" + session->username + "' logged out";
2031476687dSEd Tanous asyncResp->res.jsonValue["message"] = "200 OK";
2041476687dSEd Tanous asyncResp->res.jsonValue["status"] = "ok";
2053909dc82SJames Feist
20629aab242SPaul Fertser bmcweb::clearSessionCookies(asyncResp->res);
207002d39b4SEd Tanous persistent_data::SessionStore::getInstance().removeSession(session);
2083909dc82SJames Feist }
20946228e0eSEd Tanous }
21046228e0eSEd Tanous
requestRoutes(App & app)21146228e0eSEd Tanous inline void requestRoutes(App& app)
21246228e0eSEd Tanous {
21346228e0eSEd Tanous BMCWEB_ROUTE(app, "/login")
21446228e0eSEd Tanous .methods(boost::beast::http::verb::post)(handleLogin);
21546228e0eSEd Tanous
21646228e0eSEd Tanous BMCWEB_ROUTE(app, "/logout")
21746228e0eSEd Tanous .methods(boost::beast::http::verb::post)(handleLogout);
2183909dc82SJames Feist }
2193909dc82SJames Feist } // namespace login_routes
2203909dc82SJames Feist } // namespace crow
221