xref: /openbmc/bmcweb/features/redfish/lib/redfish_sessions.hpp (revision 6c51eab135bb573c292d111170bc138b3a4b4eb0)
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"
2052cc112dSEd Tanous #include "persistent_data.hpp"
212b7981f6SKowalski, Kamil 
227e860f15SJohn Edward Broadbent #include <app.hpp>
237e860f15SJohn Edward Broadbent 
241abe55efSEd Tanous namespace redfish
251abe55efSEd Tanous {
262b7981f6SKowalski, Kamil 
272b7981f6SKowalski, Kamil class SessionCollection;
282b7981f6SKowalski, Kamil 
291abe55efSEd Tanous class Sessions : public Node
301abe55efSEd Tanous {
312b7981f6SKowalski, Kamil   public:
3252cc112dSEd Tanous     Sessions(App& app) :
331abe55efSEd Tanous         Node(app, "/redfish/v1/SessionService/Sessions/<str>/", std::string())
341abe55efSEd 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"}}},
40900f9497SJoseph Reynolds             {boost::beast::http::verb::delete_,
41900f9497SJoseph Reynolds              {{"ConfigureManager"}, {"ConfigureSelf"}}},
42e0d918bcSEd Tanous             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
432b7981f6SKowalski, Kamil     }
442b7981f6SKowalski, Kamil 
452b7981f6SKowalski, Kamil   private:
468d1b46d7Szhanghch05     void doGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
478d1b46d7Szhanghch05                const crow::Request&,
481abe55efSEd Tanous                const std::vector<std::string>& params) override
491abe55efSEd Tanous     {
50900f9497SJoseph Reynolds         // Note that control also reaches here via doPost and doDelete.
512b7981f6SKowalski, Kamil         auto session =
5252cc112dSEd Tanous             persistent_data::SessionStore::getInstance().getSessionByUid(
534b1b8683SBorawski.Lukasz                 params[0]);
542b7981f6SKowalski, Kamil 
551abe55efSEd Tanous         if (session == nullptr)
561abe55efSEd Tanous         {
578d1b46d7Szhanghch05             messages::resourceNotFound(asyncResp->res, "Session", params[0]);
582b7981f6SKowalski, Kamil             return;
592b7981f6SKowalski, Kamil         }
602b7981f6SKowalski, Kamil 
618d1b46d7Szhanghch05         asyncResp->res.jsonValue["Id"] = session->uniqueId;
628d1b46d7Szhanghch05         asyncResp->res.jsonValue["UserName"] = session->username;
638d1b46d7Szhanghch05         asyncResp->res.jsonValue["@odata.id"] =
6455c7b7a2SEd Tanous             "/redfish/v1/SessionService/Sessions/" + session->uniqueId;
658d1b46d7Szhanghch05         asyncResp->res.jsonValue["@odata.type"] = "#Session.v1_3_0.Session";
668d1b46d7Szhanghch05         asyncResp->res.jsonValue["Name"] = "User Session";
678d1b46d7Szhanghch05         asyncResp->res.jsonValue["Description"] = "Manager User Session";
688d1b46d7Szhanghch05         asyncResp->res.jsonValue["ClientOriginIPAddress"] = session->clientIp;
69c0ea7ae1SSunitha Harish #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
708d1b46d7Szhanghch05         asyncResp->res.jsonValue["Oem"]["OpenBMC"]["@odata.type"] =
7108bdcc71SSunitha Harish             "#OemSession.v1_0_0.Session";
728d1b46d7Szhanghch05         asyncResp->res.jsonValue["Oem"]["OpenBMC"]["ClientID"] =
738d1b46d7Szhanghch05             session->clientId;
7408bdcc71SSunitha Harish #endif
752b7981f6SKowalski, Kamil     }
762b7981f6SKowalski, Kamil 
778d1b46d7Szhanghch05     void doDelete(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
788d1b46d7Szhanghch05                   const crow::Request& req,
791abe55efSEd Tanous                   const std::vector<std::string>& params) override
801abe55efSEd Tanous     {
812b7981f6SKowalski, Kamil         // Need only 1 param which should be id of session to be deleted
821abe55efSEd Tanous         if (params.size() != 1)
831abe55efSEd Tanous         {
84f4c4dcf4SKowalski, Kamil             // This should be handled by crow and never happen
851abe55efSEd Tanous             BMCWEB_LOG_ERROR << "Session DELETE has been called with invalid "
861abe55efSEd Tanous                                 "number of params";
87f4c4dcf4SKowalski, Kamil 
888d1b46d7Szhanghch05             messages::generalError(asyncResp->res);
892b7981f6SKowalski, Kamil             return;
902b7981f6SKowalski, Kamil         }
912b7981f6SKowalski, Kamil 
922b7981f6SKowalski, Kamil         auto session =
9352cc112dSEd Tanous             persistent_data::SessionStore::getInstance().getSessionByUid(
944b1b8683SBorawski.Lukasz                 params[0]);
952b7981f6SKowalski, Kamil 
961abe55efSEd Tanous         if (session == nullptr)
971abe55efSEd Tanous         {
988d1b46d7Szhanghch05             messages::resourceNotFound(asyncResp->res, "Session", params[0]);
992b7981f6SKowalski, Kamil             return;
1002b7981f6SKowalski, Kamil         }
1012b7981f6SKowalski, Kamil 
102900f9497SJoseph Reynolds         // Perform a proper ConfigureSelf authority check.  If a
103900f9497SJoseph Reynolds         // session is being used to DELETE some other user's session,
104900f9497SJoseph Reynolds         // then the ConfigureSelf privilege does not apply.  In that
105900f9497SJoseph Reynolds         // case, perform the authority check again without the user's
106900f9497SJoseph Reynolds         // ConfigureSelf privilege.
107900f9497SJoseph Reynolds         if (session->username != req.session->username)
108900f9497SJoseph Reynolds         {
109*6c51eab1SEd Tanous             Privileges effectiveUserPrivileges =
110*6c51eab1SEd Tanous                 redfish::getUserPrivileges(req.userRole);
111*6c51eab1SEd Tanous 
112*6c51eab1SEd Tanous             if (!effectiveUserPrivileges.isSupersetOf({{"ConfigureUsers"}}))
113900f9497SJoseph Reynolds             {
1148d1b46d7Szhanghch05                 messages::insufficientPrivilege(asyncResp->res);
115900f9497SJoseph Reynolds                 return;
116900f9497SJoseph Reynolds             }
117900f9497SJoseph Reynolds         }
118900f9497SJoseph Reynolds 
119f4c4dcf4SKowalski, Kamil         // DELETE should return representation of object that will be removed
1208d1b46d7Szhanghch05         doGet(asyncResp, req, params);
121f4c4dcf4SKowalski, Kamil 
12252cc112dSEd Tanous         persistent_data::SessionStore::getInstance().removeSession(session);
1232b7981f6SKowalski, Kamil     }
1242b7981f6SKowalski, Kamil 
1252b7981f6SKowalski, Kamil     /**
1262b7981f6SKowalski, Kamil      * This allows SessionCollection to reuse this class' doGet method, to
1271abe55efSEd Tanous      * maintain consistency of returned data, as Collection's doPost should
12892f68223SSunitha Harish      * return data for created member which should match member's doGet
12992f68223SSunitha Harish      * result in 100%
1302b7981f6SKowalski, Kamil      */
1312b7981f6SKowalski, Kamil     friend SessionCollection;
1322b7981f6SKowalski, Kamil };
1332b7981f6SKowalski, Kamil 
1341abe55efSEd Tanous class SessionCollection : public Node
1351abe55efSEd Tanous {
1362b7981f6SKowalski, Kamil   public:
13752cc112dSEd Tanous     SessionCollection(App& app) :
1381abe55efSEd Tanous         Node(app, "/redfish/v1/SessionService/Sessions/"), memberSession(app)
1391abe55efSEd Tanous     {
140e0d918bcSEd Tanous         entityPrivileges = {
141e0d918bcSEd Tanous             {boost::beast::http::verb::get, {{"Login"}}},
142e0d918bcSEd Tanous             {boost::beast::http::verb::head, {{"Login"}}},
143e0d918bcSEd Tanous             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
144e0d918bcSEd Tanous             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
145e0d918bcSEd Tanous             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
146e0d918bcSEd Tanous             {boost::beast::http::verb::post, {}}};
1472b7981f6SKowalski, Kamil     }
1482b7981f6SKowalski, Kamil 
1492b7981f6SKowalski, Kamil   private:
1508d1b46d7Szhanghch05     void doGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1518d1b46d7Szhanghch05                const crow::Request&, const std::vector<std::string>&) override
1521abe55efSEd Tanous     {
15355c7b7a2SEd Tanous         std::vector<const std::string*> sessionIds =
15452cc112dSEd Tanous             persistent_data::SessionStore::getInstance().getUniqueIds(
15552cc112dSEd Tanous                 false, persistent_data::PersistenceType::TIMEOUT);
1562b7981f6SKowalski, Kamil 
1578d1b46d7Szhanghch05         asyncResp->res.jsonValue["Members@odata.count"] = sessionIds.size();
1588d1b46d7Szhanghch05         asyncResp->res.jsonValue["Members"] = nlohmann::json::array();
1591abe55efSEd Tanous         for (const std::string* uid : sessionIds)
1601abe55efSEd Tanous         {
1618d1b46d7Szhanghch05             asyncResp->res.jsonValue["Members"].push_back(
1622b7981f6SKowalski, Kamil                 {{"@odata.id", "/redfish/v1/SessionService/Sessions/" + *uid}});
1632b7981f6SKowalski, Kamil         }
1648d1b46d7Szhanghch05         asyncResp->res.jsonValue["Members@odata.count"] = sessionIds.size();
1658d1b46d7Szhanghch05         asyncResp->res.jsonValue["@odata.type"] =
1668d1b46d7Szhanghch05             "#SessionCollection.SessionCollection";
1678d1b46d7Szhanghch05         asyncResp->res.jsonValue["@odata.id"] =
1688d1b46d7Szhanghch05             "/redfish/v1/SessionService/Sessions/";
1698d1b46d7Szhanghch05         asyncResp->res.jsonValue["Name"] = "Session Collection";
1708d1b46d7Szhanghch05         asyncResp->res.jsonValue["Description"] = "Session Collection";
1712b7981f6SKowalski, Kamil     }
1722b7981f6SKowalski, Kamil 
1738d1b46d7Szhanghch05     void doPost(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1748d1b46d7Szhanghch05                 const crow::Request& req,
175cb13a392SEd Tanous                 const std::vector<std::string>&) override
1761abe55efSEd Tanous     {
1779712f8acSEd Tanous         std::string username;
1789712f8acSEd Tanous         std::string password;
17908bdcc71SSunitha Harish         std::optional<nlohmann::json> oemObject;
18008bdcc71SSunitha Harish         std::string clientId;
1818d1b46d7Szhanghch05         if (!json_util::readJson(req, asyncResp->res, "UserName", username,
1828d1b46d7Szhanghch05                                  "Password", password, "Oem", oemObject))
1831abe55efSEd Tanous         {
1842b7981f6SKowalski, Kamil             return;
1852b7981f6SKowalski, Kamil         }
1862b7981f6SKowalski, Kamil 
187820ce598SEd Tanous         if (password.empty() || username.empty() ||
1888d1b46d7Szhanghch05             asyncResp->res.result() != boost::beast::http::status::ok)
1891abe55efSEd Tanous         {
1901abe55efSEd Tanous             if (username.empty())
1911abe55efSEd Tanous             {
1928d1b46d7Szhanghch05                 messages::propertyMissing(asyncResp->res, "UserName");
193f4c4dcf4SKowalski, Kamil             }
194f4c4dcf4SKowalski, Kamil 
1951abe55efSEd Tanous             if (password.empty())
1961abe55efSEd Tanous             {
1978d1b46d7Szhanghch05                 messages::propertyMissing(asyncResp->res, "Password");
198820ce598SEd Tanous             }
199820ce598SEd Tanous 
200820ce598SEd Tanous             return;
201f4c4dcf4SKowalski, Kamil         }
2022b7981f6SKowalski, Kamil 
2033bf4e632SJoseph Reynolds         int pamrc = pamAuthenticateUser(username, password);
2043bf4e632SJoseph Reynolds         bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD;
2053bf4e632SJoseph Reynolds         if ((pamrc != PAM_SUCCESS) && !isConfigureSelfOnly)
2061abe55efSEd Tanous         {
2078d1b46d7Szhanghch05             messages::resourceAtUriUnauthorized(asyncResp->res,
2088d1b46d7Szhanghch05                                                 std::string(req.url),
209f12894f8SJason M. Bills                                                 "Invalid username or password");
210820ce598SEd Tanous             return;
2112b7981f6SKowalski, Kamil         }
21208bdcc71SSunitha Harish #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
21308bdcc71SSunitha Harish         if (oemObject)
21408bdcc71SSunitha Harish         {
21508bdcc71SSunitha Harish             std::optional<nlohmann::json> bmcOem;
2168d1b46d7Szhanghch05             if (!json_util::readJson(*oemObject, asyncResp->res, "OpenBMC",
2178d1b46d7Szhanghch05                                      bmcOem))
21808bdcc71SSunitha Harish             {
21908bdcc71SSunitha Harish                 return;
22008bdcc71SSunitha Harish             }
2218d1b46d7Szhanghch05             if (!json_util::readJson(*bmcOem, asyncResp->res, "ClientID",
2228d1b46d7Szhanghch05                                      clientId))
22308bdcc71SSunitha Harish             {
22408bdcc71SSunitha Harish                 BMCWEB_LOG_ERROR << "Could not read ClientId";
22508bdcc71SSunitha Harish                 return;
22608bdcc71SSunitha Harish             }
22708bdcc71SSunitha Harish         }
22808bdcc71SSunitha Harish #endif
2296f115bbbSManojkiran Eda 
230820ce598SEd Tanous         // User is authenticated - create session
23152cc112dSEd Tanous         std::shared_ptr<persistent_data::UserSession> session =
23252cc112dSEd Tanous             persistent_data::SessionStore::getInstance().generateUserSession(
233d3239224SSunitha Harish                 username, req.ipAddress.to_string(), clientId,
234d3239224SSunitha Harish                 persistent_data::PersistenceType::TIMEOUT, isConfigureSelfOnly);
2358d1b46d7Szhanghch05         asyncResp->res.addHeader("X-Auth-Token", session->sessionToken);
2368d1b46d7Szhanghch05         asyncResp->res.addHeader("Location",
2378d1b46d7Szhanghch05                                  "/redfish/v1/SessionService/Sessions/" +
238820ce598SEd Tanous                                      session->uniqueId);
2398d1b46d7Szhanghch05         asyncResp->res.result(boost::beast::http::status::created);
2403bf4e632SJoseph Reynolds         if (session->isConfigureSelfOnly)
2413bf4e632SJoseph Reynolds         {
2423bf4e632SJoseph Reynolds             messages::passwordChangeRequired(
2438d1b46d7Szhanghch05                 asyncResp->res,
2443bf4e632SJoseph Reynolds                 "/redfish/v1/AccountService/Accounts/" + session->username);
2453bf4e632SJoseph Reynolds         }
2468d1b46d7Szhanghch05         memberSession.doGet(asyncResp, req, {session->uniqueId});
2472b7981f6SKowalski, Kamil     }
2482b7981f6SKowalski, Kamil 
2492b7981f6SKowalski, Kamil     /**
2502b7981f6SKowalski, Kamil      * Member session to ensure consistency between collection's doPost and
2512b7981f6SKowalski, Kamil      * member's doGet, as they should return 100% matching data
2522b7981f6SKowalski, Kamil      */
2532b7981f6SKowalski, Kamil     Sessions memberSession;
2542b7981f6SKowalski, Kamil };
2552b7981f6SKowalski, Kamil 
2561abe55efSEd Tanous class SessionService : public Node
2571abe55efSEd Tanous {
2585d27b854SBorawski.Lukasz   public:
25952cc112dSEd Tanous     SessionService(App& app) : Node(app, "/redfish/v1/SessionService/")
2601abe55efSEd Tanous     {
2613ebd75f7SEd Tanous 
262e0d918bcSEd Tanous         entityPrivileges = {
263e0d918bcSEd Tanous             {boost::beast::http::verb::get, {{"Login"}}},
264e0d918bcSEd Tanous             {boost::beast::http::verb::head, {{"Login"}}},
265e0d918bcSEd Tanous             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
266e0d918bcSEd Tanous             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
267e0d918bcSEd Tanous             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
268e0d918bcSEd Tanous             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
2695d27b854SBorawski.Lukasz     }
2705d27b854SBorawski.Lukasz 
2715d27b854SBorawski.Lukasz   private:
2728d1b46d7Szhanghch05     void doGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2738d1b46d7Szhanghch05                const crow::Request&, const std::vector<std::string>&) override
2741abe55efSEd Tanous     {
2758d1b46d7Szhanghch05         asyncResp->res.jsonValue["@odata.type"] =
2768d1b46d7Szhanghch05             "#SessionService.v1_0_2.SessionService";
2778d1b46d7Szhanghch05         asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/SessionService/";
2788d1b46d7Szhanghch05         asyncResp->res.jsonValue["Name"] = "Session Service";
2798d1b46d7Szhanghch05         asyncResp->res.jsonValue["Id"] = "SessionService";
2808d1b46d7Szhanghch05         asyncResp->res.jsonValue["Description"] = "Session Service";
2818d1b46d7Szhanghch05         asyncResp->res.jsonValue["SessionTimeout"] =
28252cc112dSEd Tanous             persistent_data::SessionStore::getInstance().getTimeoutInSeconds();
2838d1b46d7Szhanghch05         asyncResp->res.jsonValue["ServiceEnabled"] = true;
2840f74e643SEd Tanous 
2858d1b46d7Szhanghch05         asyncResp->res.jsonValue["Sessions"] = {
2860f261533SEd Tanous             {"@odata.id", "/redfish/v1/SessionService/Sessions"}};
2875d27b854SBorawski.Lukasz     }
288f2a4a606SManojkiran Eda 
2898d1b46d7Szhanghch05     void doPatch(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2908d1b46d7Szhanghch05                  const crow::Request& req,
291f2a4a606SManojkiran Eda                  const std::vector<std::string>&) override
292f2a4a606SManojkiran Eda     {
293f2a4a606SManojkiran Eda         std::optional<int64_t> sessionTimeout;
2948d1b46d7Szhanghch05         if (!json_util::readJson(req, asyncResp->res, "SessionTimeout",
2958d1b46d7Szhanghch05                                  sessionTimeout))
296f2a4a606SManojkiran Eda         {
297f2a4a606SManojkiran Eda             return;
298f2a4a606SManojkiran Eda         }
299f2a4a606SManojkiran Eda 
300f2a4a606SManojkiran Eda         if (sessionTimeout)
301f2a4a606SManojkiran Eda         {
302f2a4a606SManojkiran Eda             // The mininum & maximum allowed values for session timeout are 30
303f2a4a606SManojkiran Eda             // seconds and 86400 seconds respectively as per the session service
304f2a4a606SManojkiran Eda             // schema mentioned at
305f2a4a606SManojkiran Eda             // https://redfish.dmtf.org/schemas/v1/SessionService.v1_1_7.json
306f2a4a606SManojkiran Eda 
307f2a4a606SManojkiran Eda             if (*sessionTimeout <= 86400 && *sessionTimeout >= 30)
308f2a4a606SManojkiran Eda             {
309f2a4a606SManojkiran Eda                 std::chrono::seconds sessionTimeoutInseconds(*sessionTimeout);
310f2a4a606SManojkiran Eda                 persistent_data::SessionStore::getInstance()
311f2a4a606SManojkiran Eda                     .updateSessionTimeout(sessionTimeoutInseconds);
312f2a4a606SManojkiran Eda                 messages::propertyValueModified(
313f2a4a606SManojkiran Eda                     asyncResp->res, "SessionTimeOut",
314f2a4a606SManojkiran Eda                     std::to_string(*sessionTimeout));
315f2a4a606SManojkiran Eda             }
316f2a4a606SManojkiran Eda             else
317f2a4a606SManojkiran Eda             {
318f2a4a606SManojkiran Eda                 messages::propertyValueNotInList(
3198d1b46d7Szhanghch05                     asyncResp->res, std::to_string(*sessionTimeout),
3208d1b46d7Szhanghch05                     "SessionTimeOut");
321f2a4a606SManojkiran Eda             }
322f2a4a606SManojkiran Eda         }
323f2a4a606SManojkiran Eda     }
3245d27b854SBorawski.Lukasz };
3255d27b854SBorawski.Lukasz 
3262b7981f6SKowalski, Kamil } // namespace redfish
327