xref: /openbmc/bmcweb/features/webui_login/login_routes.hpp (revision 3b28fa2b201513be89807dfb76b47b3ca7ee3f9b)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 #pragma once
4 
5 #include "app.hpp"
6 #include "async_resp.hpp"
7 #include "cookies.hpp"
8 #include "http_request.hpp"
9 #include "http_response.hpp"
10 #include "logging.hpp"
11 #include "multipart_parser.hpp"
12 #include "pam_authenticate.hpp"
13 #include "sessions.hpp"
14 
15 #include <security/_pam_types.h>
16 
17 #include <boost/beast/http/fields.hpp>
18 #include <boost/beast/http/status.hpp>
19 #include <boost/beast/http/verb.hpp>
20 #include <nlohmann/json.hpp>
21 
22 #include <memory>
23 #include <optional>
24 #include <string>
25 #include <string_view>
26 
27 namespace crow
28 {
29 
30 namespace login_routes
31 {
32 
handleLogin(const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)33 inline void handleLogin(const crow::Request& req,
34                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
35 {
36     MultipartParser parser;
37     std::string_view contentType = req.getHeaderValue("content-type");
38     std::string_view username;
39     std::string_view password;
40 
41     // This object needs to be declared at this scope so the strings
42     // within it are not destroyed before we can use them
43     nlohmann::json loginCredentials;
44     // Check if auth was provided by a payload
45     if (contentType.starts_with("application/json"))
46     {
47         loginCredentials = nlohmann::json::parse(req.body(), nullptr, false);
48         if (loginCredentials.is_discarded())
49         {
50             BMCWEB_LOG_DEBUG("Bad json in request");
51             asyncResp->res.result(boost::beast::http::status::bad_request);
52             return;
53         }
54 
55         // check for username/password in the root object
56         // THis method is how intel APIs authenticate
57         nlohmann::json::iterator userIt = loginCredentials.find("username");
58         nlohmann::json::iterator passIt = loginCredentials.find("password");
59         if (userIt != loginCredentials.end() &&
60             passIt != loginCredentials.end())
61         {
62             const std::string* userStr = userIt->get_ptr<const std::string*>();
63             const std::string* passStr = passIt->get_ptr<const std::string*>();
64             if (userStr != nullptr && passStr != nullptr)
65             {
66                 username = *userStr;
67                 password = *passStr;
68             }
69         }
70         else
71         {
72             // Openbmc appears to push a data object that contains the
73             // same keys (username and password), attempt to use that
74             auto dataIt = loginCredentials.find("data");
75             if (dataIt != loginCredentials.end())
76             {
77                 // Some apis produce an array of value ["username",
78                 // "password"]
79                 if (dataIt->is_array())
80                 {
81                     if (dataIt->size() == 2)
82                     {
83                         nlohmann::json::iterator userIt2 = dataIt->begin();
84                         nlohmann::json::iterator passIt2 = dataIt->begin() + 1;
85                         if (userIt2 != dataIt->end() &&
86                             passIt2 != dataIt->end())
87                         {
88                             const std::string* userStr =
89                                 userIt2->get_ptr<const std::string*>();
90                             const std::string* passStr =
91                                 passIt2->get_ptr<const std::string*>();
92                             if (userStr != nullptr && passStr != nullptr)
93                             {
94                                 username = *userStr;
95                                 password = *passStr;
96                             }
97                         }
98                     }
99                 }
100                 else
101                 {
102                     nlohmann::json::object_t* obj =
103                         dataIt->get_ptr<nlohmann::json::object_t*>();
104                     if (obj != nullptr)
105                     {
106                         nlohmann::json::object_t::iterator userIt2 =
107                             obj->find("username");
108                         nlohmann::json::object_t::iterator passIt2 =
109                             obj->find("password");
110                         if (userIt2 != obj->end() && passIt2 != obj->end())
111                         {
112                             const std::string* userStr =
113                                 userIt2->second.get_ptr<const std::string*>();
114                             const std::string* passStr =
115                                 passIt2->second.get_ptr<const std::string*>();
116                             if (userStr != nullptr && passStr != nullptr)
117                             {
118                                 username = *userStr;
119                                 password = *passStr;
120                             }
121                         }
122                     }
123                 }
124             }
125         }
126     }
127     else if (contentType.starts_with("multipart/form-data"))
128     {
129         ParserError ec = parser.parse(req);
130         if (ec != ParserError::PARSER_SUCCESS)
131         {
132             // handle error
133             BMCWEB_LOG_ERROR("MIME parse failed, ec : {}",
134                              static_cast<int>(ec));
135             asyncResp->res.result(boost::beast::http::status::bad_request);
136             return;
137         }
138 
139         for (const FormPart& formpart : parser.mime_fields)
140         {
141             boost::beast::http::fields::const_iterator it =
142                 formpart.fields.find("Content-Disposition");
143             if (it == formpart.fields.end())
144             {
145                 BMCWEB_LOG_ERROR("Couldn't find Content-Disposition");
146                 asyncResp->res.result(boost::beast::http::status::bad_request);
147                 continue;
148             }
149 
150             BMCWEB_LOG_INFO("Parsing value {}", it->value());
151 
152             if (it->value() == "form-data; name=\"username\"")
153             {
154                 username = formpart.content;
155             }
156             else if (it->value() == "form-data; name=\"password\"")
157             {
158                 password = formpart.content;
159             }
160             else
161             {
162                 BMCWEB_LOG_INFO("Extra format, ignore it.{}", it->value());
163             }
164         }
165     }
166     else
167     {
168         // check if auth was provided as a headers
169         username = req.getHeaderValue("username");
170         password = req.getHeaderValue("password");
171     }
172 
173     if (!username.empty() && !password.empty())
174     {
175         int pamrc = pamAuthenticateUser(username, password, std::nullopt);
176         bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD;
177         if ((pamrc != PAM_SUCCESS) && !isConfigureSelfOnly)
178         {
179             asyncResp->res.result(boost::beast::http::status::unauthorized);
180         }
181         else
182         {
183             auto session =
184                 persistent_data::SessionStore::getInstance()
185                     .generateUserSession(username, req.ipAddress, std::nullopt,
186                                          persistent_data::SessionType::Session,
187                                          isConfigureSelfOnly);
188 
189             bmcweb::setSessionCookies(asyncResp->res, *session);
190 
191             // if content type is json, assume json token
192             asyncResp->res.jsonValue["token"] = session->sessionToken;
193         }
194     }
195     else
196     {
197         BMCWEB_LOG_DEBUG("Couldn't interpret password");
198         asyncResp->res.result(boost::beast::http::status::bad_request);
199     }
200 }
201 
handleLogout(const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)202 inline void handleLogout(const crow::Request& req,
203                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
204 {
205     const auto& session = req.session;
206     if (session != nullptr)
207     {
208         asyncResp->res.jsonValue["data"] =
209             "User '" + session->username + "' logged out";
210         asyncResp->res.jsonValue["message"] = "200 OK";
211         asyncResp->res.jsonValue["status"] = "ok";
212 
213         bmcweb::clearSessionCookies(asyncResp->res);
214         persistent_data::SessionStore::getInstance().removeSession(session);
215     }
216 }
217 
requestRoutes(App & app)218 inline void requestRoutes(App& app)
219 {
220     BMCWEB_ROUTE(app, "/login")
221         .methods(boost::beast::http::verb::post)(handleLogin);
222 
223     BMCWEB_ROUTE(app, "/logout")
224         .methods(boost::beast::http::verb::post)(handleLogout);
225 }
226 } // namespace login_routes
227 } // namespace crow
228