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 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); 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 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"] = "User '" + session->username + 190 "' 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 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