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