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