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