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