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 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 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 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