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 "app.hpp"
19 #include "error_messages.hpp"
20 #include "http/utility.hpp"
21 #include "persistent_data.hpp"
22 #include "query.hpp"
23 #include "registries/privilege_registry.hpp"
24 #include "utils/json_utils.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"] = crow::utility::urlFromPieces(
35         "redfish", "v1", "SessionService", "Sessions", session.uniqueId);
36     res.jsonValue["@odata.type"] = "#Session.v1_5_0.Session";
37     res.jsonValue["Name"] = "User Session";
38     res.jsonValue["Description"] = "Manager User Session";
39     res.jsonValue["ClientOriginIPAddress"] = session.clientIp;
40     if (session.clientId)
41     {
42         res.jsonValue["Context"] = *session.clientId;
43     }
44 }
45 
46 inline void
47     handleSessionHead(crow::App& app, const crow::Request& req,
48                       const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
49                       const std::string& /*sessionId*/)
50 {
51 
52     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
53     {
54         return;
55     }
56     asyncResp->res.addHeader(
57         boost::beast::http::field::link,
58         "</redfish/v1/JsonSchemas/Session/Session.json>; rel=describedby");
59 }
60 
61 inline void
62     handleSessionGet(crow::App& app, const crow::Request& req,
63                      const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
64                      const std::string& sessionId)
65 {
66     handleSessionHead(app, req, asyncResp, sessionId);
67 
68     // Note that control also reaches here via doPost and doDelete.
69     auto session =
70         persistent_data::SessionStore::getInstance().getSessionByUid(sessionId);
71 
72     if (session == nullptr)
73     {
74         messages::resourceNotFound(asyncResp->res, "Session", sessionId);
75         return;
76     }
77 
78     fillSessionObject(asyncResp->res, *session);
79 }
80 
81 inline void
82     handleSessionDelete(crow::App& app, const crow::Request& req,
83                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
84                         const std::string& sessionId)
85 {
86     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
87     {
88         return;
89     }
90     auto session =
91         persistent_data::SessionStore::getInstance().getSessionByUid(sessionId);
92 
93     if (session == nullptr)
94     {
95         messages::resourceNotFound(asyncResp->res, "Session", sessionId);
96         return;
97     }
98 
99     // Perform a proper ConfigureSelf authority check.  If a
100     // session is being used to DELETE some other user's session,
101     // then the ConfigureSelf privilege does not apply.  In that
102     // case, perform the authority check again without the user's
103     // ConfigureSelf privilege.
104     if (req.session != nullptr && !session->username.empty() &&
105         session->username != req.session->username)
106     {
107         Privileges effectiveUserPrivileges =
108             redfish::getUserPrivileges(req.userRole);
109 
110         if (!effectiveUserPrivileges.isSupersetOf({"ConfigureUsers"}))
111         {
112             messages::insufficientPrivilege(asyncResp->res);
113             return;
114         }
115     }
116 
117     persistent_data::SessionStore::getInstance().removeSession(session);
118     messages::success(asyncResp->res);
119 }
120 
121 inline nlohmann::json getSessionCollectionMembers()
122 {
123     std::vector<const std::string*> sessionIds =
124         persistent_data::SessionStore::getInstance().getUniqueIds(
125             false, persistent_data::PersistenceType::TIMEOUT);
126     nlohmann::json ret = nlohmann::json::array();
127     for (const std::string* uid : sessionIds)
128     {
129         nlohmann::json::object_t session;
130         session["@odata.id"] = crow::utility::urlFromPieces(
131             "redfish", "v1", "SessionService", "Sessions", *uid);
132         ret.push_back(std::move(session));
133     }
134     return ret;
135 }
136 
137 inline void handleSessionCollectionHead(
138     crow::App& app, const crow::Request& req,
139     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
140 {
141 
142     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
143     {
144         return;
145     }
146     asyncResp->res.addHeader(
147         boost::beast::http::field::link,
148         "</redfish/v1/JsonSchemas/SessionCollection.json>; rel=describedby");
149 }
150 
151 inline void handleSessionCollectionGet(
152     crow::App& app, const crow::Request& req,
153     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
154 {
155     handleSessionCollectionHead(app, req, asyncResp);
156     asyncResp->res.jsonValue["Members"] = getSessionCollectionMembers();
157     asyncResp->res.jsonValue["Members@odata.count"] =
158         asyncResp->res.jsonValue["Members"].size();
159     asyncResp->res.jsonValue["@odata.type"] =
160         "#SessionCollection.SessionCollection";
161     asyncResp->res.jsonValue["@odata.id"] =
162         "/redfish/v1/SessionService/Sessions/";
163     asyncResp->res.jsonValue["Name"] = "Session Collection";
164     asyncResp->res.jsonValue["Description"] = "Session Collection";
165 }
166 
167 inline void handleSessionCollectionMembersGet(
168     crow::App& app, const crow::Request& req,
169     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
170 {
171     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
172     {
173         return;
174     }
175     asyncResp->res.jsonValue = getSessionCollectionMembers();
176 }
177 
178 inline void handleSessionCollectionPost(
179     crow::App& app, const crow::Request& req,
180     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
181 {
182     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
183     {
184         return;
185     }
186     std::string username;
187     std::string password;
188     std::optional<std::string> clientId;
189     if (!json_util::readJsonPatch(req, asyncResp->res, "UserName", username,
190                                   "Password", password, "Context", clientId))
191     {
192         return;
193     }
194 
195     if (password.empty() || username.empty() ||
196         asyncResp->res.result() != boost::beast::http::status::ok)
197     {
198         if (username.empty())
199         {
200             messages::propertyMissing(asyncResp->res, "UserName");
201         }
202 
203         if (password.empty())
204         {
205             messages::propertyMissing(asyncResp->res, "Password");
206         }
207 
208         return;
209     }
210 
211     int pamrc = pamAuthenticateUser(username, password);
212     bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD;
213     if ((pamrc != PAM_SUCCESS) && !isConfigureSelfOnly)
214     {
215         messages::resourceAtUriUnauthorized(asyncResp->res, req.url(),
216                                             "Invalid username or password");
217         return;
218     }
219 
220     // User is authenticated - create session
221     std::shared_ptr<persistent_data::UserSession> session =
222         persistent_data::SessionStore::getInstance().generateUserSession(
223             username, req.ipAddress, clientId,
224             persistent_data::PersistenceType::TIMEOUT, isConfigureSelfOnly);
225     if (session == nullptr)
226     {
227         messages::internalError(asyncResp->res);
228         return;
229     }
230 
231     asyncResp->res.addHeader("X-Auth-Token", session->sessionToken);
232     asyncResp->res.addHeader(
233         "Location", "/redfish/v1/SessionService/Sessions/" + session->uniqueId);
234     asyncResp->res.result(boost::beast::http::status::created);
235     if (session->isConfigureSelfOnly)
236     {
237         messages::passwordChangeRequired(
238             asyncResp->res,
239             crow::utility::urlFromPieces("redfish", "v1", "AccountService",
240                                          "Accounts", session->username));
241     }
242 
243     fillSessionObject(asyncResp->res, *session);
244 }
245 inline void handleSessionServiceHead(
246     crow::App& app, const crow::Request& req,
247     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
248 {
249 
250     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
251     {
252         return;
253     }
254     asyncResp->res.addHeader(
255         boost::beast::http::field::link,
256         "</redfish/v1/JsonSchemas/SessionService/SessionService.json>; rel=describedby");
257 }
258 inline void
259     handleSessionServiceGet(crow::App& app, const crow::Request& req,
260                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
261 
262 {
263     handleSessionServiceHead(app, req, asyncResp);
264     asyncResp->res.jsonValue["@odata.type"] =
265         "#SessionService.v1_0_2.SessionService";
266     asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/SessionService/";
267     asyncResp->res.jsonValue["Name"] = "Session Service";
268     asyncResp->res.jsonValue["Id"] = "SessionService";
269     asyncResp->res.jsonValue["Description"] = "Session Service";
270     asyncResp->res.jsonValue["SessionTimeout"] =
271         persistent_data::SessionStore::getInstance().getTimeoutInSeconds();
272     asyncResp->res.jsonValue["ServiceEnabled"] = true;
273 
274     asyncResp->res.jsonValue["Sessions"]["@odata.id"] =
275         "/redfish/v1/SessionService/Sessions";
276 }
277 
278 inline void handleSessionServicePatch(
279     crow::App& app, const crow::Request& req,
280     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
281 {
282     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
283     {
284         return;
285     }
286     std::optional<int64_t> sessionTimeout;
287     if (!json_util::readJsonPatch(req, asyncResp->res, "SessionTimeout",
288                                   sessionTimeout))
289     {
290         return;
291     }
292 
293     if (sessionTimeout)
294     {
295         // The mininum & maximum allowed values for session timeout
296         // are 30 seconds and 86400 seconds respectively as per the
297         // session service schema mentioned at
298         // https://redfish.dmtf.org/schemas/v1/SessionService.v1_1_7.json
299 
300         if (*sessionTimeout <= 86400 && *sessionTimeout >= 30)
301         {
302             std::chrono::seconds sessionTimeoutInseconds(*sessionTimeout);
303             persistent_data::SessionStore::getInstance().updateSessionTimeout(
304                 sessionTimeoutInseconds);
305             messages::propertyValueModified(asyncResp->res, "SessionTimeOut",
306                                             std::to_string(*sessionTimeout));
307         }
308         else
309         {
310             messages::propertyValueNotInList(asyncResp->res,
311                                              std::to_string(*sessionTimeout),
312                                              "SessionTimeOut");
313         }
314     }
315 }
316 
317 inline void requestRoutesSession(App& app)
318 {
319     BMCWEB_ROUTE(app, "/redfish/v1/SessionService/Sessions/<str>/")
320         .privileges(redfish::privileges::headSession)
321         .methods(boost::beast::http::verb::head)(
322             std::bind_front(handleSessionHead, std::ref(app)));
323 
324     BMCWEB_ROUTE(app, "/redfish/v1/SessionService/Sessions/<str>/")
325         .privileges(redfish::privileges::getSession)
326         .methods(boost::beast::http::verb::get)(
327             std::bind_front(handleSessionGet, std::ref(app)));
328 
329     BMCWEB_ROUTE(app, "/redfish/v1/SessionService/Sessions/<str>/")
330         .privileges(redfish::privileges::deleteSession)
331         .methods(boost::beast::http::verb::delete_)(
332             std::bind_front(handleSessionDelete, std::ref(app)));
333 
334     BMCWEB_ROUTE(app, "/redfish/v1/SessionService/Sessions/")
335         .privileges(redfish::privileges::headSessionCollection)
336         .methods(boost::beast::http::verb::head)(
337             std::bind_front(handleSessionCollectionHead, std::ref(app)));
338 
339     BMCWEB_ROUTE(app, "/redfish/v1/SessionService/Sessions/")
340         .privileges(redfish::privileges::getSessionCollection)
341         .methods(boost::beast::http::verb::get)(
342             std::bind_front(handleSessionCollectionGet, std::ref(app)));
343 
344     // Note, the next two routes technically don't match the privilege
345     // registry given the way login mechanisms work.  The base privilege
346     // registry lists this endpoint as requiring login privilege, but because
347     // this is the endpoint responsible for giving the login privilege, and it
348     // is itself its own route, it needs to not require Login
349     BMCWEB_ROUTE(app, "/redfish/v1/SessionService/Sessions/")
350         .privileges({})
351         .methods(boost::beast::http::verb::post)(
352             std::bind_front(handleSessionCollectionPost, std::ref(app)));
353 
354     BMCWEB_ROUTE(app, "/redfish/v1/SessionService/Sessions/Members/")
355         .privileges({})
356         .methods(boost::beast::http::verb::post)(
357             std::bind_front(handleSessionCollectionPost, std::ref(app)));
358 
359     BMCWEB_ROUTE(app, "/redfish/v1/SessionService/")
360         .privileges(redfish::privileges::headSessionService)
361         .methods(boost::beast::http::verb::head)(
362             std::bind_front(handleSessionServiceHead, std::ref(app)));
363 
364     BMCWEB_ROUTE(app, "/redfish/v1/SessionService/")
365         .privileges(redfish::privileges::getSessionService)
366         .methods(boost::beast::http::verb::get)(
367             std::bind_front(handleSessionServiceGet, std::ref(app)));
368 
369     BMCWEB_ROUTE(app, "/redfish/v1/SessionService/")
370         .privileges(redfish::privileges::patchSessionService)
371         .methods(boost::beast::http::verb::patch)(
372             std::bind_front(handleSessionServicePatch, std::ref(app)));
373 }
374 
375 } // namespace redfish
376