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.to_string(), 147 unsupportedClientId, 148 persistent_data::PersistenceType::TIMEOUT, 149 isConfigureSelfOnly); 150 151 if (looksLikePhosphorRest) 152 { 153 // Phosphor-Rest requires a very specific login 154 // structure, and doesn't actually look at the status 155 // code. 156 // TODO(ed).... Fix that upstream 157 asyncResp->res.jsonValue = { 158 {"data", 159 "User '" + std::string(username) + "' logged in"}, 160 {"message", "200 OK"}, 161 {"status", "ok"}}; 162 163 // Hack alert. Boost beast by default doesn't let you 164 // declare multiple headers of the same name, and in 165 // most cases this is fine. Unfortunately here we need 166 // to set the Session cookie, which requires the 167 // httpOnly attribute, as well as the XSRF cookie, which 168 // requires it to not have an httpOnly attribute. To get 169 // the behavior we want, we simply inject the second 170 // "set-cookie" string into the value header, and get 171 // the result we want, even though we are technicaly 172 // declaring two headers here. 173 asyncResp->res.addHeader( 174 "Set-Cookie", 175 "XSRF-TOKEN=" + session->csrfToken + 176 "; SameSite=Strict; Secure\r\nSet-Cookie: " 177 "SESSION=" + 178 session->sessionToken + 179 "; SameSite=Strict; Secure; HttpOnly"); 180 } 181 else 182 { 183 // if content type is json, assume json token 184 asyncResp->res.jsonValue = { 185 {"token", session->sessionToken}}; 186 } 187 } 188 } 189 else 190 { 191 BMCWEB_LOG_DEBUG << "Couldn't interpret password"; 192 asyncResp->res.result(boost::beast::http::status::bad_request); 193 } 194 }); 195 196 BMCWEB_ROUTE(app, "/logout") 197 .methods(boost::beast::http::verb::post)( 198 [](const crow::Request& req, 199 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 200 auto& session = req.session; 201 if (session != nullptr) 202 { 203 asyncResp->res.jsonValue = { 204 {"data", "User '" + session->username + "' logged out"}, 205 {"message", "200 OK"}, 206 {"status", "ok"}}; 207 208 persistent_data::SessionStore::getInstance().removeSession( 209 session); 210 } 211 return; 212 }); 213 } 214 } // namespace login_routes 215 } // namespace crow 216