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