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