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