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