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