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