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