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::PersistentData::SessionStore::getInstance().get_session_by_uid( 49 params[0]); 50 51 if (session == nullptr) { 52 messages::addMessageToErrorJson( 53 res.json_value, 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->unique_id; 61 Node::json["UserName"] = session->username; 62 Node::json["@odata.id"] = 63 "/redfish/v1/SessionService/Sessions/" + session->unique_id; 64 65 res.json_value = 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 CROW_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.json_value, messages::generalError()); 79 80 res.end(); 81 return; 82 } 83 84 auto session = 85 crow::PersistentData::SessionStore::getInstance().get_session_by_uid( 86 params[0]); 87 88 if (session == nullptr) { 89 messages::addMessageToErrorJson( 90 res.json_value, 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::PersistentData::SessionStore::getInstance().remove_session(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*> session_ids = 137 crow::PersistentData::SessionStore::getInstance().get_unique_ids( 138 false, crow::PersistentData::PersistenceType::TIMEOUT); 139 140 Node::json["Members@odata.count"] = session_ids.size(); 141 Node::json["Members"] = nlohmann::json::array(); 142 for (const std::string* uid : session_ids) { 143 Node::json["Members"].push_back( 144 {{"@odata.id", "/redfish/v1/SessionService/Sessions/" + *uid}}); 145 } 146 147 res.json_value = 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.json_value); 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::PersistentData::SessionStore::getInstance().generate_user_session( 167 username); 168 res.add_header("X-Auth-Token", session->session_token); 169 170 // Return data for created session 171 memberSession.doGet(res, req, {session->unique_id}); 172 173 // No need for res.end(), as it is called by doGet() 174 } 175 176 /** 177 * @brief Verifies data provided in request and tries to authenticate user 178 * 179 * @param[in] req Crow request containing authentication data 180 * @param[out] httpRespCode HTTP Code that should be returned in response 181 * @param[out] user Retrieved username - not filled on failure 182 * @param[out] errJson JSON to which error messages will be written 183 * 184 * @return true if authentication was successful, false otherwise 185 */ 186 bool authenticateUser(const crow::request& req, 187 boost::beast::http::status& httpRespCode, 188 std::string& user, nlohmann::json& errJson) { 189 // We need only UserName and Password - nothing more, nothing less 190 static constexpr const unsigned int numberOfRequiredFieldsInReq = 2; 191 192 // call with exceptions disabled 193 auto login_credentials = nlohmann::json::parse(req.body, nullptr, false); 194 if (login_credentials.is_discarded()) { 195 httpRespCode = boost::beast::http::status::bad_request; 196 197 messages::addMessageToErrorJson(errJson, messages::malformedJSON()); 198 199 return false; 200 } 201 202 // Check that there are only as many fields as there should be 203 if (login_credentials.size() != numberOfRequiredFieldsInReq) { 204 httpRespCode = boost::beast::http::status::bad_request; 205 206 messages::addMessageToErrorJson(errJson, messages::malformedJSON()); 207 208 return false; 209 } 210 211 // Find fields that we need - UserName and Password 212 auto user_it = login_credentials.find("UserName"); 213 auto pass_it = login_credentials.find("Password"); 214 if (user_it == login_credentials.end() || 215 pass_it == login_credentials.end()) { 216 httpRespCode = boost::beast::http::status::bad_request; 217 218 if (user_it == login_credentials.end()) { 219 messages::addMessageToErrorJson(errJson, 220 messages::propertyMissing("UserName")); 221 } 222 223 if (pass_it == login_credentials.end()) { 224 messages::addMessageToErrorJson(errJson, 225 messages::propertyMissing("Password")); 226 } 227 228 return false; 229 } 230 231 // Check that given data is of valid type (string) 232 if (!user_it->is_string() || !pass_it->is_string()) { 233 httpRespCode = boost::beast::http::status::bad_request; 234 235 if (!user_it->is_string()) { 236 messages::addMessageToErrorJson( 237 errJson, 238 messages::propertyValueTypeError(user_it->dump(), "UserName")); 239 } 240 241 if (!pass_it->is_string()) { 242 messages::addMessageToErrorJson( 243 errJson, 244 messages::propertyValueTypeError(user_it->dump(), "Password")); 245 } 246 247 return false; 248 } 249 250 // Extract username and password 251 std::string username = user_it->get<const std::string>(); 252 std::string password = pass_it->get<const std::string>(); 253 254 // Verify that required fields are not empty 255 if (username.empty() || password.empty()) { 256 httpRespCode = boost::beast::http::status::bad_request; 257 258 if (username.empty()) { 259 messages::addMessageToErrorJson(errJson, 260 messages::propertyMissing("UserName")); 261 } 262 263 if (password.empty()) { 264 messages::addMessageToErrorJson(errJson, 265 messages::propertyMissing("Password")); 266 } 267 268 return false; 269 } 270 271 // Finally - try to authenticate user 272 if (!pam_authenticate_user(username, password)) { 273 httpRespCode = boost::beast::http::status::unauthorized; 274 275 messages::addMessageToErrorJson( 276 errJson, messages::resourceAtUriUnauthorized( 277 std::string(req.url), "Invalid username or password")); 278 279 return false; 280 } 281 282 // User authenticated successfully 283 httpRespCode = boost::beast::http::status::ok; 284 user = username; 285 286 return true; 287 } 288 289 /** 290 * Member session to ensure consistency between collection's doPost and 291 * member's doGet, as they should return 100% matching data 292 */ 293 Sessions memberSession; 294 }; 295 296 class SessionService : public Node { 297 public: 298 SessionService(CrowApp& app) : Node(app, "/redfish/v1/SessionService/") { 299 Node::json["@odata.type"] = "#SessionService.v1_0_2.SessionService"; 300 Node::json["@odata.id"] = "/redfish/v1/SessionService/"; 301 Node::json["@odata.context"] = 302 "/redfish/v1/$metadata#SessionService.SessionService"; 303 Node::json["Name"] = "Session Service"; 304 Node::json["Id"] = "SessionService"; 305 Node::json["Description"] = "Session Service"; 306 Node::json["SessionTimeout"] = 307 crow::PersistentData::SessionStore::getInstance() 308 .get_timeout_in_seconds(); 309 Node::json["ServiceEnabled"] = true; 310 311 entityPrivileges = { 312 {boost::beast::http::verb::get, {{"Login"}}}, 313 {boost::beast::http::verb::head, {{"Login"}}}, 314 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 315 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 316 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 317 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 318 } 319 320 private: 321 void doGet(crow::response& res, const crow::request& req, 322 const std::vector<std::string>& params) override { 323 res.json_value = Node::json; 324 res.end(); 325 } 326 }; 327 328 } // namespace redfish 329