1 #pragma once
2
3 #include "app.hpp"
4 #include "cookies.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
handleLogin(const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)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, std::nullopt);
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 =
165 persistent_data::SessionStore::getInstance()
166 .generateUserSession(username, req.ipAddress, std::nullopt,
167 persistent_data::SessionType::Session,
168 isConfigureSelfOnly);
169
170 bmcweb::setSessionCookies(asyncResp->res, *session);
171
172 // if content type is json, assume json token
173 asyncResp->res.jsonValue["token"] = session->sessionToken;
174 }
175 }
176 else
177 {
178 BMCWEB_LOG_DEBUG("Couldn't interpret password");
179 asyncResp->res.result(boost::beast::http::status::bad_request);
180 }
181 }
182
handleLogout(const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)183 inline void handleLogout(const crow::Request& req,
184 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
185 {
186 const auto& session = req.session;
187 if (session != nullptr)
188 {
189 asyncResp->res.jsonValue["data"] =
190 "User '" + session->username + "' logged out";
191 asyncResp->res.jsonValue["message"] = "200 OK";
192 asyncResp->res.jsonValue["status"] = "ok";
193
194 bmcweb::clearSessionCookies(asyncResp->res);
195 persistent_data::SessionStore::getInstance().removeSession(session);
196 }
197 }
198
requestRoutes(App & app)199 inline void requestRoutes(App& app)
200 {
201 BMCWEB_ROUTE(app, "/login")
202 .methods(boost::beast::http::verb::post)(handleLogin);
203
204 BMCWEB_ROUTE(app, "/logout")
205 .methods(boost::beast::http::verb::post)(handleLogout);
206 }
207 } // namespace login_routes
208 } // namespace crow
209