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