blob: 5e7d3df458b3f40c80131e33477ac4e6e79ed0cb [file] [log] [blame]
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +01001/*
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
Borawski.Lukasz43a095a2018-02-19 15:39:01 +010017
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +010018#include "error_messages.hpp"
Ed Tanous52cc1122020-07-18 13:51:21 -070019#include "persistent_data.hpp"
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010020
John Edward Broadbent7e860f12021-04-08 15:57:16 -070021#include <app.hpp>
Ed Tanousace85d62021-10-26 12:45:59 -070022#include <http/utility.hpp>
Ed Tanoused398212021-06-09 17:05:54 -070023#include <registries/privilege_registry.hpp>
John Edward Broadbent7e860f12021-04-08 15:57:16 -070024
Ed Tanous1abe55e2018-09-05 08:30:59 -070025namespace redfish
26{
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010027
Ed Tanous4f48d5f2021-06-21 08:27:45 -070028inline void fillSessionObject(crow::Response& res,
29 const persistent_data::UserSession& session)
Ed Tanous1abe55e2018-09-05 08:30:59 -070030{
Ed Tanousfaa34cc2021-06-03 13:27:02 -070031 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;
Sunitha Harishc0ea7ae2020-10-30 02:37:30 -050039#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
Ed Tanousfaa34cc2021-06-03 13:27:02 -070040 res.jsonValue["Oem"]["OpenBMC"]["@odata.type"] =
41 "#OemSession.v1_0_0.Session";
42 res.jsonValue["Oem"]["OpenBMC"]["ClientID"] = session.clientId;
Sunitha Harish08bdcc72020-05-12 05:17:57 -050043#endif
Ed Tanousfaa34cc2021-06-03 13:27:02 -070044}
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010045
Ed Tanous724340d2022-03-14 09:10:07 -070046inline 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
64inline 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
99inline 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
113inline 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
128inline void handleSessionCollectionMembersGet(
129 const crow::Request& /*req*/,
130 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
131{
132 asyncResp->res.jsonValue = getSessionCollectionMembers();
133}
134
135void 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}
208inline 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
227inline 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
Ed Tanousfaa34cc2021-06-03 13:27:02 -0700262inline void requestRoutesSession(App& app)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700263{
Ed Tanousfaa34cc2021-06-03 13:27:02 -0700264 BMCWEB_ROUTE(app, "/redfish/v1/SessionService/Sessions/<str>/")
Ed Tanoused398212021-06-09 17:05:54 -0700265 .privileges(redfish::privileges::getSession)
Ed Tanous724340d2022-03-14 09:10:07 -0700266 .methods(boost::beast::http::verb::get)(handleSessionGet);
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100267
Ed Tanousfaa34cc2021-06-03 13:27:02 -0700268 BMCWEB_ROUTE(app, "/redfish/v1/SessionService/Sessions/<str>/")
Ed Tanoused398212021-06-09 17:05:54 -0700269 .privileges(redfish::privileges::deleteSession)
Ed Tanous724340d2022-03-14 09:10:07 -0700270 .methods(boost::beast::http::verb::delete_)(handleSessionDelete);
Ed Tanousfaa34cc2021-06-03 13:27:02 -0700271
272 BMCWEB_ROUTE(app, "/redfish/v1/SessionService/Sessions/")
Ed Tanoused398212021-06-09 17:05:54 -0700273 .privileges(redfish::privileges::getSessionCollection)
Ed Tanous724340d2022-03-14 09:10:07 -0700274 .methods(boost::beast::http::verb::get)(handleSessionCollectionGet);
Ed Tanousfaa34cc2021-06-03 13:27:02 -0700275
Ed Tanous724340d2022-03-14 09:10:07 -0700276 // 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
Ed Tanousfaa34cc2021-06-03 13:27:02 -0700281 BMCWEB_ROUTE(app, "/redfish/v1/SessionService/Sessions/")
282 .privileges({})
Ed Tanous724340d2022-03-14 09:10:07 -0700283 .methods(boost::beast::http::verb::post)(handleSessionCollectionPost);
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100284
Ed Tanousfaa34cc2021-06-03 13:27:02 -0700285 BMCWEB_ROUTE(app, "/redfish/v1/SessionService/")
Ed Tanoused398212021-06-09 17:05:54 -0700286 .privileges(redfish::privileges::getSessionService)
Ed Tanous724340d2022-03-14 09:10:07 -0700287 .methods(boost::beast::http::verb::get)(handleSessionServiceGet);
Borawski.Lukasz5d27b852018-02-08 13:24:24 +0100288
Ed Tanousfaa34cc2021-06-03 13:27:02 -0700289 BMCWEB_ROUTE(app, "/redfish/v1/SessionService/")
Ed Tanoused398212021-06-09 17:05:54 -0700290 .privileges(redfish::privileges::patchSessionService)
Ed Tanous724340d2022-03-14 09:10:07 -0700291 .methods(boost::beast::http::verb::patch)(handleSessionServicePatch);
Ed Tanousfaa34cc2021-06-03 13:27:02 -0700292}
Borawski.Lukasz5d27b852018-02-08 13:24:24 +0100293
Ed Tanous1abe55e2018-09-05 08:30:59 -0700294} // namespace redfish