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