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.hpp" 21 22 #include <app.hpp> 23 24 namespace redfish 25 { 26 27 class SessionCollection; 28 29 class Sessions : public Node 30 { 31 public: 32 Sessions(App& app) : 33 Node(app, "/redfish/v1/SessionService/Sessions/<str>/", std::string()) 34 { 35 entityPrivileges = { 36 {boost::beast::http::verb::get, {{"Login"}}}, 37 {boost::beast::http::verb::head, {{"Login"}}}, 38 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 39 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 40 {boost::beast::http::verb::delete_, 41 {{"ConfigureManager"}, {"ConfigureSelf"}}}, 42 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 43 } 44 45 private: 46 void doGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 47 const crow::Request&, 48 const std::vector<std::string>& params) override 49 { 50 // Note that control also reaches here via doPost and doDelete. 51 auto session = 52 persistent_data::SessionStore::getInstance().getSessionByUid( 53 params[0]); 54 55 if (session == nullptr) 56 { 57 messages::resourceNotFound(asyncResp->res, "Session", params[0]); 58 return; 59 } 60 61 asyncResp->res.jsonValue["Id"] = session->uniqueId; 62 asyncResp->res.jsonValue["UserName"] = session->username; 63 asyncResp->res.jsonValue["@odata.id"] = 64 "/redfish/v1/SessionService/Sessions/" + session->uniqueId; 65 asyncResp->res.jsonValue["@odata.type"] = "#Session.v1_3_0.Session"; 66 asyncResp->res.jsonValue["Name"] = "User Session"; 67 asyncResp->res.jsonValue["Description"] = "Manager User Session"; 68 asyncResp->res.jsonValue["ClientOriginIPAddress"] = session->clientIp; 69 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE 70 asyncResp->res.jsonValue["Oem"]["OpenBMC"]["@odata.type"] = 71 "#OemSession.v1_0_0.Session"; 72 asyncResp->res.jsonValue["Oem"]["OpenBMC"]["ClientID"] = 73 session->clientId; 74 #endif 75 } 76 77 void doDelete(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 78 const crow::Request& req, 79 const std::vector<std::string>& params) override 80 { 81 // Need only 1 param which should be id of session to be deleted 82 if (params.size() != 1) 83 { 84 // This should be handled by crow and never happen 85 BMCWEB_LOG_ERROR << "Session DELETE has been called with invalid " 86 "number of params"; 87 88 messages::generalError(asyncResp->res); 89 return; 90 } 91 92 auto session = 93 persistent_data::SessionStore::getInstance().getSessionByUid( 94 params[0]); 95 96 if (session == nullptr) 97 { 98 messages::resourceNotFound(asyncResp->res, "Session", params[0]); 99 return; 100 } 101 102 // Perform a proper ConfigureSelf authority check. If a 103 // session is being used to DELETE some other user's session, 104 // then the ConfigureSelf privilege does not apply. In that 105 // case, perform the authority check again without the user's 106 // ConfigureSelf privilege. 107 if (session->username != req.session->username) 108 { 109 if (!isAllowedWithoutConfigureSelf(req)) 110 { 111 BMCWEB_LOG_WARNING << "DELETE Session denied access"; 112 messages::insufficientPrivilege(asyncResp->res); 113 return; 114 } 115 } 116 117 // DELETE should return representation of object that will be removed 118 doGet(asyncResp, req, params); 119 120 persistent_data::SessionStore::getInstance().removeSession(session); 121 } 122 123 /** 124 * This allows SessionCollection to reuse this class' doGet method, to 125 * maintain consistency of returned data, as Collection's doPost should 126 * return data for created member which should match member's doGet 127 * result in 100% 128 */ 129 friend SessionCollection; 130 }; 131 132 class SessionCollection : public Node 133 { 134 public: 135 SessionCollection(App& app) : 136 Node(app, "/redfish/v1/SessionService/Sessions/"), memberSession(app) 137 { 138 entityPrivileges = { 139 {boost::beast::http::verb::get, {{"Login"}}}, 140 {boost::beast::http::verb::head, {{"Login"}}}, 141 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 142 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 143 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 144 {boost::beast::http::verb::post, {}}}; 145 } 146 147 private: 148 void doGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 149 const crow::Request&, const std::vector<std::string>&) override 150 { 151 std::vector<const std::string*> sessionIds = 152 persistent_data::SessionStore::getInstance().getUniqueIds( 153 false, persistent_data::PersistenceType::TIMEOUT); 154 155 asyncResp->res.jsonValue["Members@odata.count"] = sessionIds.size(); 156 asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); 157 for (const std::string* uid : sessionIds) 158 { 159 asyncResp->res.jsonValue["Members"].push_back( 160 {{"@odata.id", "/redfish/v1/SessionService/Sessions/" + *uid}}); 161 } 162 asyncResp->res.jsonValue["Members@odata.count"] = sessionIds.size(); 163 asyncResp->res.jsonValue["@odata.type"] = 164 "#SessionCollection.SessionCollection"; 165 asyncResp->res.jsonValue["@odata.id"] = 166 "/redfish/v1/SessionService/Sessions/"; 167 asyncResp->res.jsonValue["Name"] = "Session Collection"; 168 asyncResp->res.jsonValue["Description"] = "Session Collection"; 169 } 170 171 void doPost(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 172 const crow::Request& req, 173 const std::vector<std::string>&) override 174 { 175 std::string username; 176 std::string password; 177 std::optional<nlohmann::json> oemObject; 178 std::string clientId; 179 if (!json_util::readJson(req, asyncResp->res, "UserName", username, 180 "Password", password, "Oem", oemObject)) 181 { 182 return; 183 } 184 185 if (password.empty() || username.empty() || 186 asyncResp->res.result() != boost::beast::http::status::ok) 187 { 188 if (username.empty()) 189 { 190 messages::propertyMissing(asyncResp->res, "UserName"); 191 } 192 193 if (password.empty()) 194 { 195 messages::propertyMissing(asyncResp->res, "Password"); 196 } 197 198 return; 199 } 200 201 int pamrc = pamAuthenticateUser(username, password); 202 bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD; 203 if ((pamrc != PAM_SUCCESS) && !isConfigureSelfOnly) 204 { 205 messages::resourceAtUriUnauthorized(asyncResp->res, 206 std::string(req.url), 207 "Invalid username or password"); 208 return; 209 } 210 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE 211 if (oemObject) 212 { 213 std::optional<nlohmann::json> bmcOem; 214 if (!json_util::readJson(*oemObject, asyncResp->res, "OpenBMC", 215 bmcOem)) 216 { 217 return; 218 } 219 if (!json_util::readJson(*bmcOem, asyncResp->res, "ClientID", 220 clientId)) 221 { 222 BMCWEB_LOG_ERROR << "Could not read ClientId"; 223 return; 224 } 225 } 226 #endif 227 228 // User is authenticated - create session 229 std::shared_ptr<persistent_data::UserSession> session = 230 persistent_data::SessionStore::getInstance().generateUserSession( 231 username, req.ipAddress.to_string(), clientId, 232 persistent_data::PersistenceType::TIMEOUT, isConfigureSelfOnly); 233 asyncResp->res.addHeader("X-Auth-Token", session->sessionToken); 234 asyncResp->res.addHeader("Location", 235 "/redfish/v1/SessionService/Sessions/" + 236 session->uniqueId); 237 asyncResp->res.result(boost::beast::http::status::created); 238 if (session->isConfigureSelfOnly) 239 { 240 messages::passwordChangeRequired( 241 asyncResp->res, 242 "/redfish/v1/AccountService/Accounts/" + session->username); 243 } 244 memberSession.doGet(asyncResp, req, {session->uniqueId}); 245 } 246 247 /** 248 * Member session to ensure consistency between collection's doPost and 249 * member's doGet, as they should return 100% matching data 250 */ 251 Sessions memberSession; 252 }; 253 254 class SessionService : public Node 255 { 256 public: 257 SessionService(App& app) : Node(app, "/redfish/v1/SessionService/") 258 { 259 260 entityPrivileges = { 261 {boost::beast::http::verb::get, {{"Login"}}}, 262 {boost::beast::http::verb::head, {{"Login"}}}, 263 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 264 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 265 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 266 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 267 } 268 269 private: 270 void doGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 271 const crow::Request&, const std::vector<std::string>&) override 272 { 273 asyncResp->res.jsonValue["@odata.type"] = 274 "#SessionService.v1_0_2.SessionService"; 275 asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/SessionService/"; 276 asyncResp->res.jsonValue["Name"] = "Session Service"; 277 asyncResp->res.jsonValue["Id"] = "SessionService"; 278 asyncResp->res.jsonValue["Description"] = "Session Service"; 279 asyncResp->res.jsonValue["SessionTimeout"] = 280 persistent_data::SessionStore::getInstance().getTimeoutInSeconds(); 281 asyncResp->res.jsonValue["ServiceEnabled"] = true; 282 283 asyncResp->res.jsonValue["Sessions"] = { 284 {"@odata.id", "/redfish/v1/SessionService/Sessions"}}; 285 } 286 287 void doPatch(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 288 const crow::Request& req, 289 const std::vector<std::string>&) override 290 { 291 std::optional<int64_t> sessionTimeout; 292 if (!json_util::readJson(req, asyncResp->res, "SessionTimeout", 293 sessionTimeout)) 294 { 295 return; 296 } 297 298 if (sessionTimeout) 299 { 300 // The mininum & maximum allowed values for session timeout are 30 301 // seconds and 86400 seconds respectively as per the session service 302 // schema mentioned at 303 // https://redfish.dmtf.org/schemas/v1/SessionService.v1_1_7.json 304 305 if (*sessionTimeout <= 86400 && *sessionTimeout >= 30) 306 { 307 std::chrono::seconds sessionTimeoutInseconds(*sessionTimeout); 308 persistent_data::SessionStore::getInstance() 309 .updateSessionTimeout(sessionTimeoutInseconds); 310 messages::propertyValueModified( 311 asyncResp->res, "SessionTimeOut", 312 std::to_string(*sessionTimeout)); 313 } 314 else 315 { 316 messages::propertyValueNotInList( 317 asyncResp->res, std::to_string(*sessionTimeout), 318 "SessionTimeOut"); 319 } 320 } 321 } 322 }; 323 324 } // namespace redfish 325