xref: /openbmc/bmcweb/features/redfish/lib/redfish_sessions.hpp (revision b9845d9e0155564a7b1f4a52df2c0d99dbd0d4ec)
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:
4555c7b7a2SEd Tanous   void doGet(crow::Response& res, const crow::Request& req,
462b7981f6SKowalski, Kamil              const std::vector<std::string>& params) override {
472b7981f6SKowalski, Kamil     auto session =
4855c7b7a2SEd Tanous         crow::persistent_data::SessionStore::getInstance().getSessionByUid(
494b1b8683SBorawski.Lukasz             params[0]);
502b7981f6SKowalski, Kamil 
512b7981f6SKowalski, Kamil     if (session == nullptr) {
52f4c4dcf4SKowalski, Kamil       messages::addMessageToErrorJson(
5355c7b7a2SEd 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 
6055c7b7a2SEd Tanous     Node::json["Id"] = session->uniqueId;
61c1a46bd2SBorawski.Lukasz     Node::json["UserName"] = session->username;
62c1a46bd2SBorawski.Lukasz     Node::json["@odata.id"] =
6355c7b7a2SEd Tanous         "/redfish/v1/SessionService/Sessions/" + session->uniqueId;
642b7981f6SKowalski, Kamil 
6555c7b7a2SEd Tanous     res.jsonValue = Node::json;
662b7981f6SKowalski, Kamil     res.end();
672b7981f6SKowalski, Kamil   }
682b7981f6SKowalski, Kamil 
6955c7b7a2SEd 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
7455c7b7a2SEd 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);
7855c7b7a2SEd Tanous       messages::addMessageToErrorJson(res.jsonValue, messages::generalError());
79e0d918bcSEd Tanous 
802b7981f6SKowalski, Kamil       res.end();
812b7981f6SKowalski, Kamil       return;
822b7981f6SKowalski, Kamil     }
832b7981f6SKowalski, Kamil 
842b7981f6SKowalski, Kamil     auto session =
8555c7b7a2SEd Tanous         crow::persistent_data::SessionStore::getInstance().getSessionByUid(
864b1b8683SBorawski.Lukasz             params[0]);
872b7981f6SKowalski, Kamil 
882b7981f6SKowalski, Kamil     if (session == nullptr) {
89f4c4dcf4SKowalski, Kamil       messages::addMessageToErrorJson(
9055c7b7a2SEd 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 
10055c7b7a2SEd 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:
13455c7b7a2SEd Tanous   void doGet(crow::Response& res, const crow::Request& req,
1352b7981f6SKowalski, Kamil              const std::vector<std::string>& params) override {
13655c7b7a2SEd Tanous     std::vector<const std::string*> sessionIds =
13755c7b7a2SEd Tanous         crow::persistent_data::SessionStore::getInstance().getUniqueIds(
13855c7b7a2SEd Tanous             false, crow::persistent_data::PersistenceType::TIMEOUT);
1392b7981f6SKowalski, Kamil 
14055c7b7a2SEd Tanous     Node::json["Members@odata.count"] = sessionIds.size();
141c1a46bd2SBorawski.Lukasz     Node::json["Members"] = nlohmann::json::array();
14255c7b7a2SEd 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 
14755c7b7a2SEd Tanous     res.jsonValue = Node::json;
1482b7981f6SKowalski, Kamil     res.end();
1492b7981f6SKowalski, Kamil   }
1502b7981f6SKowalski, Kamil 
15155c7b7a2SEd 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 =
15655c7b7a2SEd 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 =
16655c7b7a2SEd Tanous         crow::persistent_data::SessionStore::getInstance().generateUserSession(
1674b1b8683SBorawski.Lukasz             username);
16855c7b7a2SEd Tanous     res.addHeader("X-Auth-Token", session->sessionToken);
1692b7981f6SKowalski, Kamil 
170*b9845d9eSEd Tanous     res.addHeader("Location",
171*b9845d9eSEd Tanous                   "/redfish/v1/SessionService/Sessions/" + session->uniqueId);
172*b9845d9eSEd Tanous 
1732b7981f6SKowalski, Kamil     // Return data for created session
17455c7b7a2SEd Tanous     memberSession.doGet(res, req, {session->uniqueId});
1752b7981f6SKowalski, Kamil 
1762b7981f6SKowalski, Kamil     // No need for res.end(), as it is called by doGet()
1772b7981f6SKowalski, Kamil   }
1782b7981f6SKowalski, Kamil 
1792b7981f6SKowalski, Kamil   /**
1802b7981f6SKowalski, Kamil    * @brief Verifies data provided in request and tries to authenticate user
1812b7981f6SKowalski, Kamil    *
1822b7981f6SKowalski, Kamil    * @param[in]  req            Crow request containing authentication data
1832b7981f6SKowalski, Kamil    * @param[out] httpRespCode   HTTP Code that should be returned in response
1842b7981f6SKowalski, Kamil    * @param[out] user           Retrieved username - not filled on failure
185f4c4dcf4SKowalski, Kamil    * @param[out] errJson        JSON to which error messages will be written
1862b7981f6SKowalski, Kamil    *
1872b7981f6SKowalski, Kamil    * @return true if authentication was successful, false otherwise
1882b7981f6SKowalski, Kamil    */
18955c7b7a2SEd Tanous   bool authenticateUser(const crow::Request& req,
190e0d918bcSEd Tanous                         boost::beast::http::status& httpRespCode,
191f4c4dcf4SKowalski, Kamil                         std::string& user, nlohmann::json& errJson) {
1922b7981f6SKowalski, Kamil     // We need only UserName and Password - nothing more, nothing less
1932b7981f6SKowalski, Kamil     static constexpr const unsigned int numberOfRequiredFieldsInReq = 2;
1942b7981f6SKowalski, Kamil 
1952b7981f6SKowalski, Kamil     // call with exceptions disabled
19655c7b7a2SEd Tanous     auto loginCredentials = nlohmann::json::parse(req.body, nullptr, false);
19755c7b7a2SEd Tanous     if (loginCredentials.is_discarded()) {
198e0d918bcSEd Tanous       httpRespCode = boost::beast::http::status::bad_request;
199f4c4dcf4SKowalski, Kamil 
200f4c4dcf4SKowalski, Kamil       messages::addMessageToErrorJson(errJson, messages::malformedJSON());
2012b7981f6SKowalski, Kamil 
2022b7981f6SKowalski, Kamil       return false;
2032b7981f6SKowalski, Kamil     }
2042b7981f6SKowalski, Kamil 
2052b7981f6SKowalski, Kamil     // Check that there are only as many fields as there should be
20655c7b7a2SEd Tanous     if (loginCredentials.size() != numberOfRequiredFieldsInReq) {
207e0d918bcSEd Tanous       httpRespCode = boost::beast::http::status::bad_request;
208f4c4dcf4SKowalski, Kamil 
209f4c4dcf4SKowalski, Kamil       messages::addMessageToErrorJson(errJson, messages::malformedJSON());
2102b7981f6SKowalski, Kamil 
2112b7981f6SKowalski, Kamil       return false;
2122b7981f6SKowalski, Kamil     }
2132b7981f6SKowalski, Kamil 
2142b7981f6SKowalski, Kamil     // Find fields that we need - UserName and Password
21555c7b7a2SEd Tanous     auto userIt = loginCredentials.find("UserName");
21655c7b7a2SEd Tanous     auto passIt = loginCredentials.find("Password");
21755c7b7a2SEd Tanous     if (userIt == loginCredentials.end() || passIt == loginCredentials.end()) {
218e0d918bcSEd Tanous       httpRespCode = boost::beast::http::status::bad_request;
219f4c4dcf4SKowalski, Kamil 
22055c7b7a2SEd Tanous       if (userIt == loginCredentials.end()) {
221f4c4dcf4SKowalski, Kamil         messages::addMessageToErrorJson(errJson,
222f4c4dcf4SKowalski, Kamil                                         messages::propertyMissing("UserName"));
223f4c4dcf4SKowalski, Kamil       }
224f4c4dcf4SKowalski, Kamil 
22555c7b7a2SEd Tanous       if (passIt == loginCredentials.end()) {
226f4c4dcf4SKowalski, Kamil         messages::addMessageToErrorJson(errJson,
227f4c4dcf4SKowalski, Kamil                                         messages::propertyMissing("Password"));
228f4c4dcf4SKowalski, Kamil       }
2292b7981f6SKowalski, Kamil 
2302b7981f6SKowalski, Kamil       return false;
2312b7981f6SKowalski, Kamil     }
2322b7981f6SKowalski, Kamil 
2332b7981f6SKowalski, Kamil     // Check that given data is of valid type (string)
23455c7b7a2SEd Tanous     if (!userIt->is_string() || !passIt->is_string()) {
235e0d918bcSEd Tanous       httpRespCode = boost::beast::http::status::bad_request;
236f4c4dcf4SKowalski, Kamil 
23755c7b7a2SEd Tanous       if (!userIt->is_string()) {
238f4c4dcf4SKowalski, Kamil         messages::addMessageToErrorJson(
239f4c4dcf4SKowalski, Kamil             errJson,
24055c7b7a2SEd Tanous             messages::propertyValueTypeError(userIt->dump(), "UserName"));
241f4c4dcf4SKowalski, Kamil       }
242f4c4dcf4SKowalski, Kamil 
24355c7b7a2SEd Tanous       if (!passIt->is_string()) {
244f4c4dcf4SKowalski, Kamil         messages::addMessageToErrorJson(
245f4c4dcf4SKowalski, Kamil             errJson,
24655c7b7a2SEd Tanous             messages::propertyValueTypeError(userIt->dump(), "Password"));
247f4c4dcf4SKowalski, Kamil       }
2482b7981f6SKowalski, Kamil 
2492b7981f6SKowalski, Kamil       return false;
2502b7981f6SKowalski, Kamil     }
2512b7981f6SKowalski, Kamil 
2522b7981f6SKowalski, Kamil     // Extract username and password
25355c7b7a2SEd Tanous     std::string username = userIt->get<const std::string>();
25455c7b7a2SEd Tanous     std::string password = passIt->get<const std::string>();
2552b7981f6SKowalski, Kamil 
2562b7981f6SKowalski, Kamil     // Verify that required fields are not empty
2572b7981f6SKowalski, Kamil     if (username.empty() || password.empty()) {
258e0d918bcSEd Tanous       httpRespCode = boost::beast::http::status::bad_request;
259f4c4dcf4SKowalski, Kamil 
260f4c4dcf4SKowalski, Kamil       if (username.empty()) {
261f4c4dcf4SKowalski, Kamil         messages::addMessageToErrorJson(errJson,
262f4c4dcf4SKowalski, Kamil                                         messages::propertyMissing("UserName"));
263f4c4dcf4SKowalski, Kamil       }
264f4c4dcf4SKowalski, Kamil 
265f4c4dcf4SKowalski, Kamil       if (password.empty()) {
266f4c4dcf4SKowalski, Kamil         messages::addMessageToErrorJson(errJson,
267f4c4dcf4SKowalski, Kamil                                         messages::propertyMissing("Password"));
268f4c4dcf4SKowalski, Kamil       }
2692b7981f6SKowalski, Kamil 
2702b7981f6SKowalski, Kamil       return false;
2712b7981f6SKowalski, Kamil     }
2722b7981f6SKowalski, Kamil 
2732b7981f6SKowalski, Kamil     // Finally - try to authenticate user
27455c7b7a2SEd Tanous     if (!pamAuthenticateUser(username, password)) {
275e0d918bcSEd Tanous       httpRespCode = boost::beast::http::status::unauthorized;
276f4c4dcf4SKowalski, Kamil 
277f4c4dcf4SKowalski, Kamil       messages::addMessageToErrorJson(
278f4c4dcf4SKowalski, Kamil           errJson, messages::resourceAtUriUnauthorized(
279e0d918bcSEd Tanous                        std::string(req.url), "Invalid username or password"));
2802b7981f6SKowalski, Kamil 
2812b7981f6SKowalski, Kamil       return false;
2822b7981f6SKowalski, Kamil     }
2832b7981f6SKowalski, Kamil 
2842b7981f6SKowalski, Kamil     // User authenticated successfully
285e0d918bcSEd Tanous     httpRespCode = boost::beast::http::status::ok;
286f4c4dcf4SKowalski, Kamil     user = username;
2872b7981f6SKowalski, Kamil 
2882b7981f6SKowalski, Kamil     return true;
2892b7981f6SKowalski, Kamil   }
2902b7981f6SKowalski, Kamil 
2912b7981f6SKowalski, Kamil   /**
2922b7981f6SKowalski, Kamil    * Member session to ensure consistency between collection's doPost and
2932b7981f6SKowalski, Kamil    * member's doGet, as they should return 100% matching data
2942b7981f6SKowalski, Kamil    */
2952b7981f6SKowalski, Kamil   Sessions memberSession;
2962b7981f6SKowalski, Kamil };
2972b7981f6SKowalski, Kamil 
2985d27b854SBorawski.Lukasz class SessionService : public Node {
2995d27b854SBorawski.Lukasz  public:
3003ebd75f7SEd Tanous   SessionService(CrowApp& app) : Node(app, "/redfish/v1/SessionService/") {
3015d27b854SBorawski.Lukasz     Node::json["@odata.type"] = "#SessionService.v1_0_2.SessionService";
3025d27b854SBorawski.Lukasz     Node::json["@odata.id"] = "/redfish/v1/SessionService/";
3035d27b854SBorawski.Lukasz     Node::json["@odata.context"] =
3045d27b854SBorawski.Lukasz         "/redfish/v1/$metadata#SessionService.SessionService";
3055d27b854SBorawski.Lukasz     Node::json["Name"] = "Session Service";
3066c233015SEd Tanous     Node::json["Id"] = "SessionService";
3075d27b854SBorawski.Lukasz     Node::json["Description"] = "Session Service";
3085d27b854SBorawski.Lukasz     Node::json["SessionTimeout"] =
30955c7b7a2SEd Tanous         crow::persistent_data::SessionStore::getInstance()
31055c7b7a2SEd Tanous             .getTimeoutInSeconds();
3115d27b854SBorawski.Lukasz     Node::json["ServiceEnabled"] = true;
3123ebd75f7SEd Tanous 
313e0d918bcSEd Tanous     entityPrivileges = {
314e0d918bcSEd Tanous         {boost::beast::http::verb::get, {{"Login"}}},
315e0d918bcSEd Tanous         {boost::beast::http::verb::head, {{"Login"}}},
316e0d918bcSEd Tanous         {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
317e0d918bcSEd Tanous         {boost::beast::http::verb::put, {{"ConfigureManager"}}},
318e0d918bcSEd Tanous         {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
319e0d918bcSEd Tanous         {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
3205d27b854SBorawski.Lukasz   }
3215d27b854SBorawski.Lukasz 
3225d27b854SBorawski.Lukasz  private:
32355c7b7a2SEd Tanous   void doGet(crow::Response& res, const crow::Request& req,
3245d27b854SBorawski.Lukasz              const std::vector<std::string>& params) override {
32555c7b7a2SEd Tanous     res.jsonValue = Node::json;
3265d27b854SBorawski.Lukasz     res.end();
3275d27b854SBorawski.Lukasz   }
3285d27b854SBorawski.Lukasz };
3295d27b854SBorawski.Lukasz 
3302b7981f6SKowalski, Kamil }  // namespace redfish
331