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