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