xref: /openbmc/bmcweb/features/redfish/lib/redfish_sessions.hpp (revision e0d918bc397350aa21af3dab9faa6e21748f6373)
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 
18f4c4dcf4SKowalski, 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 
35*e0d918bcSEd Tanous     entityPrivileges = {
36*e0d918bcSEd Tanous         {boost::beast::http::verb::get, {{"Login"}}},
37*e0d918bcSEd Tanous         {boost::beast::http::verb::head, {{"Login"}}},
38*e0d918bcSEd Tanous         {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
39*e0d918bcSEd Tanous         {boost::beast::http::verb::put, {{"ConfigureManager"}}},
40*e0d918bcSEd Tanous         {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
41*e0d918bcSEd Tanous         {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
422b7981f6SKowalski, Kamil   }
432b7981f6SKowalski, Kamil 
442b7981f6SKowalski, Kamil  private:
452b7981f6SKowalski, Kamil   void doGet(crow::response& res, const crow::request& req,
462b7981f6SKowalski, Kamil              const std::vector<std::string>& params) override {
472b7981f6SKowalski, Kamil     auto session =
482b7981f6SKowalski, Kamil         crow::PersistentData::session_store->get_session_by_uid(params[0]);
492b7981f6SKowalski, Kamil 
502b7981f6SKowalski, Kamil     if (session == nullptr) {
51f4c4dcf4SKowalski, Kamil       messages::addMessageToErrorJson(
52f4c4dcf4SKowalski, Kamil           res.json_value, messages::resourceNotFound("Session", params[0]));
53f4c4dcf4SKowalski, Kamil 
54*e0d918bcSEd Tanous       res.result(boost::beast::http::status::not_found);
552b7981f6SKowalski, Kamil       res.end();
562b7981f6SKowalski, Kamil       return;
572b7981f6SKowalski, Kamil     }
582b7981f6SKowalski, Kamil 
59c1a46bd2SBorawski.Lukasz     Node::json["Id"] = session->unique_id;
60c1a46bd2SBorawski.Lukasz     Node::json["UserName"] = session->username;
61c1a46bd2SBorawski.Lukasz     Node::json["@odata.id"] =
622b7981f6SKowalski, Kamil         "/redfish/v1/SessionService/Sessions/" + session->unique_id;
632b7981f6SKowalski, Kamil 
64c1a46bd2SBorawski.Lukasz     res.json_value = Node::json;
652b7981f6SKowalski, Kamil     res.end();
662b7981f6SKowalski, Kamil   }
672b7981f6SKowalski, Kamil 
682b7981f6SKowalski, Kamil   void doDelete(crow::response& res, const crow::request& req,
692b7981f6SKowalski, Kamil                 const std::vector<std::string>& params) override {
702b7981f6SKowalski, Kamil     // Need only 1 param which should be id of session to be deleted
712b7981f6SKowalski, Kamil     if (params.size() != 1) {
72f4c4dcf4SKowalski, Kamil       // This should be handled by crow and never happen
73f4c4dcf4SKowalski, Kamil       CROW_LOG_ERROR
74f4c4dcf4SKowalski, Kamil           << "Session DELETE has been called with invalid number of params";
75f4c4dcf4SKowalski, Kamil 
76*e0d918bcSEd Tanous       res.result(boost::beast::http::status::bad_request);
77f4c4dcf4SKowalski, Kamil       messages::addMessageToErrorJson(res.json_value, messages::generalError());
78*e0d918bcSEd Tanous 
792b7981f6SKowalski, Kamil       res.end();
802b7981f6SKowalski, Kamil       return;
812b7981f6SKowalski, Kamil     }
822b7981f6SKowalski, Kamil 
832b7981f6SKowalski, Kamil     auto session =
842b7981f6SKowalski, Kamil         crow::PersistentData::session_store->get_session_by_uid(params[0]);
852b7981f6SKowalski, Kamil 
862b7981f6SKowalski, Kamil     if (session == nullptr) {
87f4c4dcf4SKowalski, Kamil       messages::addMessageToErrorJson(
88f4c4dcf4SKowalski, Kamil           res.json_value, messages::resourceNotFound("Session", params[0]));
89f4c4dcf4SKowalski, Kamil 
90*e0d918bcSEd Tanous       res.result(boost::beast::http::status::not_found);
912b7981f6SKowalski, Kamil       res.end();
922b7981f6SKowalski, Kamil       return;
932b7981f6SKowalski, Kamil     }
942b7981f6SKowalski, Kamil 
95f4c4dcf4SKowalski, Kamil     // DELETE should return representation of object that will be removed
96f4c4dcf4SKowalski, Kamil     doGet(res, req, params);
97f4c4dcf4SKowalski, Kamil 
982b7981f6SKowalski, Kamil     crow::PersistentData::session_store->remove_session(session);
992b7981f6SKowalski, Kamil   }
1002b7981f6SKowalski, Kamil 
1012b7981f6SKowalski, Kamil   /**
1022b7981f6SKowalski, Kamil    * This allows SessionCollection to reuse this class' doGet method, to
1032b7981f6SKowalski, Kamil    * maintain consistency of returned data, as Collection's doPost should return
1042b7981f6SKowalski, Kamil    * data for created member which should match member's doGet result in 100%
1052b7981f6SKowalski, Kamil    */
1062b7981f6SKowalski, Kamil   friend SessionCollection;
1072b7981f6SKowalski, Kamil };
1082b7981f6SKowalski, Kamil 
1092b7981f6SKowalski, Kamil class SessionCollection : public Node {
1102b7981f6SKowalski, Kamil  public:
11143a095abSBorawski.Lukasz   SessionCollection(CrowApp& app)
1123ebd75f7SEd Tanous       : Node(app, "/redfish/v1/SessionService/Sessions/"), memberSession(app) {
113c1a46bd2SBorawski.Lukasz     Node::json["@odata.type"] = "#SessionCollection.SessionCollection";
114c1a46bd2SBorawski.Lukasz     Node::json["@odata.id"] = "/redfish/v1/SessionService/Sessions/";
115c1a46bd2SBorawski.Lukasz     Node::json["@odata.context"] =
1162b7981f6SKowalski, Kamil         "/redfish/v1/$metadata#SessionCollection.SessionCollection";
117c1a46bd2SBorawski.Lukasz     Node::json["Name"] = "Session Collection";
118c1a46bd2SBorawski.Lukasz     Node::json["Description"] = "Session Collection";
119c1a46bd2SBorawski.Lukasz     Node::json["Members@odata.count"] = 0;
120c1a46bd2SBorawski.Lukasz     Node::json["Members"] = nlohmann::json::array();
1213ebd75f7SEd Tanous 
122*e0d918bcSEd Tanous     entityPrivileges = {
123*e0d918bcSEd Tanous         {boost::beast::http::verb::get, {{"Login"}}},
124*e0d918bcSEd Tanous         {boost::beast::http::verb::head, {{"Login"}}},
125*e0d918bcSEd Tanous         {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
126*e0d918bcSEd Tanous         {boost::beast::http::verb::put, {{"ConfigureManager"}}},
127*e0d918bcSEd Tanous         {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
128*e0d918bcSEd Tanous         {boost::beast::http::verb::post, {}}};
1292b7981f6SKowalski, Kamil   }
1302b7981f6SKowalski, Kamil 
1312b7981f6SKowalski, Kamil  private:
1322b7981f6SKowalski, Kamil   void doGet(crow::response& res, const crow::request& req,
1332b7981f6SKowalski, Kamil              const std::vector<std::string>& params) override {
1342b7981f6SKowalski, Kamil     std::vector<const std::string*> session_ids =
1352b7981f6SKowalski, Kamil         crow::PersistentData::session_store->get_unique_ids(
1362b7981f6SKowalski, Kamil             false, crow::PersistentData::PersistenceType::TIMEOUT);
1372b7981f6SKowalski, Kamil 
138c1a46bd2SBorawski.Lukasz     Node::json["Members@odata.count"] = session_ids.size();
139c1a46bd2SBorawski.Lukasz     Node::json["Members"] = nlohmann::json::array();
140*e0d918bcSEd Tanous     for (const std::string* uid : session_ids) {
141c1a46bd2SBorawski.Lukasz       Node::json["Members"].push_back(
1422b7981f6SKowalski, Kamil           {{"@odata.id", "/redfish/v1/SessionService/Sessions/" + *uid}});
1432b7981f6SKowalski, Kamil     }
1442b7981f6SKowalski, Kamil 
145c1a46bd2SBorawski.Lukasz     res.json_value = Node::json;
1462b7981f6SKowalski, Kamil     res.end();
1472b7981f6SKowalski, Kamil   }
1482b7981f6SKowalski, Kamil 
1492b7981f6SKowalski, Kamil   void doPost(crow::response& res, const crow::request& req,
1502b7981f6SKowalski, Kamil               const std::vector<std::string>& params) override {
151*e0d918bcSEd Tanous     boost::beast::http::status status;
1522b7981f6SKowalski, Kamil     std::string username;
153f4c4dcf4SKowalski, Kamil     bool userAuthSuccessful =
154*e0d918bcSEd Tanous         authenticateUser(req, status, username, res.json_value);
155*e0d918bcSEd Tanous     res.result(status);
156*e0d918bcSEd Tanous 
1572b7981f6SKowalski, Kamil     if (!userAuthSuccessful) {
1582b7981f6SKowalski, Kamil       res.end();
1592b7981f6SKowalski, Kamil       return;
1602b7981f6SKowalski, Kamil     }
1612b7981f6SKowalski, Kamil 
1622b7981f6SKowalski, Kamil     // User is authenticated - create session for him
1632b7981f6SKowalski, Kamil     auto session =
1642b7981f6SKowalski, Kamil         crow::PersistentData::session_store->generate_user_session(username);
165*e0d918bcSEd Tanous     res.add_header("X-Auth-Token", session->session_token);
1662b7981f6SKowalski, Kamil 
1672b7981f6SKowalski, Kamil     // Return data for created session
168*e0d918bcSEd Tanous     memberSession.doGet(res, req, {session->unique_id});
1692b7981f6SKowalski, Kamil 
1702b7981f6SKowalski, Kamil     // No need for res.end(), as it is called by doGet()
1712b7981f6SKowalski, Kamil   }
1722b7981f6SKowalski, Kamil 
1732b7981f6SKowalski, Kamil   /**
1742b7981f6SKowalski, Kamil    * @brief Verifies data provided in request and tries to authenticate user
1752b7981f6SKowalski, Kamil    *
1762b7981f6SKowalski, Kamil    * @param[in]  req            Crow request containing authentication data
1772b7981f6SKowalski, Kamil    * @param[out] httpRespCode   HTTP Code that should be returned in response
1782b7981f6SKowalski, Kamil    * @param[out] user           Retrieved username - not filled on failure
179f4c4dcf4SKowalski, Kamil    * @param[out] errJson        JSON to which error messages will be written
1802b7981f6SKowalski, Kamil    *
1812b7981f6SKowalski, Kamil    * @return true if authentication was successful, false otherwise
1822b7981f6SKowalski, Kamil    */
183*e0d918bcSEd Tanous   bool authenticateUser(const crow::request& req,
184*e0d918bcSEd Tanous                         boost::beast::http::status& httpRespCode,
185f4c4dcf4SKowalski, Kamil                         std::string& user, nlohmann::json& errJson) {
1862b7981f6SKowalski, Kamil     // We need only UserName and Password - nothing more, nothing less
1872b7981f6SKowalski, Kamil     static constexpr const unsigned int numberOfRequiredFieldsInReq = 2;
1882b7981f6SKowalski, Kamil 
1892b7981f6SKowalski, Kamil     // call with exceptions disabled
1902b7981f6SKowalski, Kamil     auto login_credentials = nlohmann::json::parse(req.body, nullptr, false);
1912b7981f6SKowalski, Kamil     if (login_credentials.is_discarded()) {
192*e0d918bcSEd Tanous       httpRespCode = boost::beast::http::status::bad_request;
193f4c4dcf4SKowalski, Kamil 
194f4c4dcf4SKowalski, Kamil       messages::addMessageToErrorJson(errJson, messages::malformedJSON());
1952b7981f6SKowalski, Kamil 
1962b7981f6SKowalski, Kamil       return false;
1972b7981f6SKowalski, Kamil     }
1982b7981f6SKowalski, Kamil 
1992b7981f6SKowalski, Kamil     // Check that there are only as many fields as there should be
2002b7981f6SKowalski, Kamil     if (login_credentials.size() != numberOfRequiredFieldsInReq) {
201*e0d918bcSEd Tanous       httpRespCode = boost::beast::http::status::bad_request;
202f4c4dcf4SKowalski, Kamil 
203f4c4dcf4SKowalski, Kamil       messages::addMessageToErrorJson(errJson, messages::malformedJSON());
2042b7981f6SKowalski, Kamil 
2052b7981f6SKowalski, Kamil       return false;
2062b7981f6SKowalski, Kamil     }
2072b7981f6SKowalski, Kamil 
2082b7981f6SKowalski, Kamil     // Find fields that we need - UserName and Password
2092b7981f6SKowalski, Kamil     auto user_it = login_credentials.find("UserName");
2102b7981f6SKowalski, Kamil     auto pass_it = login_credentials.find("Password");
2112b7981f6SKowalski, Kamil     if (user_it == login_credentials.end() ||
2122b7981f6SKowalski, Kamil         pass_it == login_credentials.end()) {
213*e0d918bcSEd Tanous       httpRespCode = boost::beast::http::status::bad_request;
214f4c4dcf4SKowalski, Kamil 
215f4c4dcf4SKowalski, Kamil       if (user_it == login_credentials.end()) {
216f4c4dcf4SKowalski, Kamil         messages::addMessageToErrorJson(errJson,
217f4c4dcf4SKowalski, Kamil                                         messages::propertyMissing("UserName"));
218f4c4dcf4SKowalski, Kamil       }
219f4c4dcf4SKowalski, Kamil 
220f4c4dcf4SKowalski, Kamil       if (pass_it == login_credentials.end()) {
221f4c4dcf4SKowalski, Kamil         messages::addMessageToErrorJson(errJson,
222f4c4dcf4SKowalski, Kamil                                         messages::propertyMissing("Password"));
223f4c4dcf4SKowalski, Kamil       }
2242b7981f6SKowalski, Kamil 
2252b7981f6SKowalski, Kamil       return false;
2262b7981f6SKowalski, Kamil     }
2272b7981f6SKowalski, Kamil 
2282b7981f6SKowalski, Kamil     // Check that given data is of valid type (string)
2292b7981f6SKowalski, Kamil     if (!user_it->is_string() || !pass_it->is_string()) {
230*e0d918bcSEd Tanous       httpRespCode = boost::beast::http::status::bad_request;
231f4c4dcf4SKowalski, Kamil 
232f4c4dcf4SKowalski, Kamil       if (!user_it->is_string()) {
233f4c4dcf4SKowalski, Kamil         messages::addMessageToErrorJson(
234f4c4dcf4SKowalski, Kamil             errJson,
235f4c4dcf4SKowalski, Kamil             messages::propertyValueTypeError(user_it->dump(), "UserName"));
236f4c4dcf4SKowalski, Kamil       }
237f4c4dcf4SKowalski, Kamil 
238f4c4dcf4SKowalski, Kamil       if (!pass_it->is_string()) {
239f4c4dcf4SKowalski, Kamil         messages::addMessageToErrorJson(
240f4c4dcf4SKowalski, Kamil             errJson,
241f4c4dcf4SKowalski, Kamil             messages::propertyValueTypeError(user_it->dump(), "Password"));
242f4c4dcf4SKowalski, Kamil       }
2432b7981f6SKowalski, Kamil 
2442b7981f6SKowalski, Kamil       return false;
2452b7981f6SKowalski, Kamil     }
2462b7981f6SKowalski, Kamil 
2472b7981f6SKowalski, Kamil     // Extract username and password
2482b7981f6SKowalski, Kamil     std::string username = user_it->get<const std::string>();
2492b7981f6SKowalski, Kamil     std::string password = pass_it->get<const std::string>();
2502b7981f6SKowalski, Kamil 
2512b7981f6SKowalski, Kamil     // Verify that required fields are not empty
2522b7981f6SKowalski, Kamil     if (username.empty() || password.empty()) {
253*e0d918bcSEd Tanous       httpRespCode = boost::beast::http::status::bad_request;
254f4c4dcf4SKowalski, Kamil 
255f4c4dcf4SKowalski, Kamil       if (username.empty()) {
256f4c4dcf4SKowalski, Kamil         messages::addMessageToErrorJson(errJson,
257f4c4dcf4SKowalski, Kamil                                         messages::propertyMissing("UserName"));
258f4c4dcf4SKowalski, Kamil       }
259f4c4dcf4SKowalski, Kamil 
260f4c4dcf4SKowalski, Kamil       if (password.empty()) {
261f4c4dcf4SKowalski, Kamil         messages::addMessageToErrorJson(errJson,
262f4c4dcf4SKowalski, Kamil                                         messages::propertyMissing("Password"));
263f4c4dcf4SKowalski, Kamil       }
2642b7981f6SKowalski, Kamil 
2652b7981f6SKowalski, Kamil       return false;
2662b7981f6SKowalski, Kamil     }
2672b7981f6SKowalski, Kamil 
2682b7981f6SKowalski, Kamil     // Finally - try to authenticate user
2692b7981f6SKowalski, Kamil     if (!pam_authenticate_user(username, password)) {
270*e0d918bcSEd Tanous       httpRespCode = boost::beast::http::status::unauthorized;
271f4c4dcf4SKowalski, Kamil 
272f4c4dcf4SKowalski, Kamil       messages::addMessageToErrorJson(
273f4c4dcf4SKowalski, Kamil           errJson, messages::resourceAtUriUnauthorized(
274*e0d918bcSEd Tanous                        std::string(req.url), "Invalid username or password"));
2752b7981f6SKowalski, Kamil 
2762b7981f6SKowalski, Kamil       return false;
2772b7981f6SKowalski, Kamil     }
2782b7981f6SKowalski, Kamil 
2792b7981f6SKowalski, Kamil     // User authenticated successfully
280*e0d918bcSEd Tanous     httpRespCode = boost::beast::http::status::ok;
281f4c4dcf4SKowalski, Kamil     user = username;
2822b7981f6SKowalski, Kamil 
2832b7981f6SKowalski, Kamil     return true;
2842b7981f6SKowalski, Kamil   }
2852b7981f6SKowalski, Kamil 
2862b7981f6SKowalski, Kamil   /**
2872b7981f6SKowalski, Kamil    * Member session to ensure consistency between collection's doPost and
2882b7981f6SKowalski, Kamil    * member's doGet, as they should return 100% matching data
2892b7981f6SKowalski, Kamil    */
2902b7981f6SKowalski, Kamil   Sessions memberSession;
2912b7981f6SKowalski, Kamil };
2922b7981f6SKowalski, Kamil 
2935d27b854SBorawski.Lukasz class SessionService : public Node {
2945d27b854SBorawski.Lukasz  public:
2953ebd75f7SEd Tanous   SessionService(CrowApp& app) : Node(app, "/redfish/v1/SessionService/") {
2965d27b854SBorawski.Lukasz     Node::json["@odata.type"] = "#SessionService.v1_0_2.SessionService";
2975d27b854SBorawski.Lukasz     Node::json["@odata.id"] = "/redfish/v1/SessionService/";
2985d27b854SBorawski.Lukasz     Node::json["@odata.context"] =
2995d27b854SBorawski.Lukasz         "/redfish/v1/$metadata#SessionService.SessionService";
3005d27b854SBorawski.Lukasz     Node::json["Name"] = "Session Service";
3016c233015SEd Tanous     Node::json["Id"] = "SessionService";
3025d27b854SBorawski.Lukasz     Node::json["Description"] = "Session Service";
3035d27b854SBorawski.Lukasz     Node::json["SessionTimeout"] =
3045d27b854SBorawski.Lukasz         crow::PersistentData::session_store->get_timeout_in_seconds();
3055d27b854SBorawski.Lukasz     Node::json["ServiceEnabled"] = true;
3063ebd75f7SEd Tanous 
307*e0d918bcSEd Tanous     entityPrivileges = {
308*e0d918bcSEd Tanous         {boost::beast::http::verb::get, {{"Login"}}},
309*e0d918bcSEd Tanous         {boost::beast::http::verb::head, {{"Login"}}},
310*e0d918bcSEd Tanous         {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
311*e0d918bcSEd Tanous         {boost::beast::http::verb::put, {{"ConfigureManager"}}},
312*e0d918bcSEd Tanous         {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
313*e0d918bcSEd Tanous         {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
3145d27b854SBorawski.Lukasz   }
3155d27b854SBorawski.Lukasz 
3165d27b854SBorawski.Lukasz  private:
3175d27b854SBorawski.Lukasz   void doGet(crow::response& res, const crow::request& req,
3185d27b854SBorawski.Lukasz              const std::vector<std::string>& params) override {
3195d27b854SBorawski.Lukasz     res.json_value = Node::json;
3205d27b854SBorawski.Lukasz     res.end();
3215d27b854SBorawski.Lukasz   }
3225d27b854SBorawski.Lukasz };
3235d27b854SBorawski.Lukasz 
3242b7981f6SKowalski, Kamil }  // namespace redfish
325