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