xref: /openbmc/bmcweb/redfish-core/lib/redfish_sessions.hpp (revision 4859bdbafbb2b78ae414fb050ad197db2f2cb28b)
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 "node.hpp"
20 #include "persistent_data_middleware.hpp"
21 
22 namespace redfish {
23 
24 class SessionCollection;
25 
26 class Sessions : public Node {
27  public:
28   Sessions(CrowApp& app)
29       : Node(app, "/redfish/v1/SessionService/Sessions/<str>/", std::string()) {
30     Node::json["@odata.type"] = "#Session.v1_0_2.Session";
31     Node::json["@odata.context"] = "/redfish/v1/$metadata#Session.Session";
32     Node::json["Name"] = "User Session";
33     Node::json["Description"] = "Manager User Session";
34 
35     entityPrivileges = {
36         {boost::beast::http::verb::get, {{"Login"}}},
37         {boost::beast::http::verb::head, {{"Login"}}},
38         {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
39         {boost::beast::http::verb::put, {{"ConfigureManager"}}},
40         {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
41         {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
42   }
43 
44  private:
45   void doGet(crow::Response& res, const crow::Request& req,
46              const std::vector<std::string>& params) override {
47     auto session =
48         crow::persistent_data::SessionStore::getInstance().getSessionByUid(
49             params[0]);
50 
51     if (session == nullptr) {
52       messages::addMessageToErrorJson(
53           res.jsonValue, messages::resourceNotFound("Session", params[0]));
54 
55       res.result(boost::beast::http::status::not_found);
56       res.end();
57       return;
58     }
59 
60     Node::json["Id"] = session->uniqueId;
61     Node::json["UserName"] = session->username;
62     Node::json["@odata.id"] =
63         "/redfish/v1/SessionService/Sessions/" + session->uniqueId;
64 
65     res.jsonValue = Node::json;
66     res.end();
67   }
68 
69   void doDelete(crow::Response& res, const crow::Request& req,
70                 const std::vector<std::string>& params) override {
71     // Need only 1 param which should be id of session to be deleted
72     if (params.size() != 1) {
73       // This should be handled by crow and never happen
74       BMCWEB_LOG_ERROR
75           << "Session DELETE has been called with invalid number of params";
76 
77       res.result(boost::beast::http::status::bad_request);
78       messages::addMessageToErrorJson(res.jsonValue, messages::generalError());
79 
80       res.end();
81       return;
82     }
83 
84     auto session =
85         crow::persistent_data::SessionStore::getInstance().getSessionByUid(
86             params[0]);
87 
88     if (session == nullptr) {
89       messages::addMessageToErrorJson(
90           res.jsonValue, messages::resourceNotFound("Session", params[0]));
91 
92       res.result(boost::beast::http::status::not_found);
93       res.end();
94       return;
95     }
96 
97     // DELETE should return representation of object that will be removed
98     doGet(res, req, params);
99 
100     crow::persistent_data::SessionStore::getInstance().removeSession(session);
101   }
102 
103   /**
104    * This allows SessionCollection to reuse this class' doGet method, to
105    * maintain consistency of returned data, as Collection's doPost should return
106    * data for created member which should match member's doGet result in 100%
107    */
108   friend SessionCollection;
109 };
110 
111 class SessionCollection : public Node {
112  public:
113   SessionCollection(CrowApp& app)
114       : Node(app, "/redfish/v1/SessionService/Sessions/"), memberSession(app) {
115     Node::json["@odata.type"] = "#SessionCollection.SessionCollection";
116     Node::json["@odata.id"] = "/redfish/v1/SessionService/Sessions/";
117     Node::json["@odata.context"] =
118         "/redfish/v1/$metadata#SessionCollection.SessionCollection";
119     Node::json["Name"] = "Session Collection";
120     Node::json["Description"] = "Session Collection";
121     Node::json["Members@odata.count"] = 0;
122     Node::json["Members"] = nlohmann::json::array();
123 
124     entityPrivileges = {
125         {boost::beast::http::verb::get, {{"Login"}}},
126         {boost::beast::http::verb::head, {{"Login"}}},
127         {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
128         {boost::beast::http::verb::put, {{"ConfigureManager"}}},
129         {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
130         {boost::beast::http::verb::post, {}}};
131   }
132 
133  private:
134   void doGet(crow::Response& res, const crow::Request& req,
135              const std::vector<std::string>& params) override {
136     std::vector<const std::string*> sessionIds =
137         crow::persistent_data::SessionStore::getInstance().getUniqueIds(
138             false, crow::persistent_data::PersistenceType::TIMEOUT);
139 
140     Node::json["Members@odata.count"] = sessionIds.size();
141     Node::json["Members"] = nlohmann::json::array();
142     for (const std::string* uid : sessionIds) {
143       Node::json["Members"].push_back(
144           {{"@odata.id", "/redfish/v1/SessionService/Sessions/" + *uid}});
145     }
146 
147     res.jsonValue = Node::json;
148     res.end();
149   }
150 
151   void doPost(crow::Response& res, const crow::Request& req,
152               const std::vector<std::string>& params) override {
153     boost::beast::http::status status;
154     std::string username;
155     bool userAuthSuccessful =
156         authenticateUser(req, status, username, res.jsonValue);
157     res.result(status);
158 
159     if (!userAuthSuccessful) {
160       res.end();
161       return;
162     }
163 
164     // User is authenticated - create session for him
165     auto session =
166         crow::persistent_data::SessionStore::getInstance().generateUserSession(
167             username);
168     res.addHeader("X-Auth-Token", session->sessionToken);
169 
170     res.addHeader("Location",
171                   "/redfish/v1/SessionService/Sessions/" + session->uniqueId);
172 
173     // Return data for created session
174     memberSession.doGet(res, req, {session->uniqueId});
175 
176     // No need for res.end(), as it is called by doGet()
177   }
178 
179   /**
180    * @brief Verifies data provided in request and tries to authenticate user
181    *
182    * @param[in]  req            Crow request containing authentication data
183    * @param[out] httpRespCode   HTTP Code that should be returned in response
184    * @param[out] user           Retrieved username - not filled on failure
185    * @param[out] errJson        JSON to which error messages will be written
186    *
187    * @return true if authentication was successful, false otherwise
188    */
189   bool authenticateUser(const crow::Request& req,
190                         boost::beast::http::status& httpRespCode,
191                         std::string& user, nlohmann::json& errJson) {
192     // We need only UserName and Password - nothing more, nothing less
193     static constexpr const unsigned int numberOfRequiredFieldsInReq = 2;
194 
195     // call with exceptions disabled
196     auto loginCredentials = nlohmann::json::parse(req.body, nullptr, false);
197     if (loginCredentials.is_discarded()) {
198       httpRespCode = boost::beast::http::status::bad_request;
199 
200       messages::addMessageToErrorJson(errJson, messages::malformedJSON());
201 
202       return false;
203     }
204 
205     // Check that there are only as many fields as there should be
206     if (loginCredentials.size() != numberOfRequiredFieldsInReq) {
207       httpRespCode = boost::beast::http::status::bad_request;
208 
209       messages::addMessageToErrorJson(errJson, messages::malformedJSON());
210 
211       return false;
212     }
213 
214     // Find fields that we need - UserName and Password
215     auto userIt = loginCredentials.find("UserName");
216     auto passIt = loginCredentials.find("Password");
217     if (userIt == loginCredentials.end() || passIt == loginCredentials.end()) {
218       httpRespCode = boost::beast::http::status::bad_request;
219 
220       if (userIt == loginCredentials.end()) {
221         messages::addMessageToErrorJson(errJson,
222                                         messages::propertyMissing("UserName"));
223       }
224 
225       if (passIt == loginCredentials.end()) {
226         messages::addMessageToErrorJson(errJson,
227                                         messages::propertyMissing("Password"));
228       }
229 
230       return false;
231     }
232 
233     // Check that given data is of valid type (string)
234     if (!userIt->is_string() || !passIt->is_string()) {
235       httpRespCode = boost::beast::http::status::bad_request;
236 
237       if (!userIt->is_string()) {
238         messages::addMessageToErrorJson(
239             errJson,
240             messages::propertyValueTypeError(userIt->dump(), "UserName"));
241       }
242 
243       if (!passIt->is_string()) {
244         messages::addMessageToErrorJson(
245             errJson,
246             messages::propertyValueTypeError(userIt->dump(), "Password"));
247       }
248 
249       return false;
250     }
251 
252     // Extract username and password
253     std::string username = userIt->get<const std::string>();
254     std::string password = passIt->get<const std::string>();
255 
256     // Verify that required fields are not empty
257     if (username.empty() || password.empty()) {
258       httpRespCode = boost::beast::http::status::bad_request;
259 
260       if (username.empty()) {
261         messages::addMessageToErrorJson(errJson,
262                                         messages::propertyMissing("UserName"));
263       }
264 
265       if (password.empty()) {
266         messages::addMessageToErrorJson(errJson,
267                                         messages::propertyMissing("Password"));
268       }
269 
270       return false;
271     }
272 
273     // Finally - try to authenticate user
274     if (!pamAuthenticateUser(username, password)) {
275       httpRespCode = boost::beast::http::status::unauthorized;
276 
277       messages::addMessageToErrorJson(
278           errJson, messages::resourceAtUriUnauthorized(
279                        std::string(req.url), "Invalid username or password"));
280 
281       return false;
282     }
283 
284     // User authenticated successfully
285     httpRespCode = boost::beast::http::status::ok;
286     user = username;
287 
288     return true;
289   }
290 
291   /**
292    * Member session to ensure consistency between collection's doPost and
293    * member's doGet, as they should return 100% matching data
294    */
295   Sessions memberSession;
296 };
297 
298 class SessionService : public Node {
299  public:
300   SessionService(CrowApp& app) : Node(app, "/redfish/v1/SessionService/") {
301     Node::json["@odata.type"] = "#SessionService.v1_0_2.SessionService";
302     Node::json["@odata.id"] = "/redfish/v1/SessionService/";
303     Node::json["@odata.context"] =
304         "/redfish/v1/$metadata#SessionService.SessionService";
305     Node::json["Name"] = "Session Service";
306     Node::json["Id"] = "SessionService";
307     Node::json["Description"] = "Session Service";
308     Node::json["SessionTimeout"] =
309         crow::persistent_data::SessionStore::getInstance()
310             .getTimeoutInSeconds();
311     Node::json["ServiceEnabled"] = true;
312 
313     entityPrivileges = {
314         {boost::beast::http::verb::get, {{"Login"}}},
315         {boost::beast::http::verb::head, {{"Login"}}},
316         {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
317         {boost::beast::http::verb::put, {{"ConfigureManager"}}},
318         {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
319         {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
320   }
321 
322  private:
323   void doGet(crow::Response& res, const crow::Request& req,
324              const std::vector<std::string>& params) override {
325     res.jsonValue = Node::json;
326     res.end();
327   }
328 };
329 
330 }  // namespace redfish
331