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