xref: /openbmc/bmcweb/include/login_routes.hpp (revision d78572018fc2022091ff8b8eb5a7fef2172ba3d6)
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