xref: /openbmc/bmcweb/include/login_routes.hpp (revision e9cc1bc9)
1 #pragma once
2 
3 #include "app.hpp"
4 #include "common.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 = persistent_data::SessionStore::getInstance()
165                                .generateUserSession(
166                                    username, req.ipAddress, std::nullopt,
167                                    persistent_data::PersistenceType::TIMEOUT,
168                                    isConfigureSelfOnly);
169 
170             asyncResp->res.addHeader(boost::beast::http::field::set_cookie,
171                                      "XSRF-TOKEN=" + session->csrfToken +
172                                          "; SameSite=Strict; Secure");
173             asyncResp->res.addHeader(boost::beast::http::field::set_cookie,
174                                      "SESSION=" + session->sessionToken +
175                                          "; SameSite=Strict; Secure; HttpOnly");
176 
177             // if content type is json, assume json token
178             asyncResp->res.jsonValue["token"] = session->sessionToken;
179         }
180     }
181     else
182     {
183         BMCWEB_LOG_DEBUG("Couldn't interpret password");
184         asyncResp->res.result(boost::beast::http::status::bad_request);
185     }
186 }
187 
188 inline void handleLogout(const crow::Request& req,
189                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
190 {
191     const auto& session = req.session;
192     if (session != nullptr)
193     {
194         asyncResp->res.jsonValue["data"] = "User '" + session->username +
195                                            "' logged out";
196         asyncResp->res.jsonValue["message"] = "200 OK";
197         asyncResp->res.jsonValue["status"] = "ok";
198 
199         asyncResp->res.addHeader("Set-Cookie",
200                                  "SESSION="
201                                  "; SameSite=Strict; Secure; HttpOnly; "
202                                  "expires=Thu, 01 Jan 1970 00:00:00 GMT");
203         asyncResp->res.addHeader("Clear-Site-Data",
204                                  R"("cache","cookies","storage")");
205         persistent_data::SessionStore::getInstance().removeSession(session);
206     }
207 }
208 
209 inline void requestRoutes(App& app)
210 {
211     BMCWEB_ROUTE(app, "/login")
212         .methods(boost::beast::http::verb::post)(handleLogin);
213 
214     BMCWEB_ROUTE(app, "/logout")
215         .methods(boost::beast::http::verb::post)(handleLogout);
216 }
217 } // namespace login_routes
218 } // namespace crow
219