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