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 "app.hpp" 19 #include "error_messages.hpp" 20 #include "http/utility.hpp" 21 #include "persistent_data.hpp" 22 #include "query.hpp" 23 #include "registries/privilege_registry.hpp" 24 #include "utils/json_utils.hpp" 25 26 namespace redfish 27 { 28 29 inline void fillSessionObject(crow::Response& res, 30 const persistent_data::UserSession& session) 31 { 32 res.jsonValue["Id"] = session.uniqueId; 33 res.jsonValue["UserName"] = session.username; 34 res.jsonValue["@odata.id"] = 35 "/redfish/v1/SessionService/Sessions/" + session.uniqueId; 36 res.jsonValue["@odata.type"] = "#Session.v1_5_0.Session"; 37 res.jsonValue["Name"] = "User Session"; 38 res.jsonValue["Description"] = "Manager User Session"; 39 res.jsonValue["ClientOriginIPAddress"] = session.clientIp; 40 if (session.clientId) 41 { 42 res.jsonValue["Context"] = *session.clientId; 43 } 44 // The below implementation is deprecated in leiu of Session.Context 45 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE 46 res.jsonValue["Oem"]["OpenBMC"]["@odata.type"] = 47 "#OemSession.v1_0_0.Session"; 48 res.jsonValue["Oem"]["OpenBMC"]["ClientID"] = session.clientId.value_or(""); 49 #endif 50 } 51 52 inline void 53 handleSessionHead(crow::App& app, const crow::Request& req, 54 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 55 const std::string& /*sessionId*/) 56 { 57 58 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 59 { 60 return; 61 } 62 asyncResp->res.addHeader( 63 boost::beast::http::field::link, 64 "</redfish/v1/JsonSchemas/Session/Session.json>; rel=describedby"); 65 } 66 67 inline void 68 handleSessionGet(crow::App& app, const crow::Request& req, 69 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 70 const std::string& sessionId) 71 { 72 handleSessionHead(app, req, asyncResp, sessionId); 73 74 // Note that control also reaches here via doPost and doDelete. 75 auto session = 76 persistent_data::SessionStore::getInstance().getSessionByUid(sessionId); 77 78 if (session == nullptr) 79 { 80 messages::resourceNotFound(asyncResp->res, "Session", sessionId); 81 return; 82 } 83 84 fillSessionObject(asyncResp->res, *session); 85 } 86 87 inline void 88 handleSessionDelete(crow::App& app, const crow::Request& req, 89 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 90 const std::string& sessionId) 91 { 92 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 93 { 94 return; 95 } 96 auto session = 97 persistent_data::SessionStore::getInstance().getSessionByUid(sessionId); 98 99 if (session == nullptr) 100 { 101 messages::resourceNotFound(asyncResp->res, "Session", sessionId); 102 return; 103 } 104 105 // Perform a proper ConfigureSelf authority check. If a 106 // session is being used to DELETE some other user's session, 107 // then the ConfigureSelf privilege does not apply. In that 108 // case, perform the authority check again without the user's 109 // ConfigureSelf privilege. 110 if (req.session != nullptr && !session->username.empty() && 111 session->username != req.session->username) 112 { 113 Privileges effectiveUserPrivileges = 114 redfish::getUserPrivileges(req.userRole); 115 116 if (!effectiveUserPrivileges.isSupersetOf({"ConfigureUsers"})) 117 { 118 messages::insufficientPrivilege(asyncResp->res); 119 return; 120 } 121 } 122 123 persistent_data::SessionStore::getInstance().removeSession(session); 124 messages::success(asyncResp->res); 125 } 126 127 inline nlohmann::json getSessionCollectionMembers() 128 { 129 std::vector<const std::string*> sessionIds = 130 persistent_data::SessionStore::getInstance().getUniqueIds( 131 false, persistent_data::PersistenceType::TIMEOUT); 132 nlohmann::json ret = nlohmann::json::array(); 133 for (const std::string* uid : sessionIds) 134 { 135 nlohmann::json::object_t session; 136 session["@odata.id"] = "/redfish/v1/SessionService/Sessions/" + *uid; 137 ret.push_back(std::move(session)); 138 } 139 return ret; 140 } 141 142 inline void handleSessionCollectionHead( 143 crow::App& app, const crow::Request& req, 144 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 145 { 146 147 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 148 { 149 return; 150 } 151 asyncResp->res.addHeader( 152 boost::beast::http::field::link, 153 "</redfish/v1/JsonSchemas/SessionCollection.json>; rel=describedby"); 154 } 155 156 inline void handleSessionCollectionGet( 157 crow::App& app, const crow::Request& req, 158 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 159 { 160 handleSessionCollectionHead(app, req, asyncResp); 161 asyncResp->res.jsonValue["Members"] = getSessionCollectionMembers(); 162 asyncResp->res.jsonValue["Members@odata.count"] = 163 asyncResp->res.jsonValue["Members"].size(); 164 asyncResp->res.jsonValue["@odata.type"] = 165 "#SessionCollection.SessionCollection"; 166 asyncResp->res.jsonValue["@odata.id"] = 167 "/redfish/v1/SessionService/Sessions/"; 168 asyncResp->res.jsonValue["Name"] = "Session Collection"; 169 asyncResp->res.jsonValue["Description"] = "Session Collection"; 170 } 171 172 inline void handleSessionCollectionMembersGet( 173 crow::App& app, const crow::Request& req, 174 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 175 { 176 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 177 { 178 return; 179 } 180 asyncResp->res.jsonValue = getSessionCollectionMembers(); 181 } 182 183 inline void handleSessionCollectionPost( 184 crow::App& app, const crow::Request& req, 185 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 186 { 187 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 188 { 189 return; 190 } 191 std::string username; 192 std::string password; 193 std::optional<nlohmann::json> oemObject; 194 std::optional<std::string> clientId; 195 if (!json_util::readJsonPatch(req, asyncResp->res, "UserName", username, 196 "Password", password, "Context", clientId, 197 "Oem", oemObject)) 198 { 199 return; 200 } 201 202 if (password.empty() || username.empty() || 203 asyncResp->res.result() != boost::beast::http::status::ok) 204 { 205 if (username.empty()) 206 { 207 messages::propertyMissing(asyncResp->res, "UserName"); 208 } 209 210 if (password.empty()) 211 { 212 messages::propertyMissing(asyncResp->res, "Password"); 213 } 214 215 return; 216 } 217 218 int pamrc = pamAuthenticateUser(username, password); 219 bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD; 220 if ((pamrc != PAM_SUCCESS) && !isConfigureSelfOnly) 221 { 222 messages::resourceAtUriUnauthorized(asyncResp->res, req.urlView, 223 "Invalid username or password"); 224 return; 225 } 226 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE 227 if (oemObject) 228 { 229 std::optional<nlohmann::json> bmcOem; 230 if (!json_util::readJson(*oemObject, asyncResp->res, "OpenBMC", bmcOem)) 231 { 232 return; 233 } 234 235 std::optional<std::string> oemClientId; 236 if (!json_util::readJson(*bmcOem, asyncResp->res, "ClientID", 237 oemClientId)) 238 { 239 BMCWEB_LOG_ERROR << "Could not read ClientId"; 240 return; 241 } 242 if (oemClientId) 243 { 244 if (clientId) 245 { 246 messages::propertyValueConflict(*oemClientId, *clientId); 247 return; 248 } 249 clientId = *oemClientId; 250 } 251 } 252 #endif 253 254 // User is authenticated - create session 255 std::shared_ptr<persistent_data::UserSession> session = 256 persistent_data::SessionStore::getInstance().generateUserSession( 257 username, req.ipAddress, clientId, 258 persistent_data::PersistenceType::TIMEOUT, isConfigureSelfOnly); 259 if (session == nullptr) 260 { 261 messages::internalError(asyncResp->res); 262 return; 263 } 264 265 asyncResp->res.addHeader("X-Auth-Token", session->sessionToken); 266 asyncResp->res.addHeader( 267 "Location", "/redfish/v1/SessionService/Sessions/" + session->uniqueId); 268 asyncResp->res.result(boost::beast::http::status::created); 269 if (session->isConfigureSelfOnly) 270 { 271 messages::passwordChangeRequired( 272 asyncResp->res, 273 crow::utility::urlFromPieces("redfish", "v1", "AccountService", 274 "Accounts", session->username)); 275 } 276 277 fillSessionObject(asyncResp->res, *session); 278 } 279 inline void handleSessionServiceHead( 280 crow::App& app, const crow::Request& req, 281 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 282 { 283 284 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 285 { 286 return; 287 } 288 asyncResp->res.addHeader( 289 boost::beast::http::field::link, 290 "</redfish/v1/JsonSchemas/SessionService/SessionService.json>; rel=describedby"); 291 } 292 inline void 293 handleSessionServiceGet(crow::App& app, const crow::Request& req, 294 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 295 296 { 297 handleSessionServiceHead(app, req, asyncResp); 298 asyncResp->res.jsonValue["@odata.type"] = 299 "#SessionService.v1_0_2.SessionService"; 300 asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/SessionService/"; 301 asyncResp->res.jsonValue["Name"] = "Session Service"; 302 asyncResp->res.jsonValue["Id"] = "SessionService"; 303 asyncResp->res.jsonValue["Description"] = "Session Service"; 304 asyncResp->res.jsonValue["SessionTimeout"] = 305 persistent_data::SessionStore::getInstance().getTimeoutInSeconds(); 306 asyncResp->res.jsonValue["ServiceEnabled"] = true; 307 308 asyncResp->res.jsonValue["Sessions"]["@odata.id"] = 309 "/redfish/v1/SessionService/Sessions"; 310 } 311 312 inline void handleSessionServicePatch( 313 crow::App& app, const crow::Request& req, 314 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 315 { 316 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 317 { 318 return; 319 } 320 std::optional<int64_t> sessionTimeout; 321 if (!json_util::readJsonPatch(req, asyncResp->res, "SessionTimeout", 322 sessionTimeout)) 323 { 324 return; 325 } 326 327 if (sessionTimeout) 328 { 329 // The mininum & maximum allowed values for session timeout 330 // are 30 seconds and 86400 seconds respectively as per the 331 // session service schema mentioned at 332 // https://redfish.dmtf.org/schemas/v1/SessionService.v1_1_7.json 333 334 if (*sessionTimeout <= 86400 && *sessionTimeout >= 30) 335 { 336 std::chrono::seconds sessionTimeoutInseconds(*sessionTimeout); 337 persistent_data::SessionStore::getInstance().updateSessionTimeout( 338 sessionTimeoutInseconds); 339 messages::propertyValueModified(asyncResp->res, "SessionTimeOut", 340 std::to_string(*sessionTimeout)); 341 } 342 else 343 { 344 messages::propertyValueNotInList(asyncResp->res, 345 std::to_string(*sessionTimeout), 346 "SessionTimeOut"); 347 } 348 } 349 } 350 351 inline void requestRoutesSession(App& app) 352 { 353 BMCWEB_ROUTE(app, "/redfish/v1/SessionService/Sessions/<str>/") 354 .privileges(redfish::privileges::headSession) 355 .methods(boost::beast::http::verb::head)( 356 std::bind_front(handleSessionHead, std::ref(app))); 357 358 BMCWEB_ROUTE(app, "/redfish/v1/SessionService/Sessions/<str>/") 359 .privileges(redfish::privileges::getSession) 360 .methods(boost::beast::http::verb::get)( 361 std::bind_front(handleSessionGet, std::ref(app))); 362 363 BMCWEB_ROUTE(app, "/redfish/v1/SessionService/Sessions/<str>/") 364 .privileges(redfish::privileges::deleteSession) 365 .methods(boost::beast::http::verb::delete_)( 366 std::bind_front(handleSessionDelete, std::ref(app))); 367 368 BMCWEB_ROUTE(app, "/redfish/v1/SessionService/Sessions/") 369 .privileges(redfish::privileges::headSessionCollection) 370 .methods(boost::beast::http::verb::head)( 371 std::bind_front(handleSessionCollectionHead, std::ref(app))); 372 373 BMCWEB_ROUTE(app, "/redfish/v1/SessionService/Sessions/") 374 .privileges(redfish::privileges::getSessionCollection) 375 .methods(boost::beast::http::verb::get)( 376 std::bind_front(handleSessionCollectionGet, std::ref(app))); 377 378 // Note, the next two routes technically don't match the privilege 379 // registry given the way login mechanisms work. The base privilege 380 // registry lists this endpoint as requiring login privilege, but because 381 // this is the endpoint responsible for giving the login privilege, and it 382 // is itself its own route, it needs to not require Login 383 BMCWEB_ROUTE(app, "/redfish/v1/SessionService/Sessions/") 384 .privileges({}) 385 .methods(boost::beast::http::verb::post)( 386 std::bind_front(handleSessionCollectionPost, std::ref(app))); 387 388 BMCWEB_ROUTE(app, "/redfish/v1/SessionService/Sessions/Members/") 389 .privileges({}) 390 .methods(boost::beast::http::verb::post)( 391 std::bind_front(handleSessionCollectionPost, std::ref(app))); 392 393 BMCWEB_ROUTE(app, "/redfish/v1/SessionService/") 394 .privileges(redfish::privileges::headSessionService) 395 .methods(boost::beast::http::verb::head)( 396 std::bind_front(handleSessionServiceHead, std::ref(app))); 397 398 BMCWEB_ROUTE(app, "/redfish/v1/SessionService/") 399 .privileges(redfish::privileges::getSessionService) 400 .methods(boost::beast::http::verb::get)( 401 std::bind_front(handleSessionServiceGet, std::ref(app))); 402 403 BMCWEB_ROUTE(app, "/redfish/v1/SessionService/") 404 .privileges(redfish::privileges::patchSessionService) 405 .methods(boost::beast::http::verb::patch)( 406 std::bind_front(handleSessionServicePatch, std::ref(app))); 407 } 408 409 } // namespace redfish 410