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