xref: /openbmc/bmcweb/features/redfish/lib/redfish_sessions.hpp (revision 55c7b7a2e58779580f33046d2dd8649243776700)
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"
204b1b8683SBorawski.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:
45*55c7b7a2SEd Tanous   void doGet(crow::Response& res, const crow::Request& req,
462b7981f6SKowalski, Kamil              const std::vector<std::string>& params) override {
472b7981f6SKowalski, Kamil     auto session =
48*55c7b7a2SEd Tanous         crow::persistent_data::SessionStore::getInstance().getSessionByUid(
494b1b8683SBorawski.Lukasz             params[0]);
502b7981f6SKowalski, Kamil 
512b7981f6SKowalski, Kamil     if (session == nullptr) {
52f4c4dcf4SKowalski, Kamil       messages::addMessageToErrorJson(
53*55c7b7a2SEd Tanous           res.jsonValue, 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 
60*55c7b7a2SEd Tanous     Node::json["Id"] = session->uniqueId;
61c1a46bd2SBorawski.Lukasz     Node::json["UserName"] = session->username;
62c1a46bd2SBorawski.Lukasz     Node::json["@odata.id"] =
63*55c7b7a2SEd Tanous         "/redfish/v1/SessionService/Sessions/" + session->uniqueId;
642b7981f6SKowalski, Kamil 
65*55c7b7a2SEd Tanous     res.jsonValue = Node::json;
662b7981f6SKowalski, Kamil     res.end();
672b7981f6SKowalski, Kamil   }
682b7981f6SKowalski, Kamil 
69*55c7b7a2SEd Tanous   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
74*55c7b7a2SEd Tanous       BMCWEB_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);
78*55c7b7a2SEd Tanous       messages::addMessageToErrorJson(res.jsonValue, messages::generalError());
79e0d918bcSEd Tanous 
802b7981f6SKowalski, Kamil       res.end();
812b7981f6SKowalski, Kamil       return;
822b7981f6SKowalski, Kamil     }
832b7981f6SKowalski, Kamil 
842b7981f6SKowalski, Kamil     auto session =
85*55c7b7a2SEd Tanous         crow::persistent_data::SessionStore::getInstance().getSessionByUid(
864b1b8683SBorawski.Lukasz             params[0]);
872b7981f6SKowalski, Kamil 
882b7981f6SKowalski, Kamil     if (session == nullptr) {
89f4c4dcf4SKowalski, Kamil       messages::addMessageToErrorJson(
90*55c7b7a2SEd Tanous           res.jsonValue, 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*55c7b7a2SEd Tanous     crow::persistent_data::SessionStore::getInstance().removeSession(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:
134*55c7b7a2SEd Tanous   void doGet(crow::Response& res, const crow::Request& req,
1352b7981f6SKowalski, Kamil              const std::vector<std::string>& params) override {
136*55c7b7a2SEd Tanous     std::vector<const std::string*> sessionIds =
137*55c7b7a2SEd Tanous         crow::persistent_data::SessionStore::getInstance().getUniqueIds(
138*55c7b7a2SEd Tanous             false, crow::persistent_data::PersistenceType::TIMEOUT);
1392b7981f6SKowalski, Kamil 
140*55c7b7a2SEd Tanous     Node::json["Members@odata.count"] = sessionIds.size();
141c1a46bd2SBorawski.Lukasz     Node::json["Members"] = nlohmann::json::array();
142*55c7b7a2SEd Tanous     for (const std::string* uid : sessionIds) {
143c1a46bd2SBorawski.Lukasz       Node::json["Members"].push_back(
1442b7981f6SKowalski, Kamil           {{"@odata.id", "/redfish/v1/SessionService/Sessions/" + *uid}});
1452b7981f6SKowalski, Kamil     }
1462b7981f6SKowalski, Kamil 
147*55c7b7a2SEd Tanous     res.jsonValue = Node::json;
1482b7981f6SKowalski, Kamil     res.end();
1492b7981f6SKowalski, Kamil   }
1502b7981f6SKowalski, Kamil 
151*55c7b7a2SEd Tanous   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 =
156*55c7b7a2SEd Tanous         authenticateUser(req, status, username, res.jsonValue);
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*55c7b7a2SEd Tanous         crow::persistent_data::SessionStore::getInstance().generateUserSession(
1674b1b8683SBorawski.Lukasz             username);
168*55c7b7a2SEd Tanous     res.addHeader("X-Auth-Token", session->sessionToken);
1692b7981f6SKowalski, Kamil 
1702b7981f6SKowalski, Kamil     // Return data for created session
171*55c7b7a2SEd Tanous     memberSession.doGet(res, req, {session->uniqueId});
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    */
186*55c7b7a2SEd 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
193*55c7b7a2SEd Tanous     auto loginCredentials = nlohmann::json::parse(req.body, nullptr, false);
194*55c7b7a2SEd Tanous     if (loginCredentials.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
203*55c7b7a2SEd Tanous     if (loginCredentials.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
212*55c7b7a2SEd Tanous     auto userIt = loginCredentials.find("UserName");
213*55c7b7a2SEd Tanous     auto passIt = loginCredentials.find("Password");
214*55c7b7a2SEd Tanous     if (userIt == loginCredentials.end() || passIt == loginCredentials.end()) {
215e0d918bcSEd Tanous       httpRespCode = boost::beast::http::status::bad_request;
216f4c4dcf4SKowalski, Kamil 
217*55c7b7a2SEd Tanous       if (userIt == loginCredentials.end()) {
218f4c4dcf4SKowalski, Kamil         messages::addMessageToErrorJson(errJson,
219f4c4dcf4SKowalski, Kamil                                         messages::propertyMissing("UserName"));
220f4c4dcf4SKowalski, Kamil       }
221f4c4dcf4SKowalski, Kamil 
222*55c7b7a2SEd Tanous       if (passIt == loginCredentials.end()) {
223f4c4dcf4SKowalski, Kamil         messages::addMessageToErrorJson(errJson,
224f4c4dcf4SKowalski, Kamil                                         messages::propertyMissing("Password"));
225f4c4dcf4SKowalski, Kamil       }
2262b7981f6SKowalski, Kamil 
2272b7981f6SKowalski, Kamil       return false;
2282b7981f6SKowalski, Kamil     }
2292b7981f6SKowalski, Kamil 
2302b7981f6SKowalski, Kamil     // Check that given data is of valid type (string)
231*55c7b7a2SEd Tanous     if (!userIt->is_string() || !passIt->is_string()) {
232e0d918bcSEd Tanous       httpRespCode = boost::beast::http::status::bad_request;
233f4c4dcf4SKowalski, Kamil 
234*55c7b7a2SEd Tanous       if (!userIt->is_string()) {
235f4c4dcf4SKowalski, Kamil         messages::addMessageToErrorJson(
236f4c4dcf4SKowalski, Kamil             errJson,
237*55c7b7a2SEd Tanous             messages::propertyValueTypeError(userIt->dump(), "UserName"));
238f4c4dcf4SKowalski, Kamil       }
239f4c4dcf4SKowalski, Kamil 
240*55c7b7a2SEd Tanous       if (!passIt->is_string()) {
241f4c4dcf4SKowalski, Kamil         messages::addMessageToErrorJson(
242f4c4dcf4SKowalski, Kamil             errJson,
243*55c7b7a2SEd Tanous             messages::propertyValueTypeError(userIt->dump(), "Password"));
244f4c4dcf4SKowalski, Kamil       }
2452b7981f6SKowalski, Kamil 
2462b7981f6SKowalski, Kamil       return false;
2472b7981f6SKowalski, Kamil     }
2482b7981f6SKowalski, Kamil 
2492b7981f6SKowalski, Kamil     // Extract username and password
250*55c7b7a2SEd Tanous     std::string username = userIt->get<const std::string>();
251*55c7b7a2SEd Tanous     std::string password = passIt->get<const std::string>();
2522b7981f6SKowalski, Kamil 
2532b7981f6SKowalski, Kamil     // Verify that required fields are not empty
2542b7981f6SKowalski, Kamil     if (username.empty() || password.empty()) {
255e0d918bcSEd Tanous       httpRespCode = boost::beast::http::status::bad_request;
256f4c4dcf4SKowalski, Kamil 
257f4c4dcf4SKowalski, Kamil       if (username.empty()) {
258f4c4dcf4SKowalski, Kamil         messages::addMessageToErrorJson(errJson,
259f4c4dcf4SKowalski, Kamil                                         messages::propertyMissing("UserName"));
260f4c4dcf4SKowalski, Kamil       }
261f4c4dcf4SKowalski, Kamil 
262f4c4dcf4SKowalski, Kamil       if (password.empty()) {
263f4c4dcf4SKowalski, Kamil         messages::addMessageToErrorJson(errJson,
264f4c4dcf4SKowalski, Kamil                                         messages::propertyMissing("Password"));
265f4c4dcf4SKowalski, Kamil       }
2662b7981f6SKowalski, Kamil 
2672b7981f6SKowalski, Kamil       return false;
2682b7981f6SKowalski, Kamil     }
2692b7981f6SKowalski, Kamil 
2702b7981f6SKowalski, Kamil     // Finally - try to authenticate user
271*55c7b7a2SEd Tanous     if (!pamAuthenticateUser(username, password)) {
272e0d918bcSEd Tanous       httpRespCode = boost::beast::http::status::unauthorized;
273f4c4dcf4SKowalski, Kamil 
274f4c4dcf4SKowalski, Kamil       messages::addMessageToErrorJson(
275f4c4dcf4SKowalski, Kamil           errJson, messages::resourceAtUriUnauthorized(
276e0d918bcSEd Tanous                        std::string(req.url), "Invalid username or password"));
2772b7981f6SKowalski, Kamil 
2782b7981f6SKowalski, Kamil       return false;
2792b7981f6SKowalski, Kamil     }
2802b7981f6SKowalski, Kamil 
2812b7981f6SKowalski, Kamil     // User authenticated successfully
282e0d918bcSEd Tanous     httpRespCode = boost::beast::http::status::ok;
283f4c4dcf4SKowalski, Kamil     user = username;
2842b7981f6SKowalski, Kamil 
2852b7981f6SKowalski, Kamil     return true;
2862b7981f6SKowalski, Kamil   }
2872b7981f6SKowalski, Kamil 
2882b7981f6SKowalski, Kamil   /**
2892b7981f6SKowalski, Kamil    * Member session to ensure consistency between collection's doPost and
2902b7981f6SKowalski, Kamil    * member's doGet, as they should return 100% matching data
2912b7981f6SKowalski, Kamil    */
2922b7981f6SKowalski, Kamil   Sessions memberSession;
2932b7981f6SKowalski, Kamil };
2942b7981f6SKowalski, Kamil 
2955d27b854SBorawski.Lukasz class SessionService : public Node {
2965d27b854SBorawski.Lukasz  public:
2973ebd75f7SEd Tanous   SessionService(CrowApp& app) : Node(app, "/redfish/v1/SessionService/") {
2985d27b854SBorawski.Lukasz     Node::json["@odata.type"] = "#SessionService.v1_0_2.SessionService";
2995d27b854SBorawski.Lukasz     Node::json["@odata.id"] = "/redfish/v1/SessionService/";
3005d27b854SBorawski.Lukasz     Node::json["@odata.context"] =
3015d27b854SBorawski.Lukasz         "/redfish/v1/$metadata#SessionService.SessionService";
3025d27b854SBorawski.Lukasz     Node::json["Name"] = "Session Service";
3036c233015SEd Tanous     Node::json["Id"] = "SessionService";
3045d27b854SBorawski.Lukasz     Node::json["Description"] = "Session Service";
3055d27b854SBorawski.Lukasz     Node::json["SessionTimeout"] =
306*55c7b7a2SEd Tanous         crow::persistent_data::SessionStore::getInstance()
307*55c7b7a2SEd Tanous             .getTimeoutInSeconds();
3085d27b854SBorawski.Lukasz     Node::json["ServiceEnabled"] = true;
3093ebd75f7SEd Tanous 
310e0d918bcSEd Tanous     entityPrivileges = {
311e0d918bcSEd Tanous         {boost::beast::http::verb::get, {{"Login"}}},
312e0d918bcSEd Tanous         {boost::beast::http::verb::head, {{"Login"}}},
313e0d918bcSEd Tanous         {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
314e0d918bcSEd Tanous         {boost::beast::http::verb::put, {{"ConfigureManager"}}},
315e0d918bcSEd Tanous         {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
316e0d918bcSEd Tanous         {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
3175d27b854SBorawski.Lukasz   }
3185d27b854SBorawski.Lukasz 
3195d27b854SBorawski.Lukasz  private:
320*55c7b7a2SEd Tanous   void doGet(crow::Response& res, const crow::Request& req,
3215d27b854SBorawski.Lukasz              const std::vector<std::string>& params) override {
322*55c7b7a2SEd Tanous     res.jsonValue = Node::json;
3235d27b854SBorawski.Lukasz     res.end();
3245d27b854SBorawski.Lukasz   }
3255d27b854SBorawski.Lukasz };
3265d27b854SBorawski.Lukasz 
3272b7981f6SKowalski, Kamil }  // namespace redfish
328