xref: /openbmc/bmcweb/features/redfish/lib/redfish_sessions.hpp (revision 45ca1b868e47978a4d2e8ebb680cb384e804c97e)
1 /*
2 // Copyright (c) 2018 Intel Corporation
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 */
16 #pragma once
17 
18 #include "error_messages.hpp"
19 #include "persistent_data.hpp"
20 
21 #include <app.hpp>
22 #include <http/utility.hpp>
23 #include <query.hpp>
24 #include <registries/privilege_registry.hpp>
25 
26 namespace redfish
27 {
28 
29 inline void fillSessionObject(crow::Response& res,
30                               const persistent_data::UserSession& session)
31 {
32     res.jsonValue["Id"] = session.uniqueId;
33     res.jsonValue["UserName"] = session.username;
34     res.jsonValue["@odata.id"] =
35         "/redfish/v1/SessionService/Sessions/" + session.uniqueId;
36     res.jsonValue["@odata.type"] = "#Session.v1_3_0.Session";
37     res.jsonValue["Name"] = "User Session";
38     res.jsonValue["Description"] = "Manager User Session";
39     res.jsonValue["ClientOriginIPAddress"] = session.clientIp;
40 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
41     res.jsonValue["Oem"]["OpenBMC"]["@odata.type"] =
42         "#OemSession.v1_0_0.Session";
43     res.jsonValue["Oem"]["OpenBMC"]["ClientID"] = session.clientId;
44 #endif
45 }
46 
47 inline void
48     handleSessionGet(crow::App& app, const crow::Request& req,
49                      const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
50                      const std::string& sessionId)
51 {
52     if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
53     {
54         return;
55     }
56     // Note that control also reaches here via doPost and doDelete.
57     auto session =
58         persistent_data::SessionStore::getInstance().getSessionByUid(sessionId);
59 
60     if (session == nullptr)
61     {
62         messages::resourceNotFound(asyncResp->res, "Session", sessionId);
63         return;
64     }
65 
66     fillSessionObject(asyncResp->res, *session);
67 }
68 
69 inline void
70     handleSessionDelete(crow::App& app, const crow::Request& req,
71                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
72                         const std::string& sessionId)
73 {
74     if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
75     {
76         return;
77     }
78     auto session =
79         persistent_data::SessionStore::getInstance().getSessionByUid(sessionId);
80 
81     if (session == nullptr)
82     {
83         messages::resourceNotFound(asyncResp->res, "Session", sessionId);
84         return;
85     }
86 
87     // Perform a proper ConfigureSelf authority check.  If a
88     // session is being used to DELETE some other user's session,
89     // then the ConfigureSelf privilege does not apply.  In that
90     // case, perform the authority check again without the user's
91     // ConfigureSelf privilege.
92     if (session->username != req.session->username)
93     {
94         Privileges effectiveUserPrivileges =
95             redfish::getUserPrivileges(req.userRole);
96 
97         if (!effectiveUserPrivileges.isSupersetOf({"ConfigureUsers"}))
98         {
99             messages::insufficientPrivilege(asyncResp->res);
100             return;
101         }
102     }
103 
104     persistent_data::SessionStore::getInstance().removeSession(session);
105     messages::success(asyncResp->res);
106 }
107 
108 inline nlohmann::json getSessionCollectionMembers()
109 {
110     std::vector<const std::string*> sessionIds =
111         persistent_data::SessionStore::getInstance().getUniqueIds(
112             false, persistent_data::PersistenceType::TIMEOUT);
113     nlohmann::json ret = nlohmann::json::array();
114     for (const std::string* uid : sessionIds)
115     {
116         ret.push_back(
117             {{"@odata.id", "/redfish/v1/SessionService/Sessions/" + *uid}});
118     }
119     return ret;
120 }
121 
122 inline void handleSessionCollectionGet(
123     crow::App& app, const crow::Request& req,
124     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
125 {
126     if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
127     {
128         return;
129     }
130     asyncResp->res.jsonValue["Members"] = getSessionCollectionMembers();
131     asyncResp->res.jsonValue["Members@odata.count"] =
132         asyncResp->res.jsonValue["Members"].size();
133     asyncResp->res.jsonValue["@odata.type"] =
134         "#SessionCollection.SessionCollection";
135     asyncResp->res.jsonValue["@odata.id"] =
136         "/redfish/v1/SessionService/Sessions/";
137     asyncResp->res.jsonValue["Name"] = "Session Collection";
138     asyncResp->res.jsonValue["Description"] = "Session Collection";
139 }
140 
141 inline void handleSessionCollectionMembersGet(
142     crow::App& app, const crow::Request& req,
143     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
144 {
145     if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
146     {
147         return;
148     }
149     asyncResp->res.jsonValue = getSessionCollectionMembers();
150 }
151 
152 void handleSessionCollectionPost(
153     crow::App& app, const crow::Request& req,
154     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
155 {
156     if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
157     {
158         return;
159     }
160     std::string username;
161     std::string password;
162     std::optional<nlohmann::json> oemObject;
163     std::string clientId;
164     if (!json_util::readJsonPatch(req, asyncResp->res, "UserName", username,
165                                   "Password", password, "Oem", oemObject))
166     {
167         return;
168     }
169 
170     if (password.empty() || username.empty() ||
171         asyncResp->res.result() != boost::beast::http::status::ok)
172     {
173         if (username.empty())
174         {
175             messages::propertyMissing(asyncResp->res, "UserName");
176         }
177 
178         if (password.empty())
179         {
180             messages::propertyMissing(asyncResp->res, "Password");
181         }
182 
183         return;
184     }
185 
186     int pamrc = pamAuthenticateUser(username, password);
187     bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD;
188     if ((pamrc != PAM_SUCCESS) && !isConfigureSelfOnly)
189     {
190         messages::resourceAtUriUnauthorized(asyncResp->res, req.urlView,
191                                             "Invalid username or password");
192         return;
193     }
194 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
195     if (oemObject)
196     {
197         std::optional<nlohmann::json> bmcOem;
198         if (!json_util::readJson(*oemObject, asyncResp->res, "OpenBMC", bmcOem))
199         {
200             return;
201         }
202         if (!json_util::readJson(*bmcOem, asyncResp->res, "ClientID", clientId))
203         {
204             BMCWEB_LOG_ERROR << "Could not read ClientId";
205             return;
206         }
207     }
208 #endif
209 
210     // User is authenticated - create session
211     std::shared_ptr<persistent_data::UserSession> session =
212         persistent_data::SessionStore::getInstance().generateUserSession(
213             username, req.ipAddress, clientId,
214             persistent_data::PersistenceType::TIMEOUT, isConfigureSelfOnly);
215     asyncResp->res.addHeader("X-Auth-Token", session->sessionToken);
216     asyncResp->res.addHeader(
217         "Location", "/redfish/v1/SessionService/Sessions/" + session->uniqueId);
218     asyncResp->res.result(boost::beast::http::status::created);
219     if (session->isConfigureSelfOnly)
220     {
221         messages::passwordChangeRequired(
222             asyncResp->res,
223             crow::utility::urlFromPieces("redfish", "v1", "AccountService",
224                                          "Accounts", req.session->username));
225     }
226 
227     fillSessionObject(asyncResp->res, *session);
228 }
229 inline void
230     handleSessionServiceGet(crow::App& app, const crow::Request& req,
231                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
232 
233 {
234     if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
235     {
236         return;
237     }
238     asyncResp->res.jsonValue["@odata.type"] =
239         "#SessionService.v1_0_2.SessionService";
240     asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/SessionService/";
241     asyncResp->res.jsonValue["Name"] = "Session Service";
242     asyncResp->res.jsonValue["Id"] = "SessionService";
243     asyncResp->res.jsonValue["Description"] = "Session Service";
244     asyncResp->res.jsonValue["SessionTimeout"] =
245         persistent_data::SessionStore::getInstance().getTimeoutInSeconds();
246     asyncResp->res.jsonValue["ServiceEnabled"] = true;
247 
248     asyncResp->res.jsonValue["Sessions"] = {
249         {"@odata.id", "/redfish/v1/SessionService/Sessions"}};
250 }
251 
252 inline void handleSessionServicePatch(
253     crow::App& app, const crow::Request& req,
254     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
255 {
256     if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
257     {
258         return;
259     }
260     std::optional<int64_t> sessionTimeout;
261     if (!json_util::readJsonPatch(req, asyncResp->res, "SessionTimeout",
262                                   sessionTimeout))
263     {
264         return;
265     }
266 
267     if (sessionTimeout)
268     {
269         // The mininum & maximum allowed values for session timeout
270         // are 30 seconds and 86400 seconds respectively as per the
271         // session service schema mentioned at
272         // https://redfish.dmtf.org/schemas/v1/SessionService.v1_1_7.json
273 
274         if (*sessionTimeout <= 86400 && *sessionTimeout >= 30)
275         {
276             std::chrono::seconds sessionTimeoutInseconds(*sessionTimeout);
277             persistent_data::SessionStore::getInstance().updateSessionTimeout(
278                 sessionTimeoutInseconds);
279             messages::propertyValueModified(asyncResp->res, "SessionTimeOut",
280                                             std::to_string(*sessionTimeout));
281         }
282         else
283         {
284             messages::propertyValueNotInList(asyncResp->res,
285                                              std::to_string(*sessionTimeout),
286                                              "SessionTimeOut");
287         }
288     }
289 }
290 
291 inline void requestRoutesSession(App& app)
292 {
293     BMCWEB_ROUTE(app, "/redfish/v1/SessionService/Sessions/<str>/")
294         .privileges(redfish::privileges::getSession)
295         .methods(boost::beast::http::verb::get)(
296             std::bind_front(handleSessionGet, std::ref(app)));
297 
298     BMCWEB_ROUTE(app, "/redfish/v1/SessionService/Sessions/<str>/")
299         .privileges(redfish::privileges::deleteSession)
300         .methods(boost::beast::http::verb::delete_)(
301             std::bind_front(handleSessionDelete, std::ref(app)));
302 
303     BMCWEB_ROUTE(app, "/redfish/v1/SessionService/Sessions/")
304         .privileges(redfish::privileges::getSessionCollection)
305         .methods(boost::beast::http::verb::get)(
306             std::bind_front(handleSessionCollectionGet, std::ref(app)));
307 
308     // Note, the next two routes technically don't match the privilege
309     // registry given the way login mechanisms work.  The base privilege
310     // registry lists this endpoint as requiring login privilege, but because
311     // this is the endpoint responsible for giving the login privilege, and it
312     // is itself its own route, it needs to not require Login
313     BMCWEB_ROUTE(app, "/redfish/v1/SessionService/Sessions/")
314         .privileges({})
315         .methods(boost::beast::http::verb::post)(
316             std::bind_front(handleSessionCollectionPost, std::ref(app)));
317 
318     BMCWEB_ROUTE(app, "/redfish/v1/SessionService/Sessions/Members/")
319         .privileges({})
320         .methods(boost::beast::http::verb::post)(
321             std::bind_front(handleSessionCollectionPost, std::ref(app)));
322 
323     BMCWEB_ROUTE(app, "/redfish/v1/SessionService/")
324         .privileges(redfish::privileges::getSessionService)
325         .methods(boost::beast::http::verb::get)(
326             std::bind_front(handleSessionServiceGet, std::ref(app)));
327 
328     BMCWEB_ROUTE(app, "/redfish/v1/SessionService/")
329         .privileges(redfish::privileges::patchSessionService)
330         .methods(boost::beast::http::verb::patch)(
331             std::bind_front(handleSessionServicePatch, std::ref(app)));
332 }
333 
334 } // namespace redfish
335