xref: /openbmc/bmcweb/features/redfish/lib/redfish_sessions.hpp (revision 4b1b8683d31260b3032bb9f9fcde1eadaed4e1e5)
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::PersistentData::SessionStore::getInstance().get_session_by_uid(
49             params[0]);
50 
51     if (session == nullptr) {
52       messages::addMessageToErrorJson(
53           res.json_value, 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->unique_id;
61     Node::json["UserName"] = session->username;
62     Node::json["@odata.id"] =
63         "/redfish/v1/SessionService/Sessions/" + session->unique_id;
64 
65     res.json_value = 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       CROW_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.json_value, messages::generalError());
79 
80       res.end();
81       return;
82     }
83 
84     auto session =
85         crow::PersistentData::SessionStore::getInstance().get_session_by_uid(
86             params[0]);
87 
88     if (session == nullptr) {
89       messages::addMessageToErrorJson(
90           res.json_value, 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::PersistentData::SessionStore::getInstance().remove_session(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*> session_ids =
137         crow::PersistentData::SessionStore::getInstance().get_unique_ids(
138             false, crow::PersistentData::PersistenceType::TIMEOUT);
139 
140     Node::json["Members@odata.count"] = session_ids.size();
141     Node::json["Members"] = nlohmann::json::array();
142     for (const std::string* uid : session_ids) {
143       Node::json["Members"].push_back(
144           {{"@odata.id", "/redfish/v1/SessionService/Sessions/" + *uid}});
145     }
146 
147     res.json_value = 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.json_value);
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::PersistentData::SessionStore::getInstance().generate_user_session(
167             username);
168     res.add_header("X-Auth-Token", session->session_token);
169 
170     // Return data for created session
171     memberSession.doGet(res, req, {session->unique_id});
172 
173     // No need for res.end(), as it is called by doGet()
174   }
175 
176   /**
177    * @brief Verifies data provided in request and tries to authenticate user
178    *
179    * @param[in]  req            Crow request containing authentication data
180    * @param[out] httpRespCode   HTTP Code that should be returned in response
181    * @param[out] user           Retrieved username - not filled on failure
182    * @param[out] errJson        JSON to which error messages will be written
183    *
184    * @return true if authentication was successful, false otherwise
185    */
186   bool authenticateUser(const crow::request& req,
187                         boost::beast::http::status& httpRespCode,
188                         std::string& user, nlohmann::json& errJson) {
189     // We need only UserName and Password - nothing more, nothing less
190     static constexpr const unsigned int numberOfRequiredFieldsInReq = 2;
191 
192     // call with exceptions disabled
193     auto login_credentials = nlohmann::json::parse(req.body, nullptr, false);
194     if (login_credentials.is_discarded()) {
195       httpRespCode = boost::beast::http::status::bad_request;
196 
197       messages::addMessageToErrorJson(errJson, messages::malformedJSON());
198 
199       return false;
200     }
201 
202     // Check that there are only as many fields as there should be
203     if (login_credentials.size() != numberOfRequiredFieldsInReq) {
204       httpRespCode = boost::beast::http::status::bad_request;
205 
206       messages::addMessageToErrorJson(errJson, messages::malformedJSON());
207 
208       return false;
209     }
210 
211     // Find fields that we need - UserName and Password
212     auto user_it = login_credentials.find("UserName");
213     auto pass_it = login_credentials.find("Password");
214     if (user_it == login_credentials.end() ||
215         pass_it == login_credentials.end()) {
216       httpRespCode = boost::beast::http::status::bad_request;
217 
218       if (user_it == login_credentials.end()) {
219         messages::addMessageToErrorJson(errJson,
220                                         messages::propertyMissing("UserName"));
221       }
222 
223       if (pass_it == login_credentials.end()) {
224         messages::addMessageToErrorJson(errJson,
225                                         messages::propertyMissing("Password"));
226       }
227 
228       return false;
229     }
230 
231     // Check that given data is of valid type (string)
232     if (!user_it->is_string() || !pass_it->is_string()) {
233       httpRespCode = boost::beast::http::status::bad_request;
234 
235       if (!user_it->is_string()) {
236         messages::addMessageToErrorJson(
237             errJson,
238             messages::propertyValueTypeError(user_it->dump(), "UserName"));
239       }
240 
241       if (!pass_it->is_string()) {
242         messages::addMessageToErrorJson(
243             errJson,
244             messages::propertyValueTypeError(user_it->dump(), "Password"));
245       }
246 
247       return false;
248     }
249 
250     // Extract username and password
251     std::string username = user_it->get<const std::string>();
252     std::string password = pass_it->get<const std::string>();
253 
254     // Verify that required fields are not empty
255     if (username.empty() || password.empty()) {
256       httpRespCode = boost::beast::http::status::bad_request;
257 
258       if (username.empty()) {
259         messages::addMessageToErrorJson(errJson,
260                                         messages::propertyMissing("UserName"));
261       }
262 
263       if (password.empty()) {
264         messages::addMessageToErrorJson(errJson,
265                                         messages::propertyMissing("Password"));
266       }
267 
268       return false;
269     }
270 
271     // Finally - try to authenticate user
272     if (!pam_authenticate_user(username, password)) {
273       httpRespCode = boost::beast::http::status::unauthorized;
274 
275       messages::addMessageToErrorJson(
276           errJson, messages::resourceAtUriUnauthorized(
277                        std::string(req.url), "Invalid username or password"));
278 
279       return false;
280     }
281 
282     // User authenticated successfully
283     httpRespCode = boost::beast::http::status::ok;
284     user = username;
285 
286     return true;
287   }
288 
289   /**
290    * Member session to ensure consistency between collection's doPost and
291    * member's doGet, as they should return 100% matching data
292    */
293   Sessions memberSession;
294 };
295 
296 class SessionService : public Node {
297  public:
298   SessionService(CrowApp& app) : Node(app, "/redfish/v1/SessionService/") {
299     Node::json["@odata.type"] = "#SessionService.v1_0_2.SessionService";
300     Node::json["@odata.id"] = "/redfish/v1/SessionService/";
301     Node::json["@odata.context"] =
302         "/redfish/v1/$metadata#SessionService.SessionService";
303     Node::json["Name"] = "Session Service";
304     Node::json["Id"] = "SessionService";
305     Node::json["Description"] = "Session Service";
306     Node::json["SessionTimeout"] =
307         crow::PersistentData::SessionStore::getInstance()
308             .get_timeout_in_seconds();
309     Node::json["ServiceEnabled"] = true;
310 
311     entityPrivileges = {
312         {boost::beast::http::verb::get, {{"Login"}}},
313         {boost::beast::http::verb::head, {{"Login"}}},
314         {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
315         {boost::beast::http::verb::put, {{"ConfigureManager"}}},
316         {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
317         {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
318   }
319 
320  private:
321   void doGet(crow::response& res, const crow::request& req,
322              const std::vector<std::string>& params) override {
323     res.json_value = Node::json;
324     res.end();
325   }
326 };
327 
328 }  // namespace redfish
329