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