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