1 /* 2 // Copyright (c) 2018 Intel Corporation 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 */ 16 #pragma once 17 18 #include "error_messages.hpp" 19 #include "node.hpp" 20 #include "persistent_data_middleware.hpp" 21 22 namespace redfish 23 { 24 25 class SessionCollection; 26 27 class Sessions : public Node 28 { 29 public: 30 Sessions(CrowApp& app) : 31 Node(app, "/redfish/v1/SessionService/Sessions/<str>/", std::string()) 32 { 33 entityPrivileges = { 34 {boost::beast::http::verb::get, {{"Login"}}}, 35 {boost::beast::http::verb::head, {{"Login"}}}, 36 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 37 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 38 {boost::beast::http::verb::delete_, 39 {{"ConfigureManager"}, {"ConfigureSelf"}}}, 40 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 41 } 42 43 private: 44 void doGet(crow::Response& res, const crow::Request& req, 45 const std::vector<std::string>& params) override 46 { 47 // Note that control also reaches here via doPost and doDelete. 48 auto session = 49 crow::persistent_data::SessionStore::getInstance().getSessionByUid( 50 params[0]); 51 52 if (session == nullptr) 53 { 54 messages::resourceNotFound(res, "Session", params[0]); 55 res.end(); 56 return; 57 } 58 59 res.jsonValue["Id"] = session->uniqueId; 60 res.jsonValue["UserName"] = session->username; 61 res.jsonValue["@odata.id"] = 62 "/redfish/v1/SessionService/Sessions/" + session->uniqueId; 63 res.jsonValue["@odata.type"] = "#Session.v1_0_2.Session"; 64 res.jsonValue["Name"] = "User Session"; 65 res.jsonValue["Description"] = "Manager User Session"; 66 res.jsonValue["Oem"]["OpenBMC"]["@odata.type"] = 67 "#OemSession.v1_0_0.Session"; 68 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE 69 res.jsonValue["Oem"]["OpenBMC"]["ClientID"] = session->clientId; 70 #endif 71 res.jsonValue["Oem"]["OpenBMC"]["ClientOriginIP"] = session->clientIp; 72 res.end(); 73 } 74 75 void doDelete(crow::Response& res, const crow::Request& req, 76 const std::vector<std::string>& params) override 77 { 78 // Need only 1 param which should be id of session to be deleted 79 if (params.size() != 1) 80 { 81 // This should be handled by crow and never happen 82 BMCWEB_LOG_ERROR << "Session DELETE has been called with invalid " 83 "number of params"; 84 85 messages::generalError(res); 86 res.end(); 87 return; 88 } 89 90 auto session = 91 crow::persistent_data::SessionStore::getInstance().getSessionByUid( 92 params[0]); 93 94 if (session == nullptr) 95 { 96 messages::resourceNotFound(res, "Session", params[0]); 97 res.end(); 98 return; 99 } 100 101 // Perform a proper ConfigureSelf authority check. If a 102 // session is being used to DELETE some other user's session, 103 // then the ConfigureSelf privilege does not apply. In that 104 // case, perform the authority check again without the user's 105 // ConfigureSelf privilege. 106 if (session->username != req.session->username) 107 { 108 if (!isAllowedWithoutConfigureSelf(req)) 109 { 110 BMCWEB_LOG_WARNING << "DELETE Session denied access"; 111 messages::insufficientPrivilege(res); 112 res.end(); 113 return; 114 } 115 } 116 117 // DELETE should return representation of object that will be removed 118 doGet(res, req, params); 119 120 crow::persistent_data::SessionStore::getInstance().removeSession( 121 session); 122 } 123 124 /** 125 * This allows SessionCollection to reuse this class' doGet method, to 126 * maintain consistency of returned data, as Collection's doPost should 127 * return data for created member which should match member's doGet 128 * result in 100% 129 */ 130 friend SessionCollection; 131 }; 132 133 class SessionCollection : public Node 134 { 135 public: 136 SessionCollection(CrowApp& app) : 137 Node(app, "/redfish/v1/SessionService/Sessions/"), memberSession(app) 138 { 139 entityPrivileges = { 140 {boost::beast::http::verb::get, {{"Login"}}}, 141 {boost::beast::http::verb::head, {{"Login"}}}, 142 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 143 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 144 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 145 {boost::beast::http::verb::post, {}}}; 146 } 147 148 private: 149 void doGet(crow::Response& res, const crow::Request& req, 150 const std::vector<std::string>& params) override 151 { 152 std::vector<const std::string*> sessionIds = 153 crow::persistent_data::SessionStore::getInstance().getUniqueIds( 154 false, crow::persistent_data::PersistenceType::TIMEOUT); 155 156 res.jsonValue["Members@odata.count"] = sessionIds.size(); 157 res.jsonValue["Members"] = nlohmann::json::array(); 158 for (const std::string* uid : sessionIds) 159 { 160 res.jsonValue["Members"].push_back( 161 {{"@odata.id", "/redfish/v1/SessionService/Sessions/" + *uid}}); 162 } 163 res.jsonValue["Members@odata.count"] = sessionIds.size(); 164 res.jsonValue["@odata.type"] = "#SessionCollection.SessionCollection"; 165 res.jsonValue["@odata.id"] = "/redfish/v1/SessionService/Sessions/"; 166 res.jsonValue["Name"] = "Session Collection"; 167 res.jsonValue["Description"] = "Session Collection"; 168 res.end(); 169 } 170 171 void doPost(crow::Response& res, const crow::Request& req, 172 const std::vector<std::string>& params) override 173 { 174 std::string username; 175 std::string password; 176 std::optional<nlohmann::json> oemObject; 177 std::string clientId; 178 std::string clientIp; 179 if (!json_util::readJson(req, res, "UserName", username, "Password", 180 password, "Oem", oemObject)) 181 { 182 res.end(); 183 return; 184 } 185 186 if (password.empty() || username.empty() || 187 res.result() != boost::beast::http::status::ok) 188 { 189 if (username.empty()) 190 { 191 messages::propertyMissing(res, "UserName"); 192 } 193 194 if (password.empty()) 195 { 196 messages::propertyMissing(res, "Password"); 197 } 198 res.end(); 199 200 return; 201 } 202 203 int pamrc = pamAuthenticateUser(username, password); 204 bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD; 205 if ((pamrc != PAM_SUCCESS) && !isConfigureSelfOnly) 206 { 207 messages::resourceAtUriUnauthorized(res, std::string(req.url), 208 "Invalid username or password"); 209 res.end(); 210 211 return; 212 } 213 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE 214 if (oemObject) 215 { 216 std::optional<nlohmann::json> bmcOem; 217 if (!json_util::readJson(*oemObject, res, "OpenBMC", bmcOem)) 218 { 219 res.end(); 220 return; 221 } 222 if (!json_util::readJson(*bmcOem, res, "ClientID", clientId)) 223 { 224 BMCWEB_LOG_ERROR << "Could not read ClientId"; 225 res.end(); 226 return; 227 } 228 } 229 #endif 230 231 #ifdef BMCWEB_ENABLE_SSL 232 clientIp = 233 req.socket().next_layer().remote_endpoint().address().to_string(); 234 #else 235 clientIp = req.socket().remote_endpoint().address().to_string(); 236 #endif 237 238 // User is authenticated - create session 239 std::shared_ptr<crow::persistent_data::UserSession> session = 240 crow::persistent_data::SessionStore::getInstance() 241 .generateUserSession( 242 username, crow::persistent_data::PersistenceType::TIMEOUT, 243 isConfigureSelfOnly, clientId, clientIp); 244 res.addHeader("X-Auth-Token", session->sessionToken); 245 res.addHeader("Location", "/redfish/v1/SessionService/Sessions/" + 246 session->uniqueId); 247 res.result(boost::beast::http::status::created); 248 if (session->isConfigureSelfOnly) 249 { 250 messages::passwordChangeRequired( 251 res, 252 "/redfish/v1/AccountService/Accounts/" + session->username); 253 } 254 memberSession.doGet(res, req, {session->uniqueId}); 255 } 256 257 /** 258 * Member session to ensure consistency between collection's doPost and 259 * member's doGet, as they should return 100% matching data 260 */ 261 Sessions memberSession; 262 }; 263 264 class SessionService : public Node 265 { 266 public: 267 SessionService(CrowApp& app) : Node(app, "/redfish/v1/SessionService/") 268 { 269 270 entityPrivileges = { 271 {boost::beast::http::verb::get, {{"Login"}}}, 272 {boost::beast::http::verb::head, {{"Login"}}}, 273 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 274 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 275 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 276 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 277 } 278 279 private: 280 void doGet(crow::Response& res, const crow::Request& req, 281 const std::vector<std::string>& params) override 282 { 283 res.jsonValue["@odata.type"] = "#SessionService.v1_0_2.SessionService"; 284 res.jsonValue["@odata.id"] = "/redfish/v1/SessionService/"; 285 res.jsonValue["Name"] = "Session Service"; 286 res.jsonValue["Id"] = "SessionService"; 287 res.jsonValue["Description"] = "Session Service"; 288 res.jsonValue["SessionTimeout"] = 289 crow::persistent_data::SessionStore::getInstance() 290 .getTimeoutInSeconds(); 291 res.jsonValue["ServiceEnabled"] = true; 292 293 res.jsonValue["Sessions"] = { 294 {"@odata.id", "/redfish/v1/SessionService/Sessions"}}; 295 296 res.end(); 297 } 298 }; 299 300 } // namespace redfish 301