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