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