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