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