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_middleware.hpp" 21 22 namespace redfish 23 { 24 25 class SessionCollection; 26 27 class Sessions : public Node 28 { 29 public: 30 Sessions(CrowApp& app) : 31 Node(app, "/redfish/v1/SessionService/Sessions/<str>/", std::string()) 32 { 33 Node::json["@odata.type"] = "#Session.v1_0_2.Session"; 34 Node::json["@odata.context"] = "/redfish/v1/$metadata#Session.Session"; 35 Node::json["Name"] = "User Session"; 36 Node::json["Description"] = "Manager User Session"; 37 38 entityPrivileges = { 39 {boost::beast::http::verb::get, {{"Login"}}}, 40 {boost::beast::http::verb::head, {{"Login"}}}, 41 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 42 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 43 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 44 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 45 } 46 47 private: 48 void doGet(crow::Response& res, const crow::Request& req, 49 const std::vector<std::string>& params) override 50 { 51 auto session = 52 crow::persistent_data::SessionStore::getInstance().getSessionByUid( 53 params[0]); 54 55 if (session == nullptr) 56 { 57 messages::addMessageToErrorJson( 58 res.jsonValue, 59 messages::resourceNotFound("Session", params[0])); 60 61 res.result(boost::beast::http::status::not_found); 62 res.end(); 63 return; 64 } 65 66 Node::json["Id"] = session->uniqueId; 67 Node::json["UserName"] = session->username; 68 Node::json["@odata.id"] = 69 "/redfish/v1/SessionService/Sessions/" + session->uniqueId; 70 71 res.jsonValue = Node::json; 72 res.end(); 73 } 74 75 void doDelete(crow::Response& res, const crow::Request& req, 76 const std::vector<std::string>& params) override 77 { 78 // Need only 1 param which should be id of session to be deleted 79 if (params.size() != 1) 80 { 81 // This should be handled by crow and never happen 82 BMCWEB_LOG_ERROR << "Session DELETE has been called with invalid " 83 "number of params"; 84 85 res.result(boost::beast::http::status::bad_request); 86 messages::addMessageToErrorJson(res.jsonValue, 87 messages::generalError()); 88 89 res.end(); 90 return; 91 } 92 93 auto session = 94 crow::persistent_data::SessionStore::getInstance().getSessionByUid( 95 params[0]); 96 97 if (session == nullptr) 98 { 99 messages::addMessageToErrorJson( 100 res.jsonValue, 101 messages::resourceNotFound("Session", params[0])); 102 103 res.result(boost::beast::http::status::not_found); 104 res.end(); 105 return; 106 } 107 108 // DELETE should return representation of object that will be removed 109 doGet(res, req, params); 110 111 crow::persistent_data::SessionStore::getInstance().removeSession( 112 session); 113 } 114 115 /** 116 * This allows SessionCollection to reuse this class' doGet method, to 117 * maintain consistency of returned data, as Collection's doPost should 118 * return data for created member which should match member's doGet result 119 * in 100% 120 */ 121 friend SessionCollection; 122 }; 123 124 class SessionCollection : public Node 125 { 126 public: 127 SessionCollection(CrowApp& app) : 128 Node(app, "/redfish/v1/SessionService/Sessions/"), memberSession(app) 129 { 130 Node::json["@odata.type"] = "#SessionCollection.SessionCollection"; 131 Node::json["@odata.id"] = "/redfish/v1/SessionService/Sessions/"; 132 Node::json["@odata.context"] = 133 "/redfish/v1/$metadata#SessionCollection.SessionCollection"; 134 Node::json["Name"] = "Session Collection"; 135 Node::json["Description"] = "Session Collection"; 136 Node::json["Members@odata.count"] = 0; 137 Node::json["Members"] = nlohmann::json::array(); 138 139 entityPrivileges = { 140 {boost::beast::http::verb::get, {{"Login"}}}, 141 {boost::beast::http::verb::head, {{"Login"}}}, 142 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 143 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 144 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 145 {boost::beast::http::verb::post, {}}}; 146 } 147 148 private: 149 void doGet(crow::Response& res, const crow::Request& req, 150 const std::vector<std::string>& params) override 151 { 152 std::vector<const std::string*> sessionIds = 153 crow::persistent_data::SessionStore::getInstance().getUniqueIds( 154 false, crow::persistent_data::PersistenceType::TIMEOUT); 155 156 Node::json["Members@odata.count"] = sessionIds.size(); 157 Node::json["Members"] = nlohmann::json::array(); 158 for (const std::string* uid : sessionIds) 159 { 160 Node::json["Members"].push_back( 161 {{"@odata.id", "/redfish/v1/SessionService/Sessions/" + *uid}}); 162 } 163 164 res.jsonValue = Node::json; 165 res.end(); 166 } 167 168 void doPost(crow::Response& res, const crow::Request& req, 169 const std::vector<std::string>& params) override 170 { 171 boost::beast::http::status status; 172 std::string username; 173 bool userAuthSuccessful = 174 authenticateUser(req, status, username, res.jsonValue); 175 res.result(status); 176 177 if (!userAuthSuccessful) 178 { 179 res.end(); 180 return; 181 } 182 183 // User is authenticated - create session for him 184 auto session = crow::persistent_data::SessionStore::getInstance() 185 .generateUserSession(username); 186 res.addHeader("X-Auth-Token", session->sessionToken); 187 188 res.addHeader("Location", "/redfish/v1/SessionService/Sessions/" + 189 session->uniqueId); 190 191 // Return data for created session 192 memberSession.doGet(res, req, {session->uniqueId}); 193 194 // No need for res.end(), as it is called by doGet() 195 } 196 197 /** 198 * @brief Verifies data provided in request and tries to authenticate user 199 * 200 * @param[in] req Crow request containing authentication data 201 * @param[out] httpRespCode HTTP Code that should be returned in response 202 * @param[out] user Retrieved username - not filled on failure 203 * @param[out] errJson JSON to which error messages will be written 204 * 205 * @return true if authentication was successful, false otherwise 206 */ 207 bool authenticateUser(const crow::Request& req, 208 boost::beast::http::status& httpRespCode, 209 std::string& user, nlohmann::json& errJson) 210 { 211 // We need only UserName and Password - nothing more, nothing less 212 static constexpr const unsigned int numberOfRequiredFieldsInReq = 2; 213 214 // call with exceptions disabled 215 auto loginCredentials = nlohmann::json::parse(req.body, nullptr, false); 216 if (loginCredentials.is_discarded()) 217 { 218 httpRespCode = boost::beast::http::status::bad_request; 219 220 messages::addMessageToErrorJson(errJson, messages::malformedJSON()); 221 222 return false; 223 } 224 225 // Check that there are only as many fields as there should be 226 if (loginCredentials.size() != numberOfRequiredFieldsInReq) 227 { 228 httpRespCode = boost::beast::http::status::bad_request; 229 230 messages::addMessageToErrorJson(errJson, messages::malformedJSON()); 231 232 return false; 233 } 234 235 // Find fields that we need - UserName and Password 236 auto userIt = loginCredentials.find("UserName"); 237 auto passIt = loginCredentials.find("Password"); 238 if (userIt == loginCredentials.end() || 239 passIt == loginCredentials.end()) 240 { 241 httpRespCode = boost::beast::http::status::bad_request; 242 243 if (userIt == loginCredentials.end()) 244 { 245 messages::addMessageToErrorJson( 246 errJson, messages::propertyMissing("UserName")); 247 } 248 249 if (passIt == loginCredentials.end()) 250 { 251 messages::addMessageToErrorJson( 252 errJson, messages::propertyMissing("Password")); 253 } 254 255 return false; 256 } 257 258 // Check that given data is of valid type (string) 259 if (!userIt->is_string() || !passIt->is_string()) 260 { 261 httpRespCode = boost::beast::http::status::bad_request; 262 263 if (!userIt->is_string()) 264 { 265 messages::addMessageToErrorJson( 266 errJson, messages::propertyValueTypeError(userIt->dump(), 267 "UserName")); 268 } 269 270 if (!passIt->is_string()) 271 { 272 messages::addMessageToErrorJson( 273 errJson, messages::propertyValueTypeError(userIt->dump(), 274 "Password")); 275 } 276 277 return false; 278 } 279 280 // Extract username and password 281 std::string username = userIt->get<const std::string>(); 282 std::string password = passIt->get<const std::string>(); 283 284 // Verify that required fields are not empty 285 if (username.empty() || password.empty()) 286 { 287 httpRespCode = boost::beast::http::status::bad_request; 288 289 if (username.empty()) 290 { 291 messages::addMessageToErrorJson( 292 errJson, messages::propertyMissing("UserName")); 293 } 294 295 if (password.empty()) 296 { 297 messages::addMessageToErrorJson( 298 errJson, messages::propertyMissing("Password")); 299 } 300 301 return false; 302 } 303 304 // Finally - try to authenticate user 305 if (!pamAuthenticateUser(username, password)) 306 { 307 httpRespCode = boost::beast::http::status::unauthorized; 308 309 messages::addMessageToErrorJson( 310 errJson, 311 messages::resourceAtUriUnauthorized( 312 std::string(req.url), "Invalid username or password")); 313 314 return false; 315 } 316 317 // User authenticated successfully 318 httpRespCode = boost::beast::http::status::ok; 319 user = username; 320 321 return true; 322 } 323 324 /** 325 * Member session to ensure consistency between collection's doPost and 326 * member's doGet, as they should return 100% matching data 327 */ 328 Sessions memberSession; 329 }; 330 331 class SessionService : public Node 332 { 333 public: 334 SessionService(CrowApp& app) : Node(app, "/redfish/v1/SessionService/") 335 { 336 Node::json["@odata.type"] = "#SessionService.v1_0_2.SessionService"; 337 Node::json["@odata.id"] = "/redfish/v1/SessionService/"; 338 Node::json["@odata.context"] = 339 "/redfish/v1/$metadata#SessionService.SessionService"; 340 Node::json["Name"] = "Session Service"; 341 Node::json["Id"] = "SessionService"; 342 Node::json["Description"] = "Session Service"; 343 Node::json["SessionTimeout"] = 344 crow::persistent_data::SessionStore::getInstance() 345 .getTimeoutInSeconds(); 346 Node::json["ServiceEnabled"] = true; 347 348 entityPrivileges = { 349 {boost::beast::http::verb::get, {{"Login"}}}, 350 {boost::beast::http::verb::head, {{"Login"}}}, 351 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 352 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 353 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 354 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 355 } 356 357 private: 358 void doGet(crow::Response& res, const crow::Request& req, 359 const std::vector<std::string>& params) override 360 { 361 res.jsonValue = Node::json; 362 res.end(); 363 } 364 }; 365 366 } // namespace redfish 367