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         nlohmann::json::object_t session;
117         session["@odata.id"] = "/redfish/v1/SessionService/Sessions/" + *uid;
118         ret.push_back(std::move(session));
119     }
120     return ret;
121 }
122 
123 inline void handleSessionCollectionGet(
124     crow::App& app, const crow::Request& req,
125     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
126 {
127     if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
128     {
129         return;
130     }
131     asyncResp->res.jsonValue["Members"] = getSessionCollectionMembers();
132     asyncResp->res.jsonValue["Members@odata.count"] =
133         asyncResp->res.jsonValue["Members"].size();
134     asyncResp->res.jsonValue["@odata.type"] =
135         "#SessionCollection.SessionCollection";
136     asyncResp->res.jsonValue["@odata.id"] =
137         "/redfish/v1/SessionService/Sessions/";
138     asyncResp->res.jsonValue["Name"] = "Session Collection";
139     asyncResp->res.jsonValue["Description"] = "Session Collection";
140 }
141 
142 inline void handleSessionCollectionMembersGet(
143     crow::App& app, const crow::Request& req,
144     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
145 {
146     if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
147     {
148         return;
149     }
150     asyncResp->res.jsonValue = getSessionCollectionMembers();
151 }
152 
153 void handleSessionCollectionPost(
154     crow::App& app, const crow::Request& req,
155     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
156 {
157     if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
158     {
159         return;
160     }
161     std::string username;
162     std::string password;
163     std::optional<nlohmann::json> oemObject;
164     std::string clientId;
165     if (!json_util::readJsonPatch(req, asyncResp->res, "UserName", username,
166                                   "Password", password, "Oem", oemObject))
167     {
168         return;
169     }
170 
171     if (password.empty() || username.empty() ||
172         asyncResp->res.result() != boost::beast::http::status::ok)
173     {
174         if (username.empty())
175         {
176             messages::propertyMissing(asyncResp->res, "UserName");
177         }
178 
179         if (password.empty())
180         {
181             messages::propertyMissing(asyncResp->res, "Password");
182         }
183 
184         return;
185     }
186 
187     int pamrc = pamAuthenticateUser(username, password);
188     bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD;
189     if ((pamrc != PAM_SUCCESS) && !isConfigureSelfOnly)
190     {
191         messages::resourceAtUriUnauthorized(asyncResp->res, req.urlView,
192                                             "Invalid username or password");
193         return;
194     }
195 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
196     if (oemObject)
197     {
198         std::optional<nlohmann::json> bmcOem;
199         if (!json_util::readJson(*oemObject, asyncResp->res, "OpenBMC", bmcOem))
200         {
201             return;
202         }
203         if (!json_util::readJson(*bmcOem, asyncResp->res, "ClientID", clientId))
204         {
205             BMCWEB_LOG_ERROR << "Could not read ClientId";
206             return;
207         }
208     }
209 #endif
210 
211     // User is authenticated - create session
212     std::shared_ptr<persistent_data::UserSession> session =
213         persistent_data::SessionStore::getInstance().generateUserSession(
214             username, req.ipAddress, clientId,
215             persistent_data::PersistenceType::TIMEOUT, isConfigureSelfOnly);
216     asyncResp->res.addHeader("X-Auth-Token", session->sessionToken);
217     asyncResp->res.addHeader(
218         "Location", "/redfish/v1/SessionService/Sessions/" + session->uniqueId);
219     asyncResp->res.result(boost::beast::http::status::created);
220     if (session->isConfigureSelfOnly)
221     {
222         messages::passwordChangeRequired(
223             asyncResp->res,
224             crow::utility::urlFromPieces("redfish", "v1", "AccountService",
225                                          "Accounts", req.session->username));
226     }
227 
228     fillSessionObject(asyncResp->res, *session);
229 }
230 inline void
231     handleSessionServiceGet(crow::App& app, const crow::Request& req,
232                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
233 
234 {
235     if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
236     {
237         return;
238     }
239     asyncResp->res.jsonValue["@odata.type"] =
240         "#SessionService.v1_0_2.SessionService";
241     asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/SessionService/";
242     asyncResp->res.jsonValue["Name"] = "Session Service";
243     asyncResp->res.jsonValue["Id"] = "SessionService";
244     asyncResp->res.jsonValue["Description"] = "Session Service";
245     asyncResp->res.jsonValue["SessionTimeout"] =
246         persistent_data::SessionStore::getInstance().getTimeoutInSeconds();
247     asyncResp->res.jsonValue["ServiceEnabled"] = true;
248 
249     asyncResp->res.jsonValue["Sessions"]["@odata.id"] =
250         "/redfish/v1/SessionService/Sessions";
251 }
252 
253 inline void handleSessionServicePatch(
254     crow::App& app, const crow::Request& req,
255     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
256 {
257     if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
258     {
259         return;
260     }
261     std::optional<int64_t> sessionTimeout;
262     if (!json_util::readJsonPatch(req, asyncResp->res, "SessionTimeout",
263                                   sessionTimeout))
264     {
265         return;
266     }
267 
268     if (sessionTimeout)
269     {
270         // The mininum & maximum allowed values for session timeout
271         // are 30 seconds and 86400 seconds respectively as per the
272         // session service schema mentioned at
273         // https://redfish.dmtf.org/schemas/v1/SessionService.v1_1_7.json
274 
275         if (*sessionTimeout <= 86400 && *sessionTimeout >= 30)
276         {
277             std::chrono::seconds sessionTimeoutInseconds(*sessionTimeout);
278             persistent_data::SessionStore::getInstance().updateSessionTimeout(
279                 sessionTimeoutInseconds);
280             messages::propertyValueModified(asyncResp->res, "SessionTimeOut",
281                                             std::to_string(*sessionTimeout));
282         }
283         else
284         {
285             messages::propertyValueNotInList(asyncResp->res,
286                                              std::to_string(*sessionTimeout),
287                                              "SessionTimeOut");
288         }
289     }
290 }
291 
292 inline void requestRoutesSession(App& app)
293 {
294     BMCWEB_ROUTE(app, "/redfish/v1/SessionService/Sessions/<str>/")
295         .privileges(redfish::privileges::getSession)
296         .methods(boost::beast::http::verb::get)(
297             std::bind_front(handleSessionGet, std::ref(app)));
298 
299     BMCWEB_ROUTE(app, "/redfish/v1/SessionService/Sessions/<str>/")
300         .privileges(redfish::privileges::deleteSession)
301         .methods(boost::beast::http::verb::delete_)(
302             std::bind_front(handleSessionDelete, std::ref(app)));
303 
304     BMCWEB_ROUTE(app, "/redfish/v1/SessionService/Sessions/")
305         .privileges(redfish::privileges::getSessionCollection)
306         .methods(boost::beast::http::verb::get)(
307             std::bind_front(handleSessionCollectionGet, std::ref(app)));
308 
309     // Note, the next two routes technically don't match the privilege
310     // registry given the way login mechanisms work.  The base privilege
311     // registry lists this endpoint as requiring login privilege, but because
312     // this is the endpoint responsible for giving the login privilege, and it
313     // is itself its own route, it needs to not require Login
314     BMCWEB_ROUTE(app, "/redfish/v1/SessionService/Sessions/")
315         .privileges({})
316         .methods(boost::beast::http::verb::post)(
317             std::bind_front(handleSessionCollectionPost, std::ref(app)));
318 
319     BMCWEB_ROUTE(app, "/redfish/v1/SessionService/Sessions/Members/")
320         .privileges({})
321         .methods(boost::beast::http::verb::post)(
322             std::bind_front(handleSessionCollectionPost, std::ref(app)));
323 
324     BMCWEB_ROUTE(app, "/redfish/v1/SessionService/")
325         .privileges(redfish::privileges::getSessionService)
326         .methods(boost::beast::http::verb::get)(
327             std::bind_front(handleSessionServiceGet, std::ref(app)));
328 
329     BMCWEB_ROUTE(app, "/redfish/v1/SessionService/")
330         .privileges(redfish::privileges::patchSessionService)
331         .methods(boost::beast::http::verb::patch)(
332             std::bind_front(handleSessionServicePatch, std::ref(app)));
333 }
334 
335 } // namespace redfish
336