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 "node.hpp" 20 #include "persistent_data.hpp" 21 22 #include <app.hpp> 23 24 namespace redfish 25 { 26 27 class SessionCollection; 28 29 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({{"Login"}}) 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({{"ConfigureManager"}, {"ConfigureSelf"}}) 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 asyncResp->res.result(boost::beast::http::status::no_content); 106 }); 107 108 BMCWEB_ROUTE(app, "/redfish/v1/SessionService/Sessions/") 109 .privileges({{"Login"}}) 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 .privileges({}) 138 .methods(boost::beast::http::verb::post)( 139 [](const crow::Request& req, 140 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) -> void { 141 std::string username; 142 std::string password; 143 std::optional<nlohmann::json> oemObject; 144 std::string clientId; 145 if (!json_util::readJson(req, asyncResp->res, "UserName", 146 username, "Password", password, "Oem", 147 oemObject)) 148 { 149 return; 150 } 151 152 if (password.empty() || username.empty() || 153 asyncResp->res.result() != boost::beast::http::status::ok) 154 { 155 if (username.empty()) 156 { 157 messages::propertyMissing(asyncResp->res, "UserName"); 158 } 159 160 if (password.empty()) 161 { 162 messages::propertyMissing(asyncResp->res, "Password"); 163 } 164 165 return; 166 } 167 168 int pamrc = pamAuthenticateUser(username, password); 169 bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD; 170 if ((pamrc != PAM_SUCCESS) && !isConfigureSelfOnly) 171 { 172 messages::resourceAtUriUnauthorized( 173 asyncResp->res, std::string(req.url), 174 "Invalid username or password"); 175 return; 176 } 177 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE 178 if (oemObject) 179 { 180 std::optional<nlohmann::json> bmcOem; 181 if (!json_util::readJson(*oemObject, asyncResp->res, 182 "OpenBMC", bmcOem)) 183 { 184 return; 185 } 186 if (!json_util::readJson(*bmcOem, asyncResp->res, 187 "ClientID", clientId)) 188 { 189 BMCWEB_LOG_ERROR << "Could not read ClientId"; 190 return; 191 } 192 } 193 #endif 194 195 // User is authenticated - create session 196 std::shared_ptr<persistent_data::UserSession> session = 197 persistent_data::SessionStore::getInstance() 198 .generateUserSession( 199 username, req.ipAddress.to_string(), clientId, 200 persistent_data::PersistenceType::TIMEOUT, 201 isConfigureSelfOnly); 202 asyncResp->res.addHeader("X-Auth-Token", session->sessionToken); 203 asyncResp->res.addHeader( 204 "Location", 205 "/redfish/v1/SessionService/Sessions/" + session->uniqueId); 206 asyncResp->res.result(boost::beast::http::status::created); 207 if (session->isConfigureSelfOnly) 208 { 209 messages::passwordChangeRequired( 210 asyncResp->res, "/redfish/v1/AccountService/Accounts/" + 211 session->username); 212 } 213 214 fillSessionObject(asyncResp->res, *session); 215 }); 216 217 BMCWEB_ROUTE(app, "/redfish/v1/SessionService/") 218 .privileges({{"Login"}}) 219 .methods(boost::beast::http::verb::get)( 220 [](const crow::Request& /* req */, 221 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) -> void { 222 asyncResp->res.jsonValue["@odata.type"] = 223 "#SessionService.v1_0_2.SessionService"; 224 asyncResp->res.jsonValue["@odata.id"] = 225 "/redfish/v1/SessionService/"; 226 asyncResp->res.jsonValue["Name"] = "Session Service"; 227 asyncResp->res.jsonValue["Id"] = "SessionService"; 228 asyncResp->res.jsonValue["Description"] = "Session Service"; 229 asyncResp->res.jsonValue["SessionTimeout"] = 230 persistent_data::SessionStore::getInstance() 231 .getTimeoutInSeconds(); 232 asyncResp->res.jsonValue["ServiceEnabled"] = true; 233 234 asyncResp->res.jsonValue["Sessions"] = { 235 {"@odata.id", "/redfish/v1/SessionService/Sessions"}}; 236 }); 237 238 BMCWEB_ROUTE(app, "/redfish/v1/SessionService/") 239 .privileges({{"ConfigureManager"}}) 240 .methods(boost::beast::http::verb::patch)( 241 [](const crow::Request& req, 242 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) -> void { 243 std::optional<int64_t> sessionTimeout; 244 if (!json_util::readJson(req, asyncResp->res, "SessionTimeout", 245 sessionTimeout)) 246 { 247 return; 248 } 249 250 if (sessionTimeout) 251 { 252 // The mininum & maximum allowed values for session timeout 253 // are 30 seconds and 86400 seconds respectively as per the 254 // session service schema mentioned at 255 // https://redfish.dmtf.org/schemas/v1/SessionService.v1_1_7.json 256 257 if (*sessionTimeout <= 86400 && *sessionTimeout >= 30) 258 { 259 std::chrono::seconds sessionTimeoutInseconds( 260 *sessionTimeout); 261 persistent_data::SessionStore::getInstance() 262 .updateSessionTimeout(sessionTimeoutInseconds); 263 messages::propertyValueModified( 264 asyncResp->res, "SessionTimeOut", 265 std::to_string(*sessionTimeout)); 266 } 267 else 268 { 269 messages::propertyValueNotInList( 270 asyncResp->res, std::to_string(*sessionTimeout), 271 "SessionTimeOut"); 272 } 273 } 274 }); 275 } 276 277 } // namespace redfish 278