xref: /openbmc/bmcweb/features/redfish/lib/redfish_sessions.hpp (revision f4c4dcf438a43f04fe91f1350f88eda618cdb3e4)
12b7981f6SKowalski, Kamil /*
22b7981f6SKowalski, Kamil // Copyright (c) 2018 Intel Corporation
32b7981f6SKowalski, Kamil //
42b7981f6SKowalski, Kamil // Licensed under the Apache License, Version 2.0 (the "License");
52b7981f6SKowalski, Kamil // you may not use this file except in compliance with the License.
62b7981f6SKowalski, Kamil // You may obtain a copy of the License at
72b7981f6SKowalski, Kamil //
82b7981f6SKowalski, Kamil //      http://www.apache.org/licenses/LICENSE-2.0
92b7981f6SKowalski, Kamil //
102b7981f6SKowalski, Kamil // Unless required by applicable law or agreed to in writing, software
112b7981f6SKowalski, Kamil // distributed under the License is distributed on an "AS IS" BASIS,
122b7981f6SKowalski, Kamil // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
132b7981f6SKowalski, Kamil // See the License for the specific language governing permissions and
142b7981f6SKowalski, Kamil // limitations under the License.
152b7981f6SKowalski, Kamil */
162b7981f6SKowalski, Kamil #pragma once
1743a095abSBorawski.Lukasz 
18*f4c4dcf4SKowalski, Kamil #include "error_messages.hpp"
192b7981f6SKowalski, Kamil #include "node.hpp"
202b7981f6SKowalski, Kamil #include "session_storage_singleton.hpp"
212b7981f6SKowalski, Kamil 
222b7981f6SKowalski, Kamil namespace redfish {
232b7981f6SKowalski, Kamil 
242b7981f6SKowalski, Kamil class SessionCollection;
252b7981f6SKowalski, Kamil 
262b7981f6SKowalski, Kamil class Sessions : public Node {
272b7981f6SKowalski, Kamil  public:
2843a095abSBorawski.Lukasz   Sessions(CrowApp& app)
296c233015SEd Tanous       : Node(app, "/redfish/v1/SessionService/Sessions/<str>/", std::string()) {
30c1a46bd2SBorawski.Lukasz     Node::json["@odata.type"] = "#Session.v1_0_2.Session";
31c1a46bd2SBorawski.Lukasz     Node::json["@odata.context"] = "/redfish/v1/$metadata#Session.Session";
32c1a46bd2SBorawski.Lukasz     Node::json["Name"] = "User Session";
33c1a46bd2SBorawski.Lukasz     Node::json["Description"] = "Manager User Session";
343ebd75f7SEd Tanous 
353ebd75f7SEd Tanous     entityPrivileges = {{crow::HTTPMethod::GET, {{"Login"}}},
363ebd75f7SEd Tanous                         {crow::HTTPMethod::HEAD, {{"Login"}}},
373ebd75f7SEd Tanous                         {crow::HTTPMethod::PATCH, {{"ConfigureManager"}}},
383ebd75f7SEd Tanous                         {crow::HTTPMethod::PUT, {{"ConfigureManager"}}},
393ebd75f7SEd Tanous                         {crow::HTTPMethod::DELETE, {{"ConfigureManager"}}},
403ebd75f7SEd Tanous                         {crow::HTTPMethod::POST, {{"ConfigureManager"}}}};
412b7981f6SKowalski, Kamil   }
422b7981f6SKowalski, Kamil 
432b7981f6SKowalski, Kamil  private:
442b7981f6SKowalski, Kamil   void doGet(crow::response& res, const crow::request& req,
452b7981f6SKowalski, Kamil              const std::vector<std::string>& params) override {
462b7981f6SKowalski, Kamil     auto session =
472b7981f6SKowalski, Kamil         crow::PersistentData::session_store->get_session_by_uid(params[0]);
482b7981f6SKowalski, Kamil 
492b7981f6SKowalski, Kamil     if (session == nullptr) {
50*f4c4dcf4SKowalski, Kamil       messages::addMessageToErrorJson(
51*f4c4dcf4SKowalski, Kamil           res.json_value, messages::resourceNotFound("Session", params[0]));
52*f4c4dcf4SKowalski, Kamil 
532b7981f6SKowalski, Kamil       res.code = static_cast<int>(HttpRespCode::NOT_FOUND);
542b7981f6SKowalski, Kamil       res.end();
552b7981f6SKowalski, Kamil       return;
562b7981f6SKowalski, Kamil     }
572b7981f6SKowalski, Kamil 
58c1a46bd2SBorawski.Lukasz     Node::json["Id"] = session->unique_id;
59c1a46bd2SBorawski.Lukasz     Node::json["UserName"] = session->username;
60c1a46bd2SBorawski.Lukasz     Node::json["@odata.id"] =
612b7981f6SKowalski, Kamil         "/redfish/v1/SessionService/Sessions/" + session->unique_id;
622b7981f6SKowalski, Kamil 
63c1a46bd2SBorawski.Lukasz     res.json_value = Node::json;
642b7981f6SKowalski, Kamil     res.end();
652b7981f6SKowalski, Kamil   }
662b7981f6SKowalski, Kamil 
672b7981f6SKowalski, Kamil   void doDelete(crow::response& res, const crow::request& req,
682b7981f6SKowalski, Kamil                 const std::vector<std::string>& params) override {
692b7981f6SKowalski, Kamil     // Need only 1 param which should be id of session to be deleted
702b7981f6SKowalski, Kamil     if (params.size() != 1) {
71*f4c4dcf4SKowalski, Kamil       // This should be handled by crow and never happen
72*f4c4dcf4SKowalski, Kamil       CROW_LOG_ERROR
73*f4c4dcf4SKowalski, Kamil           << "Session DELETE has been called with invalid number of params";
74*f4c4dcf4SKowalski, Kamil 
752b7981f6SKowalski, Kamil       res.code = static_cast<int>(HttpRespCode::BAD_REQUEST);
76*f4c4dcf4SKowalski, Kamil       messages::addMessageToErrorJson(res.json_value, messages::generalError());
772b7981f6SKowalski, Kamil       res.end();
782b7981f6SKowalski, Kamil       return;
792b7981f6SKowalski, Kamil     }
802b7981f6SKowalski, Kamil 
812b7981f6SKowalski, Kamil     auto session =
822b7981f6SKowalski, Kamil         crow::PersistentData::session_store->get_session_by_uid(params[0]);
832b7981f6SKowalski, Kamil 
842b7981f6SKowalski, Kamil     if (session == nullptr) {
85*f4c4dcf4SKowalski, Kamil       messages::addMessageToErrorJson(
86*f4c4dcf4SKowalski, Kamil           res.json_value, messages::resourceNotFound("Session", params[0]));
87*f4c4dcf4SKowalski, Kamil 
882b7981f6SKowalski, Kamil       res.code = static_cast<int>(HttpRespCode::NOT_FOUND);
892b7981f6SKowalski, Kamil       res.end();
902b7981f6SKowalski, Kamil       return;
912b7981f6SKowalski, Kamil     }
922b7981f6SKowalski, Kamil 
93*f4c4dcf4SKowalski, Kamil     // DELETE should return representation of object that will be removed
94*f4c4dcf4SKowalski, Kamil     doGet(res, req, params);
95*f4c4dcf4SKowalski, Kamil 
962b7981f6SKowalski, Kamil     crow::PersistentData::session_store->remove_session(session);
972b7981f6SKowalski, Kamil   }
982b7981f6SKowalski, Kamil 
992b7981f6SKowalski, Kamil   /**
1002b7981f6SKowalski, Kamil    * This allows SessionCollection to reuse this class' doGet method, to
1012b7981f6SKowalski, Kamil    * maintain consistency of returned data, as Collection's doPost should return
1022b7981f6SKowalski, Kamil    * data for created member which should match member's doGet result in 100%
1032b7981f6SKowalski, Kamil    */
1042b7981f6SKowalski, Kamil   friend SessionCollection;
1052b7981f6SKowalski, Kamil };
1062b7981f6SKowalski, Kamil 
1072b7981f6SKowalski, Kamil class SessionCollection : public Node {
1082b7981f6SKowalski, Kamil  public:
10943a095abSBorawski.Lukasz   SessionCollection(CrowApp& app)
1103ebd75f7SEd Tanous       : Node(app, "/redfish/v1/SessionService/Sessions/"), memberSession(app) {
111c1a46bd2SBorawski.Lukasz     Node::json["@odata.type"] = "#SessionCollection.SessionCollection";
112c1a46bd2SBorawski.Lukasz     Node::json["@odata.id"] = "/redfish/v1/SessionService/Sessions/";
113c1a46bd2SBorawski.Lukasz     Node::json["@odata.context"] =
1142b7981f6SKowalski, Kamil         "/redfish/v1/$metadata#SessionCollection.SessionCollection";
115c1a46bd2SBorawski.Lukasz     Node::json["Name"] = "Session Collection";
116c1a46bd2SBorawski.Lukasz     Node::json["Description"] = "Session Collection";
117c1a46bd2SBorawski.Lukasz     Node::json["Members@odata.count"] = 0;
118c1a46bd2SBorawski.Lukasz     Node::json["Members"] = nlohmann::json::array();
1193ebd75f7SEd Tanous 
1203ebd75f7SEd Tanous     entityPrivileges = {{crow::HTTPMethod::GET, {{"Login"}}},
1213ebd75f7SEd Tanous                         {crow::HTTPMethod::HEAD, {{"Login"}}},
1223ebd75f7SEd Tanous                         {crow::HTTPMethod::PATCH, {{"ConfigureManager"}}},
1233ebd75f7SEd Tanous                         {crow::HTTPMethod::PUT, {{"ConfigureManager"}}},
1243ebd75f7SEd Tanous                         {crow::HTTPMethod::DELETE, {{"ConfigureManager"}}},
1253ebd75f7SEd Tanous                         {crow::HTTPMethod::POST, {}}};
1262b7981f6SKowalski, Kamil   }
1272b7981f6SKowalski, Kamil 
1282b7981f6SKowalski, Kamil  private:
1292b7981f6SKowalski, Kamil   void doGet(crow::response& res, const crow::request& req,
1302b7981f6SKowalski, Kamil              const std::vector<std::string>& params) override {
1312b7981f6SKowalski, Kamil     std::vector<const std::string*> session_ids =
1322b7981f6SKowalski, Kamil         crow::PersistentData::session_store->get_unique_ids(
1332b7981f6SKowalski, Kamil             false, crow::PersistentData::PersistenceType::TIMEOUT);
1342b7981f6SKowalski, Kamil 
135c1a46bd2SBorawski.Lukasz     Node::json["Members@odata.count"] = session_ids.size();
136c1a46bd2SBorawski.Lukasz     Node::json["Members"] = nlohmann::json::array();
1372b7981f6SKowalski, Kamil     for (const auto& uid : session_ids) {
138c1a46bd2SBorawski.Lukasz       Node::json["Members"].push_back(
1392b7981f6SKowalski, Kamil           {{"@odata.id", "/redfish/v1/SessionService/Sessions/" + *uid}});
1402b7981f6SKowalski, Kamil     }
1412b7981f6SKowalski, Kamil 
142c1a46bd2SBorawski.Lukasz     res.json_value = Node::json;
1432b7981f6SKowalski, Kamil     res.end();
1442b7981f6SKowalski, Kamil   }
1452b7981f6SKowalski, Kamil 
1462b7981f6SKowalski, Kamil   void doPost(crow::response& res, const crow::request& req,
1472b7981f6SKowalski, Kamil               const std::vector<std::string>& params) override {
1482b7981f6SKowalski, Kamil     std::string username;
149*f4c4dcf4SKowalski, Kamil     bool userAuthSuccessful =
150*f4c4dcf4SKowalski, Kamil         authenticateUser(req, res.code, username, res.json_value);
1512b7981f6SKowalski, Kamil     if (!userAuthSuccessful) {
1522b7981f6SKowalski, Kamil       res.end();
1532b7981f6SKowalski, Kamil       return;
1542b7981f6SKowalski, Kamil     }
1552b7981f6SKowalski, Kamil 
1562b7981f6SKowalski, Kamil     // User is authenticated - create session for him
1572b7981f6SKowalski, Kamil     auto session =
1582b7981f6SKowalski, Kamil         crow::PersistentData::session_store->generate_user_session(username);
1592b7981f6SKowalski, Kamil     res.add_header("X-Auth-Token", session.session_token);
1602b7981f6SKowalski, Kamil 
1612b7981f6SKowalski, Kamil     // Return data for created session
1622b7981f6SKowalski, Kamil     memberSession.doGet(res, req, {session.unique_id});
1632b7981f6SKowalski, Kamil 
1642b7981f6SKowalski, Kamil     // No need for res.end(), as it is called by doGet()
1652b7981f6SKowalski, Kamil   }
1662b7981f6SKowalski, Kamil 
1672b7981f6SKowalski, Kamil   /**
1682b7981f6SKowalski, Kamil    * @brief Verifies data provided in request and tries to authenticate user
1692b7981f6SKowalski, Kamil    *
1702b7981f6SKowalski, Kamil    * @param[in]  req            Crow request containing authentication data
1712b7981f6SKowalski, Kamil    * @param[out] httpRespCode   HTTP Code that should be returned in response
1722b7981f6SKowalski, Kamil    * @param[out] user           Retrieved username - not filled on failure
173*f4c4dcf4SKowalski, Kamil    * @param[out] errJson        JSON to which error messages will be written
1742b7981f6SKowalski, Kamil    *
1752b7981f6SKowalski, Kamil    * @return true if authentication was successful, false otherwise
1762b7981f6SKowalski, Kamil    */
177*f4c4dcf4SKowalski, Kamil   bool authenticateUser(const crow::request& req, int& httpRespCode,
178*f4c4dcf4SKowalski, Kamil                         std::string& user, nlohmann::json& errJson) {
1792b7981f6SKowalski, Kamil     // We need only UserName and Password - nothing more, nothing less
1802b7981f6SKowalski, Kamil     static constexpr const unsigned int numberOfRequiredFieldsInReq = 2;
1812b7981f6SKowalski, Kamil 
1822b7981f6SKowalski, Kamil     // call with exceptions disabled
1832b7981f6SKowalski, Kamil     auto login_credentials = nlohmann::json::parse(req.body, nullptr, false);
1842b7981f6SKowalski, Kamil     if (login_credentials.is_discarded()) {
185*f4c4dcf4SKowalski, Kamil       httpRespCode = static_cast<int>(HttpRespCode::BAD_REQUEST);
186*f4c4dcf4SKowalski, Kamil 
187*f4c4dcf4SKowalski, Kamil       messages::addMessageToErrorJson(errJson, messages::malformedJSON());
1882b7981f6SKowalski, Kamil 
1892b7981f6SKowalski, Kamil       return false;
1902b7981f6SKowalski, Kamil     }
1912b7981f6SKowalski, Kamil 
1922b7981f6SKowalski, Kamil     // Check that there are only as many fields as there should be
1932b7981f6SKowalski, Kamil     if (login_credentials.size() != numberOfRequiredFieldsInReq) {
194*f4c4dcf4SKowalski, Kamil       httpRespCode = static_cast<int>(HttpRespCode::BAD_REQUEST);
195*f4c4dcf4SKowalski, Kamil 
196*f4c4dcf4SKowalski, Kamil       messages::addMessageToErrorJson(errJson, messages::malformedJSON());
1972b7981f6SKowalski, Kamil 
1982b7981f6SKowalski, Kamil       return false;
1992b7981f6SKowalski, Kamil     }
2002b7981f6SKowalski, Kamil 
2012b7981f6SKowalski, Kamil     // Find fields that we need - UserName and Password
2022b7981f6SKowalski, Kamil     auto user_it = login_credentials.find("UserName");
2032b7981f6SKowalski, Kamil     auto pass_it = login_credentials.find("Password");
2042b7981f6SKowalski, Kamil     if (user_it == login_credentials.end() ||
2052b7981f6SKowalski, Kamil         pass_it == login_credentials.end()) {
206*f4c4dcf4SKowalski, Kamil       httpRespCode = static_cast<int>(HttpRespCode::BAD_REQUEST);
207*f4c4dcf4SKowalski, Kamil 
208*f4c4dcf4SKowalski, Kamil       if (user_it == login_credentials.end()) {
209*f4c4dcf4SKowalski, Kamil         messages::addMessageToErrorJson(errJson,
210*f4c4dcf4SKowalski, Kamil                                         messages::propertyMissing("UserName"));
211*f4c4dcf4SKowalski, Kamil       }
212*f4c4dcf4SKowalski, Kamil 
213*f4c4dcf4SKowalski, Kamil       if (pass_it == login_credentials.end()) {
214*f4c4dcf4SKowalski, Kamil         messages::addMessageToErrorJson(errJson,
215*f4c4dcf4SKowalski, Kamil                                         messages::propertyMissing("Password"));
216*f4c4dcf4SKowalski, Kamil       }
2172b7981f6SKowalski, Kamil 
2182b7981f6SKowalski, Kamil       return false;
2192b7981f6SKowalski, Kamil     }
2202b7981f6SKowalski, Kamil 
2212b7981f6SKowalski, Kamil     // Check that given data is of valid type (string)
2222b7981f6SKowalski, Kamil     if (!user_it->is_string() || !pass_it->is_string()) {
223*f4c4dcf4SKowalski, Kamil       httpRespCode = static_cast<int>(HttpRespCode::BAD_REQUEST);
224*f4c4dcf4SKowalski, Kamil 
225*f4c4dcf4SKowalski, Kamil       if (!user_it->is_string()) {
226*f4c4dcf4SKowalski, Kamil         messages::addMessageToErrorJson(
227*f4c4dcf4SKowalski, Kamil             errJson,
228*f4c4dcf4SKowalski, Kamil             messages::propertyValueTypeError(user_it->dump(), "UserName"));
229*f4c4dcf4SKowalski, Kamil       }
230*f4c4dcf4SKowalski, Kamil 
231*f4c4dcf4SKowalski, Kamil       if (!pass_it->is_string()) {
232*f4c4dcf4SKowalski, Kamil         messages::addMessageToErrorJson(
233*f4c4dcf4SKowalski, Kamil             errJson,
234*f4c4dcf4SKowalski, Kamil             messages::propertyValueTypeError(user_it->dump(), "Password"));
235*f4c4dcf4SKowalski, Kamil       }
2362b7981f6SKowalski, Kamil 
2372b7981f6SKowalski, Kamil       return false;
2382b7981f6SKowalski, Kamil     }
2392b7981f6SKowalski, Kamil 
2402b7981f6SKowalski, Kamil     // Extract username and password
2412b7981f6SKowalski, Kamil     std::string username = user_it->get<const std::string>();
2422b7981f6SKowalski, Kamil     std::string password = pass_it->get<const std::string>();
2432b7981f6SKowalski, Kamil 
2442b7981f6SKowalski, Kamil     // Verify that required fields are not empty
2452b7981f6SKowalski, Kamil     if (username.empty() || password.empty()) {
246*f4c4dcf4SKowalski, Kamil       httpRespCode = static_cast<int>(HttpRespCode::BAD_REQUEST);
247*f4c4dcf4SKowalski, Kamil 
248*f4c4dcf4SKowalski, Kamil       if (username.empty()) {
249*f4c4dcf4SKowalski, Kamil         messages::addMessageToErrorJson(errJson,
250*f4c4dcf4SKowalski, Kamil                                         messages::propertyMissing("UserName"));
251*f4c4dcf4SKowalski, Kamil       }
252*f4c4dcf4SKowalski, Kamil 
253*f4c4dcf4SKowalski, Kamil       if (password.empty()) {
254*f4c4dcf4SKowalski, Kamil         messages::addMessageToErrorJson(errJson,
255*f4c4dcf4SKowalski, Kamil                                         messages::propertyMissing("Password"));
256*f4c4dcf4SKowalski, Kamil       }
2572b7981f6SKowalski, Kamil 
2582b7981f6SKowalski, Kamil       return false;
2592b7981f6SKowalski, Kamil     }
2602b7981f6SKowalski, Kamil 
2612b7981f6SKowalski, Kamil     // Finally - try to authenticate user
2622b7981f6SKowalski, Kamil     if (!pam_authenticate_user(username, password)) {
263*f4c4dcf4SKowalski, Kamil       httpRespCode = static_cast<int>(HttpRespCode::UNAUTHORIZED);
264*f4c4dcf4SKowalski, Kamil 
265*f4c4dcf4SKowalski, Kamil       messages::addMessageToErrorJson(
266*f4c4dcf4SKowalski, Kamil           errJson, messages::resourceAtUriUnauthorized(
267*f4c4dcf4SKowalski, Kamil                        req.url, "Invalid username or password"));
2682b7981f6SKowalski, Kamil 
2692b7981f6SKowalski, Kamil       return false;
2702b7981f6SKowalski, Kamil     }
2712b7981f6SKowalski, Kamil 
2722b7981f6SKowalski, Kamil     // User authenticated successfully
273*f4c4dcf4SKowalski, Kamil     httpRespCode = static_cast<int>(HttpRespCode::OK);
274*f4c4dcf4SKowalski, Kamil     user = username;
2752b7981f6SKowalski, Kamil 
2762b7981f6SKowalski, Kamil     return true;
2772b7981f6SKowalski, Kamil   }
2782b7981f6SKowalski, Kamil 
2792b7981f6SKowalski, Kamil   /**
2802b7981f6SKowalski, Kamil    * Member session to ensure consistency between collection's doPost and
2812b7981f6SKowalski, Kamil    * member's doGet, as they should return 100% matching data
2822b7981f6SKowalski, Kamil    */
2832b7981f6SKowalski, Kamil   Sessions memberSession;
2842b7981f6SKowalski, Kamil };
2852b7981f6SKowalski, Kamil 
2865d27b854SBorawski.Lukasz class SessionService : public Node {
2875d27b854SBorawski.Lukasz  public:
2883ebd75f7SEd Tanous   SessionService(CrowApp& app) : Node(app, "/redfish/v1/SessionService/") {
2895d27b854SBorawski.Lukasz     Node::json["@odata.type"] = "#SessionService.v1_0_2.SessionService";
2905d27b854SBorawski.Lukasz     Node::json["@odata.id"] = "/redfish/v1/SessionService/";
2915d27b854SBorawski.Lukasz     Node::json["@odata.context"] =
2925d27b854SBorawski.Lukasz         "/redfish/v1/$metadata#SessionService.SessionService";
2935d27b854SBorawski.Lukasz     Node::json["Name"] = "Session Service";
2946c233015SEd Tanous     Node::json["Id"] = "SessionService";
2955d27b854SBorawski.Lukasz     Node::json["Description"] = "Session Service";
2965d27b854SBorawski.Lukasz     Node::json["SessionTimeout"] =
2975d27b854SBorawski.Lukasz         crow::PersistentData::session_store->get_timeout_in_seconds();
2985d27b854SBorawski.Lukasz     Node::json["ServiceEnabled"] = true;
2993ebd75f7SEd Tanous 
3003ebd75f7SEd Tanous     entityPrivileges = {{crow::HTTPMethod::GET, {{"Login"}}},
3013ebd75f7SEd Tanous                         {crow::HTTPMethod::HEAD, {{"Login"}}},
3023ebd75f7SEd Tanous                         {crow::HTTPMethod::PATCH, {{"ConfigureManager"}}},
3033ebd75f7SEd Tanous                         {crow::HTTPMethod::PUT, {{"ConfigureManager"}}},
3043ebd75f7SEd Tanous                         {crow::HTTPMethod::DELETE, {{"ConfigureManager"}}},
3053ebd75f7SEd Tanous                         {crow::HTTPMethod::POST, {{"ConfigureManager"}}}};
3065d27b854SBorawski.Lukasz   }
3075d27b854SBorawski.Lukasz 
3085d27b854SBorawski.Lukasz  private:
3095d27b854SBorawski.Lukasz   void doGet(crow::response& res, const crow::request& req,
3105d27b854SBorawski.Lukasz              const std::vector<std::string>& params) override {
3115d27b854SBorawski.Lukasz     res.json_value = Node::json;
3125d27b854SBorawski.Lukasz     res.end();
3135d27b854SBorawski.Lukasz   }
3145d27b854SBorawski.Lukasz };
3155d27b854SBorawski.Lukasz 
3162b7981f6SKowalski, Kamil }  // namespace redfish
317