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