xref: /openbmc/bmcweb/features/redfish/lib/redfish_sessions.hpp (revision 4b1b8683d31260b3032bb9f9fcde1eadaed4e1e5)
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"
20*4b1b8683SBorawski.Lukasz #include "persistent_data_middleware.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 
35e0d918bcSEd Tanous     entityPrivileges = {
36e0d918bcSEd Tanous         {boost::beast::http::verb::get, {{"Login"}}},
37e0d918bcSEd Tanous         {boost::beast::http::verb::head, {{"Login"}}},
38e0d918bcSEd Tanous         {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
39e0d918bcSEd Tanous         {boost::beast::http::verb::put, {{"ConfigureManager"}}},
40e0d918bcSEd Tanous         {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
41e0d918bcSEd 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 =
48*4b1b8683SBorawski.Lukasz         crow::PersistentData::SessionStore::getInstance().get_session_by_uid(
49*4b1b8683SBorawski.Lukasz             params[0]);
502b7981f6SKowalski, Kamil 
512b7981f6SKowalski, Kamil     if (session == nullptr) {
52f4c4dcf4SKowalski, Kamil       messages::addMessageToErrorJson(
53f4c4dcf4SKowalski, Kamil           res.json_value, messages::resourceNotFound("Session", params[0]));
54f4c4dcf4SKowalski, Kamil 
55e0d918bcSEd Tanous       res.result(boost::beast::http::status::not_found);
562b7981f6SKowalski, Kamil       res.end();
572b7981f6SKowalski, Kamil       return;
582b7981f6SKowalski, Kamil     }
592b7981f6SKowalski, Kamil 
60c1a46bd2SBorawski.Lukasz     Node::json["Id"] = session->unique_id;
61c1a46bd2SBorawski.Lukasz     Node::json["UserName"] = session->username;
62c1a46bd2SBorawski.Lukasz     Node::json["@odata.id"] =
632b7981f6SKowalski, Kamil         "/redfish/v1/SessionService/Sessions/" + session->unique_id;
642b7981f6SKowalski, Kamil 
65c1a46bd2SBorawski.Lukasz     res.json_value = Node::json;
662b7981f6SKowalski, Kamil     res.end();
672b7981f6SKowalski, Kamil   }
682b7981f6SKowalski, Kamil 
692b7981f6SKowalski, Kamil   void doDelete(crow::response& res, const crow::request& req,
702b7981f6SKowalski, Kamil                 const std::vector<std::string>& params) override {
712b7981f6SKowalski, Kamil     // Need only 1 param which should be id of session to be deleted
722b7981f6SKowalski, Kamil     if (params.size() != 1) {
73f4c4dcf4SKowalski, Kamil       // This should be handled by crow and never happen
74f4c4dcf4SKowalski, Kamil       CROW_LOG_ERROR
75f4c4dcf4SKowalski, Kamil           << "Session DELETE has been called with invalid number of params";
76f4c4dcf4SKowalski, Kamil 
77e0d918bcSEd Tanous       res.result(boost::beast::http::status::bad_request);
78f4c4dcf4SKowalski, Kamil       messages::addMessageToErrorJson(res.json_value, messages::generalError());
79e0d918bcSEd Tanous 
802b7981f6SKowalski, Kamil       res.end();
812b7981f6SKowalski, Kamil       return;
822b7981f6SKowalski, Kamil     }
832b7981f6SKowalski, Kamil 
842b7981f6SKowalski, Kamil     auto session =
85*4b1b8683SBorawski.Lukasz         crow::PersistentData::SessionStore::getInstance().get_session_by_uid(
86*4b1b8683SBorawski.Lukasz             params[0]);
872b7981f6SKowalski, Kamil 
882b7981f6SKowalski, Kamil     if (session == nullptr) {
89f4c4dcf4SKowalski, Kamil       messages::addMessageToErrorJson(
90f4c4dcf4SKowalski, Kamil           res.json_value, messages::resourceNotFound("Session", params[0]));
91f4c4dcf4SKowalski, Kamil 
92e0d918bcSEd Tanous       res.result(boost::beast::http::status::not_found);
932b7981f6SKowalski, Kamil       res.end();
942b7981f6SKowalski, Kamil       return;
952b7981f6SKowalski, Kamil     }
962b7981f6SKowalski, Kamil 
97f4c4dcf4SKowalski, Kamil     // DELETE should return representation of object that will be removed
98f4c4dcf4SKowalski, Kamil     doGet(res, req, params);
99f4c4dcf4SKowalski, Kamil 
100*4b1b8683SBorawski.Lukasz     crow::PersistentData::SessionStore::getInstance().remove_session(session);
1012b7981f6SKowalski, Kamil   }
1022b7981f6SKowalski, Kamil 
1032b7981f6SKowalski, Kamil   /**
1042b7981f6SKowalski, Kamil    * This allows SessionCollection to reuse this class' doGet method, to
1052b7981f6SKowalski, Kamil    * maintain consistency of returned data, as Collection's doPost should return
1062b7981f6SKowalski, Kamil    * data for created member which should match member's doGet result in 100%
1072b7981f6SKowalski, Kamil    */
1082b7981f6SKowalski, Kamil   friend SessionCollection;
1092b7981f6SKowalski, Kamil };
1102b7981f6SKowalski, Kamil 
1112b7981f6SKowalski, Kamil class SessionCollection : public Node {
1122b7981f6SKowalski, Kamil  public:
11343a095abSBorawski.Lukasz   SessionCollection(CrowApp& app)
1143ebd75f7SEd Tanous       : Node(app, "/redfish/v1/SessionService/Sessions/"), memberSession(app) {
115c1a46bd2SBorawski.Lukasz     Node::json["@odata.type"] = "#SessionCollection.SessionCollection";
116c1a46bd2SBorawski.Lukasz     Node::json["@odata.id"] = "/redfish/v1/SessionService/Sessions/";
117c1a46bd2SBorawski.Lukasz     Node::json["@odata.context"] =
1182b7981f6SKowalski, Kamil         "/redfish/v1/$metadata#SessionCollection.SessionCollection";
119c1a46bd2SBorawski.Lukasz     Node::json["Name"] = "Session Collection";
120c1a46bd2SBorawski.Lukasz     Node::json["Description"] = "Session Collection";
121c1a46bd2SBorawski.Lukasz     Node::json["Members@odata.count"] = 0;
122c1a46bd2SBorawski.Lukasz     Node::json["Members"] = nlohmann::json::array();
1233ebd75f7SEd Tanous 
124e0d918bcSEd Tanous     entityPrivileges = {
125e0d918bcSEd Tanous         {boost::beast::http::verb::get, {{"Login"}}},
126e0d918bcSEd Tanous         {boost::beast::http::verb::head, {{"Login"}}},
127e0d918bcSEd Tanous         {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
128e0d918bcSEd Tanous         {boost::beast::http::verb::put, {{"ConfigureManager"}}},
129e0d918bcSEd Tanous         {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
130e0d918bcSEd Tanous         {boost::beast::http::verb::post, {}}};
1312b7981f6SKowalski, Kamil   }
1322b7981f6SKowalski, Kamil 
1332b7981f6SKowalski, Kamil  private:
1342b7981f6SKowalski, Kamil   void doGet(crow::response& res, const crow::request& req,
1352b7981f6SKowalski, Kamil              const std::vector<std::string>& params) override {
1362b7981f6SKowalski, Kamil     std::vector<const std::string*> session_ids =
137*4b1b8683SBorawski.Lukasz         crow::PersistentData::SessionStore::getInstance().get_unique_ids(
1382b7981f6SKowalski, Kamil             false, crow::PersistentData::PersistenceType::TIMEOUT);
1392b7981f6SKowalski, Kamil 
140c1a46bd2SBorawski.Lukasz     Node::json["Members@odata.count"] = session_ids.size();
141c1a46bd2SBorawski.Lukasz     Node::json["Members"] = nlohmann::json::array();
142e0d918bcSEd Tanous     for (const std::string* uid : session_ids) {
143c1a46bd2SBorawski.Lukasz       Node::json["Members"].push_back(
1442b7981f6SKowalski, Kamil           {{"@odata.id", "/redfish/v1/SessionService/Sessions/" + *uid}});
1452b7981f6SKowalski, Kamil     }
1462b7981f6SKowalski, Kamil 
147c1a46bd2SBorawski.Lukasz     res.json_value = Node::json;
1482b7981f6SKowalski, Kamil     res.end();
1492b7981f6SKowalski, Kamil   }
1502b7981f6SKowalski, Kamil 
1512b7981f6SKowalski, Kamil   void doPost(crow::response& res, const crow::request& req,
1522b7981f6SKowalski, Kamil               const std::vector<std::string>& params) override {
153e0d918bcSEd Tanous     boost::beast::http::status status;
1542b7981f6SKowalski, Kamil     std::string username;
155f4c4dcf4SKowalski, Kamil     bool userAuthSuccessful =
156e0d918bcSEd Tanous         authenticateUser(req, status, username, res.json_value);
157e0d918bcSEd Tanous     res.result(status);
158e0d918bcSEd Tanous 
1592b7981f6SKowalski, Kamil     if (!userAuthSuccessful) {
1602b7981f6SKowalski, Kamil       res.end();
1612b7981f6SKowalski, Kamil       return;
1622b7981f6SKowalski, Kamil     }
1632b7981f6SKowalski, Kamil 
1642b7981f6SKowalski, Kamil     // User is authenticated - create session for him
1652b7981f6SKowalski, Kamil     auto session =
166*4b1b8683SBorawski.Lukasz         crow::PersistentData::SessionStore::getInstance().generate_user_session(
167*4b1b8683SBorawski.Lukasz             username);
168e0d918bcSEd Tanous     res.add_header("X-Auth-Token", session->session_token);
1692b7981f6SKowalski, Kamil 
1702b7981f6SKowalski, Kamil     // Return data for created session
171e0d918bcSEd Tanous     memberSession.doGet(res, req, {session->unique_id});
1722b7981f6SKowalski, Kamil 
1732b7981f6SKowalski, Kamil     // No need for res.end(), as it is called by doGet()
1742b7981f6SKowalski, Kamil   }
1752b7981f6SKowalski, Kamil 
1762b7981f6SKowalski, Kamil   /**
1772b7981f6SKowalski, Kamil    * @brief Verifies data provided in request and tries to authenticate user
1782b7981f6SKowalski, Kamil    *
1792b7981f6SKowalski, Kamil    * @param[in]  req            Crow request containing authentication data
1802b7981f6SKowalski, Kamil    * @param[out] httpRespCode   HTTP Code that should be returned in response
1812b7981f6SKowalski, Kamil    * @param[out] user           Retrieved username - not filled on failure
182f4c4dcf4SKowalski, Kamil    * @param[out] errJson        JSON to which error messages will be written
1832b7981f6SKowalski, Kamil    *
1842b7981f6SKowalski, Kamil    * @return true if authentication was successful, false otherwise
1852b7981f6SKowalski, Kamil    */
186e0d918bcSEd Tanous   bool authenticateUser(const crow::request& req,
187e0d918bcSEd Tanous                         boost::beast::http::status& httpRespCode,
188f4c4dcf4SKowalski, Kamil                         std::string& user, nlohmann::json& errJson) {
1892b7981f6SKowalski, Kamil     // We need only UserName and Password - nothing more, nothing less
1902b7981f6SKowalski, Kamil     static constexpr const unsigned int numberOfRequiredFieldsInReq = 2;
1912b7981f6SKowalski, Kamil 
1922b7981f6SKowalski, Kamil     // call with exceptions disabled
1932b7981f6SKowalski, Kamil     auto login_credentials = nlohmann::json::parse(req.body, nullptr, false);
1942b7981f6SKowalski, Kamil     if (login_credentials.is_discarded()) {
195e0d918bcSEd Tanous       httpRespCode = boost::beast::http::status::bad_request;
196f4c4dcf4SKowalski, Kamil 
197f4c4dcf4SKowalski, Kamil       messages::addMessageToErrorJson(errJson, messages::malformedJSON());
1982b7981f6SKowalski, Kamil 
1992b7981f6SKowalski, Kamil       return false;
2002b7981f6SKowalski, Kamil     }
2012b7981f6SKowalski, Kamil 
2022b7981f6SKowalski, Kamil     // Check that there are only as many fields as there should be
2032b7981f6SKowalski, Kamil     if (login_credentials.size() != numberOfRequiredFieldsInReq) {
204e0d918bcSEd Tanous       httpRespCode = boost::beast::http::status::bad_request;
205f4c4dcf4SKowalski, Kamil 
206f4c4dcf4SKowalski, Kamil       messages::addMessageToErrorJson(errJson, messages::malformedJSON());
2072b7981f6SKowalski, Kamil 
2082b7981f6SKowalski, Kamil       return false;
2092b7981f6SKowalski, Kamil     }
2102b7981f6SKowalski, Kamil 
2112b7981f6SKowalski, Kamil     // Find fields that we need - UserName and Password
2122b7981f6SKowalski, Kamil     auto user_it = login_credentials.find("UserName");
2132b7981f6SKowalski, Kamil     auto pass_it = login_credentials.find("Password");
2142b7981f6SKowalski, Kamil     if (user_it == login_credentials.end() ||
2152b7981f6SKowalski, Kamil         pass_it == login_credentials.end()) {
216e0d918bcSEd Tanous       httpRespCode = boost::beast::http::status::bad_request;
217f4c4dcf4SKowalski, Kamil 
218f4c4dcf4SKowalski, Kamil       if (user_it == login_credentials.end()) {
219f4c4dcf4SKowalski, Kamil         messages::addMessageToErrorJson(errJson,
220f4c4dcf4SKowalski, Kamil                                         messages::propertyMissing("UserName"));
221f4c4dcf4SKowalski, Kamil       }
222f4c4dcf4SKowalski, Kamil 
223f4c4dcf4SKowalski, Kamil       if (pass_it == login_credentials.end()) {
224f4c4dcf4SKowalski, Kamil         messages::addMessageToErrorJson(errJson,
225f4c4dcf4SKowalski, Kamil                                         messages::propertyMissing("Password"));
226f4c4dcf4SKowalski, Kamil       }
2272b7981f6SKowalski, Kamil 
2282b7981f6SKowalski, Kamil       return false;
2292b7981f6SKowalski, Kamil     }
2302b7981f6SKowalski, Kamil 
2312b7981f6SKowalski, Kamil     // Check that given data is of valid type (string)
2322b7981f6SKowalski, Kamil     if (!user_it->is_string() || !pass_it->is_string()) {
233e0d918bcSEd Tanous       httpRespCode = boost::beast::http::status::bad_request;
234f4c4dcf4SKowalski, Kamil 
235f4c4dcf4SKowalski, Kamil       if (!user_it->is_string()) {
236f4c4dcf4SKowalski, Kamil         messages::addMessageToErrorJson(
237f4c4dcf4SKowalski, Kamil             errJson,
238f4c4dcf4SKowalski, Kamil             messages::propertyValueTypeError(user_it->dump(), "UserName"));
239f4c4dcf4SKowalski, Kamil       }
240f4c4dcf4SKowalski, Kamil 
241f4c4dcf4SKowalski, Kamil       if (!pass_it->is_string()) {
242f4c4dcf4SKowalski, Kamil         messages::addMessageToErrorJson(
243f4c4dcf4SKowalski, Kamil             errJson,
244f4c4dcf4SKowalski, Kamil             messages::propertyValueTypeError(user_it->dump(), "Password"));
245f4c4dcf4SKowalski, Kamil       }
2462b7981f6SKowalski, Kamil 
2472b7981f6SKowalski, Kamil       return false;
2482b7981f6SKowalski, Kamil     }
2492b7981f6SKowalski, Kamil 
2502b7981f6SKowalski, Kamil     // Extract username and password
2512b7981f6SKowalski, Kamil     std::string username = user_it->get<const std::string>();
2522b7981f6SKowalski, Kamil     std::string password = pass_it->get<const std::string>();
2532b7981f6SKowalski, Kamil 
2542b7981f6SKowalski, Kamil     // Verify that required fields are not empty
2552b7981f6SKowalski, Kamil     if (username.empty() || password.empty()) {
256e0d918bcSEd Tanous       httpRespCode = boost::beast::http::status::bad_request;
257f4c4dcf4SKowalski, Kamil 
258f4c4dcf4SKowalski, Kamil       if (username.empty()) {
259f4c4dcf4SKowalski, Kamil         messages::addMessageToErrorJson(errJson,
260f4c4dcf4SKowalski, Kamil                                         messages::propertyMissing("UserName"));
261f4c4dcf4SKowalski, Kamil       }
262f4c4dcf4SKowalski, Kamil 
263f4c4dcf4SKowalski, Kamil       if (password.empty()) {
264f4c4dcf4SKowalski, Kamil         messages::addMessageToErrorJson(errJson,
265f4c4dcf4SKowalski, Kamil                                         messages::propertyMissing("Password"));
266f4c4dcf4SKowalski, Kamil       }
2672b7981f6SKowalski, Kamil 
2682b7981f6SKowalski, Kamil       return false;
2692b7981f6SKowalski, Kamil     }
2702b7981f6SKowalski, Kamil 
2712b7981f6SKowalski, Kamil     // Finally - try to authenticate user
2722b7981f6SKowalski, Kamil     if (!pam_authenticate_user(username, password)) {
273e0d918bcSEd Tanous       httpRespCode = boost::beast::http::status::unauthorized;
274f4c4dcf4SKowalski, Kamil 
275f4c4dcf4SKowalski, Kamil       messages::addMessageToErrorJson(
276f4c4dcf4SKowalski, Kamil           errJson, messages::resourceAtUriUnauthorized(
277e0d918bcSEd Tanous                        std::string(req.url), "Invalid username or password"));
2782b7981f6SKowalski, Kamil 
2792b7981f6SKowalski, Kamil       return false;
2802b7981f6SKowalski, Kamil     }
2812b7981f6SKowalski, Kamil 
2822b7981f6SKowalski, Kamil     // User authenticated successfully
283e0d918bcSEd Tanous     httpRespCode = boost::beast::http::status::ok;
284f4c4dcf4SKowalski, Kamil     user = username;
2852b7981f6SKowalski, Kamil 
2862b7981f6SKowalski, Kamil     return true;
2872b7981f6SKowalski, Kamil   }
2882b7981f6SKowalski, Kamil 
2892b7981f6SKowalski, Kamil   /**
2902b7981f6SKowalski, Kamil    * Member session to ensure consistency between collection's doPost and
2912b7981f6SKowalski, Kamil    * member's doGet, as they should return 100% matching data
2922b7981f6SKowalski, Kamil    */
2932b7981f6SKowalski, Kamil   Sessions memberSession;
2942b7981f6SKowalski, Kamil };
2952b7981f6SKowalski, Kamil 
2965d27b854SBorawski.Lukasz class SessionService : public Node {
2975d27b854SBorawski.Lukasz  public:
2983ebd75f7SEd Tanous   SessionService(CrowApp& app) : Node(app, "/redfish/v1/SessionService/") {
2995d27b854SBorawski.Lukasz     Node::json["@odata.type"] = "#SessionService.v1_0_2.SessionService";
3005d27b854SBorawski.Lukasz     Node::json["@odata.id"] = "/redfish/v1/SessionService/";
3015d27b854SBorawski.Lukasz     Node::json["@odata.context"] =
3025d27b854SBorawski.Lukasz         "/redfish/v1/$metadata#SessionService.SessionService";
3035d27b854SBorawski.Lukasz     Node::json["Name"] = "Session Service";
3046c233015SEd Tanous     Node::json["Id"] = "SessionService";
3055d27b854SBorawski.Lukasz     Node::json["Description"] = "Session Service";
3065d27b854SBorawski.Lukasz     Node::json["SessionTimeout"] =
307*4b1b8683SBorawski.Lukasz         crow::PersistentData::SessionStore::getInstance()
308*4b1b8683SBorawski.Lukasz             .get_timeout_in_seconds();
3095d27b854SBorawski.Lukasz     Node::json["ServiceEnabled"] = true;
3103ebd75f7SEd Tanous 
311e0d918bcSEd Tanous     entityPrivileges = {
312e0d918bcSEd Tanous         {boost::beast::http::verb::get, {{"Login"}}},
313e0d918bcSEd Tanous         {boost::beast::http::verb::head, {{"Login"}}},
314e0d918bcSEd Tanous         {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
315e0d918bcSEd Tanous         {boost::beast::http::verb::put, {{"ConfigureManager"}}},
316e0d918bcSEd Tanous         {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
317e0d918bcSEd Tanous         {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
3185d27b854SBorawski.Lukasz   }
3195d27b854SBorawski.Lukasz 
3205d27b854SBorawski.Lukasz  private:
3215d27b854SBorawski.Lukasz   void doGet(crow::response& res, const crow::request& req,
3225d27b854SBorawski.Lukasz              const std::vector<std::string>& params) override {
3235d27b854SBorawski.Lukasz     res.json_value = Node::json;
3245d27b854SBorawski.Lukasz     res.end();
3255d27b854SBorawski.Lukasz   }
3265d27b854SBorawski.Lukasz };
3275d27b854SBorawski.Lukasz 
3282b7981f6SKowalski, Kamil }  // namespace redfish
329