xref: /openbmc/bmcweb/features/redfish/lib/redfish_sessions.hpp (revision 724340d72d0376e3b79d395354ac39e2273d5dd5)
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 <registries/privilege_registry.hpp>
24 
25 namespace redfish
26 {
27 
28 inline void fillSessionObject(crow::Response& res,
29                               const persistent_data::UserSession& session)
30 {
31     res.jsonValue["Id"] = session.uniqueId;
32     res.jsonValue["UserName"] = session.username;
33     res.jsonValue["@odata.id"] =
34         "/redfish/v1/SessionService/Sessions/" + session.uniqueId;
35     res.jsonValue["@odata.type"] = "#Session.v1_3_0.Session";
36     res.jsonValue["Name"] = "User Session";
37     res.jsonValue["Description"] = "Manager User Session";
38     res.jsonValue["ClientOriginIPAddress"] = session.clientIp;
39 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
40     res.jsonValue["Oem"]["OpenBMC"]["@odata.type"] =
41         "#OemSession.v1_0_0.Session";
42     res.jsonValue["Oem"]["OpenBMC"]["ClientID"] = session.clientId;
43 #endif
44 }
45 
46 inline void
47     handleSessionGet(const crow::Request& /*req*/,
48                      const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
49                      const std::string& sessionId)
50 {
51     // Note that control also reaches here via doPost and doDelete.
52     auto session =
53         persistent_data::SessionStore::getInstance().getSessionByUid(sessionId);
54 
55     if (session == nullptr)
56     {
57         messages::resourceNotFound(asyncResp->res, "Session", sessionId);
58         return;
59     }
60 
61     fillSessionObject(asyncResp->res, *session);
62 }
63 
64 inline void
65     handleSessionDelete(const crow::Request& req,
66                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
67                         const std::string& sessionId)
68 {
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     // Perform a proper ConfigureSelf authority check.  If a
79     // session is being used to DELETE some other user's session,
80     // then the ConfigureSelf privilege does not apply.  In that
81     // case, perform the authority check again without the user's
82     // ConfigureSelf privilege.
83     if (session->username != req.session->username)
84     {
85         Privileges effectiveUserPrivileges =
86             redfish::getUserPrivileges(req.userRole);
87 
88         if (!effectiveUserPrivileges.isSupersetOf({"ConfigureUsers"}))
89         {
90             messages::insufficientPrivilege(asyncResp->res);
91             return;
92         }
93     }
94 
95     persistent_data::SessionStore::getInstance().removeSession(session);
96     messages::success(asyncResp->res);
97 }
98 
99 inline nlohmann::json getSessionCollectionMembers()
100 {
101     std::vector<const std::string*> sessionIds =
102         persistent_data::SessionStore::getInstance().getUniqueIds(
103             false, persistent_data::PersistenceType::TIMEOUT);
104     nlohmann::json ret = nlohmann::json::array();
105     for (const std::string* uid : sessionIds)
106     {
107         ret.push_back(
108             {{"@odata.id", "/redfish/v1/SessionService/Sessions/" + *uid}});
109     }
110     return ret;
111 }
112 
113 inline void handleSessionCollectionGet(
114     const crow::Request& /*req*/,
115     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
116 {
117     asyncResp->res.jsonValue["Members"] = getSessionCollectionMembers();
118     asyncResp->res.jsonValue["Members@odata.count"] =
119         asyncResp->res.jsonValue["Members"].size();
120     asyncResp->res.jsonValue["@odata.type"] =
121         "#SessionCollection.SessionCollection";
122     asyncResp->res.jsonValue["@odata.id"] =
123         "/redfish/v1/SessionService/Sessions/";
124     asyncResp->res.jsonValue["Name"] = "Session Collection";
125     asyncResp->res.jsonValue["Description"] = "Session Collection";
126 }
127 
128 inline void handleSessionCollectionMembersGet(
129     const crow::Request& /*req*/,
130     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
131 {
132     asyncResp->res.jsonValue = getSessionCollectionMembers();
133 }
134 
135 void handleSessionCollectionPost(
136     const crow::Request& req,
137     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
138 {
139     std::string username;
140     std::string password;
141     std::optional<nlohmann::json> oemObject;
142     std::string clientId;
143     if (!json_util::readJsonPatch(req, asyncResp->res, "UserName", username,
144                                   "Password", password, "Oem", oemObject))
145     {
146         return;
147     }
148 
149     if (password.empty() || username.empty() ||
150         asyncResp->res.result() != boost::beast::http::status::ok)
151     {
152         if (username.empty())
153         {
154             messages::propertyMissing(asyncResp->res, "UserName");
155         }
156 
157         if (password.empty())
158         {
159             messages::propertyMissing(asyncResp->res, "Password");
160         }
161 
162         return;
163     }
164 
165     int pamrc = pamAuthenticateUser(username, password);
166     bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD;
167     if ((pamrc != PAM_SUCCESS) && !isConfigureSelfOnly)
168     {
169         messages::resourceAtUriUnauthorized(asyncResp->res, req.urlView,
170                                             "Invalid username or password");
171         return;
172     }
173 #ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
174     if (oemObject)
175     {
176         std::optional<nlohmann::json> bmcOem;
177         if (!json_util::readJson(*oemObject, asyncResp->res, "OpenBMC", bmcOem))
178         {
179             return;
180         }
181         if (!json_util::readJson(*bmcOem, asyncResp->res, "ClientID", clientId))
182         {
183             BMCWEB_LOG_ERROR << "Could not read ClientId";
184             return;
185         }
186     }
187 #endif
188 
189     // User is authenticated - create session
190     std::shared_ptr<persistent_data::UserSession> session =
191         persistent_data::SessionStore::getInstance().generateUserSession(
192             username, req.ipAddress, clientId,
193             persistent_data::PersistenceType::TIMEOUT, isConfigureSelfOnly);
194     asyncResp->res.addHeader("X-Auth-Token", session->sessionToken);
195     asyncResp->res.addHeader(
196         "Location", "/redfish/v1/SessionService/Sessions/" + session->uniqueId);
197     asyncResp->res.result(boost::beast::http::status::created);
198     if (session->isConfigureSelfOnly)
199     {
200         messages::passwordChangeRequired(
201             asyncResp->res,
202             crow::utility::urlFromPieces("redfish", "v1", "AccountService",
203                                          "Accounts", req.session->username));
204     }
205 
206     fillSessionObject(asyncResp->res, *session);
207 }
208 inline void
209     handleSessionServiceGet(const crow::Request& /* req */,
210                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
211 
212 {
213     asyncResp->res.jsonValue["@odata.type"] =
214         "#SessionService.v1_0_2.SessionService";
215     asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/SessionService/";
216     asyncResp->res.jsonValue["Name"] = "Session Service";
217     asyncResp->res.jsonValue["Id"] = "SessionService";
218     asyncResp->res.jsonValue["Description"] = "Session Service";
219     asyncResp->res.jsonValue["SessionTimeout"] =
220         persistent_data::SessionStore::getInstance().getTimeoutInSeconds();
221     asyncResp->res.jsonValue["ServiceEnabled"] = true;
222 
223     asyncResp->res.jsonValue["Sessions"] = {
224         {"@odata.id", "/redfish/v1/SessionService/Sessions"}};
225 }
226 
227 inline void handleSessionServicePatch(
228     const crow::Request& req,
229     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
230 {
231     std::optional<int64_t> sessionTimeout;
232     if (!json_util::readJsonPatch(req, asyncResp->res, "SessionTimeout",
233                                   sessionTimeout))
234     {
235         return;
236     }
237 
238     if (sessionTimeout)
239     {
240         // The mininum & maximum allowed values for session timeout
241         // are 30 seconds and 86400 seconds respectively as per the
242         // session service schema mentioned at
243         // https://redfish.dmtf.org/schemas/v1/SessionService.v1_1_7.json
244 
245         if (*sessionTimeout <= 86400 && *sessionTimeout >= 30)
246         {
247             std::chrono::seconds sessionTimeoutInseconds(*sessionTimeout);
248             persistent_data::SessionStore::getInstance().updateSessionTimeout(
249                 sessionTimeoutInseconds);
250             messages::propertyValueModified(asyncResp->res, "SessionTimeOut",
251                                             std::to_string(*sessionTimeout));
252         }
253         else
254         {
255             messages::propertyValueNotInList(asyncResp->res,
256                                              std::to_string(*sessionTimeout),
257                                              "SessionTimeOut");
258         }
259     }
260 }
261 
262 inline void requestRoutesSession(App& app)
263 {
264     BMCWEB_ROUTE(app, "/redfish/v1/SessionService/Sessions/<str>/")
265         .privileges(redfish::privileges::getSession)
266         .methods(boost::beast::http::verb::get)(handleSessionGet);
267 
268     BMCWEB_ROUTE(app, "/redfish/v1/SessionService/Sessions/<str>/")
269         .privileges(redfish::privileges::deleteSession)
270         .methods(boost::beast::http::verb::delete_)(handleSessionDelete);
271 
272     BMCWEB_ROUTE(app, "/redfish/v1/SessionService/Sessions/")
273         .privileges(redfish::privileges::getSessionCollection)
274         .methods(boost::beast::http::verb::get)(handleSessionCollectionGet);
275 
276     // Note, the next route technically doesn't match the privilege
277     // registry given the way login mechanisms work.  The base privilege
278     // registry lists this endpoint as requiring login privilege, but because
279     // this is the endpoint responsible for giving the login privilege, and it
280     // is itself its own route, it needs to not require Login
281     BMCWEB_ROUTE(app, "/redfish/v1/SessionService/Sessions/")
282         .privileges({})
283         .methods(boost::beast::http::verb::post)(handleSessionCollectionPost);
284 
285     BMCWEB_ROUTE(app, "/redfish/v1/SessionService/")
286         .privileges(redfish::privileges::getSessionService)
287         .methods(boost::beast::http::verb::get)(handleSessionServiceGet);
288 
289     BMCWEB_ROUTE(app, "/redfish/v1/SessionService/")
290         .privileges(redfish::privileges::patchSessionService)
291         .methods(boost::beast::http::verb::patch)(handleSessionServicePatch);
292 }
293 
294 } // namespace redfish
295