xref: /openbmc/bmcweb/include/login_routes.hpp (revision d78572018fc2022091ff8b8eb5a7fef2172ba3d6)
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 if (dataIt->is_object())
101                 {
102                     nlohmann::json::iterator userIt2 = dataIt->find("username");
103                     nlohmann::json::iterator passIt2 = dataIt->find("password");
104                     if (userIt2 != dataIt->end() && passIt2 != dataIt->end())
105                     {
106                         const std::string* userStr =
107                             userIt2->get_ptr<const std::string*>();
108                         const std::string* passStr =
109                             passIt2->get_ptr<const std::string*>();
110                         if (userStr != nullptr && passStr != nullptr)
111                         {
112                             username = *userStr;
113                             password = *passStr;
114                         }
115                     }
116                 }
117             }
118         }
119     }
120     else if (contentType.starts_with("multipart/form-data"))
121     {
122         ParserError ec = parser.parse(req);
123         if (ec != ParserError::PARSER_SUCCESS)
124         {
125             // handle error
126             BMCWEB_LOG_ERROR("MIME parse failed, ec : {}",
127                              static_cast<int>(ec));
128             asyncResp->res.result(boost::beast::http::status::bad_request);
129             return;
130         }
131 
132         for (const FormPart& formpart : parser.mime_fields)
133         {
134             boost::beast::http::fields::const_iterator it =
135                 formpart.fields.find("Content-Disposition");
136             if (it == formpart.fields.end())
137             {
138                 BMCWEB_LOG_ERROR("Couldn't find Content-Disposition");
139                 asyncResp->res.result(boost::beast::http::status::bad_request);
140                 continue;
141             }
142 
143             BMCWEB_LOG_INFO("Parsing value {}", it->value());
144 
145             if (it->value() == "form-data; name=\"username\"")
146             {
147                 username = formpart.content;
148             }
149             else if (it->value() == "form-data; name=\"password\"")
150             {
151                 password = formpart.content;
152             }
153             else
154             {
155                 BMCWEB_LOG_INFO("Extra format, ignore it.{}", it->value());
156             }
157         }
158     }
159     else
160     {
161         // check if auth was provided as a headers
162         username = req.getHeaderValue("username");
163         password = req.getHeaderValue("password");
164     }
165 
166     if (!username.empty() && !password.empty())
167     {
168         int pamrc = pamAuthenticateUser(username, password, std::nullopt);
169         bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD;
170         if ((pamrc != PAM_SUCCESS) && !isConfigureSelfOnly)
171         {
172             asyncResp->res.result(boost::beast::http::status::unauthorized);
173         }
174         else
175         {
176             auto session =
177                 persistent_data::SessionStore::getInstance()
178                     .generateUserSession(username, req.ipAddress, std::nullopt,
179                                          persistent_data::SessionType::Session,
180                                          isConfigureSelfOnly);
181 
182             bmcweb::setSessionCookies(asyncResp->res, *session);
183 
184             // if content type is json, assume json token
185             asyncResp->res.jsonValue["token"] = session->sessionToken;
186         }
187     }
188     else
189     {
190         BMCWEB_LOG_DEBUG("Couldn't interpret password");
191         asyncResp->res.result(boost::beast::http::status::bad_request);
192     }
193 }
194 
handleLogout(const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)195 inline void handleLogout(const crow::Request& req,
196                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
197 {
198     const auto& session = req.session;
199     if (session != nullptr)
200     {
201         asyncResp->res.jsonValue["data"] =
202             "User '" + session->username + "' logged out";
203         asyncResp->res.jsonValue["message"] = "200 OK";
204         asyncResp->res.jsonValue["status"] = "ok";
205 
206         bmcweb::clearSessionCookies(asyncResp->res);
207         persistent_data::SessionStore::getInstance().removeSession(session);
208     }
209 }
210 
requestRoutes(App & app)211 inline void requestRoutes(App& app)
212 {
213     BMCWEB_ROUTE(app, "/login")
214         .methods(boost::beast::http::verb::post)(handleLogin);
215 
216     BMCWEB_ROUTE(app, "/logout")
217         .methods(boost::beast::http::verb::post)(handleLogout);
218 }
219 } // namespace login_routes
220 } // namespace crow
221