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