xref: /openbmc/bmcweb/features/redfish/lib/redfish_sessions.hpp (revision 55c7b7a2e58779580f33046d2dd8649243776700)
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     // Return data for created session
171     memberSession.doGet(res, req, {session->uniqueId});
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 loginCredentials = nlohmann::json::parse(req.body, nullptr, false);
194     if (loginCredentials.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 (loginCredentials.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 userIt = loginCredentials.find("UserName");
213     auto passIt = loginCredentials.find("Password");
214     if (userIt == loginCredentials.end() || passIt == loginCredentials.end()) {
215       httpRespCode = boost::beast::http::status::bad_request;
216 
217       if (userIt == loginCredentials.end()) {
218         messages::addMessageToErrorJson(errJson,
219                                         messages::propertyMissing("UserName"));
220       }
221 
222       if (passIt == loginCredentials.end()) {
223         messages::addMessageToErrorJson(errJson,
224                                         messages::propertyMissing("Password"));
225       }
226 
227       return false;
228     }
229 
230     // Check that given data is of valid type (string)
231     if (!userIt->is_string() || !passIt->is_string()) {
232       httpRespCode = boost::beast::http::status::bad_request;
233 
234       if (!userIt->is_string()) {
235         messages::addMessageToErrorJson(
236             errJson,
237             messages::propertyValueTypeError(userIt->dump(), "UserName"));
238       }
239 
240       if (!passIt->is_string()) {
241         messages::addMessageToErrorJson(
242             errJson,
243             messages::propertyValueTypeError(userIt->dump(), "Password"));
244       }
245 
246       return false;
247     }
248 
249     // Extract username and password
250     std::string username = userIt->get<const std::string>();
251     std::string password = passIt->get<const std::string>();
252 
253     // Verify that required fields are not empty
254     if (username.empty() || password.empty()) {
255       httpRespCode = boost::beast::http::status::bad_request;
256 
257       if (username.empty()) {
258         messages::addMessageToErrorJson(errJson,
259                                         messages::propertyMissing("UserName"));
260       }
261 
262       if (password.empty()) {
263         messages::addMessageToErrorJson(errJson,
264                                         messages::propertyMissing("Password"));
265       }
266 
267       return false;
268     }
269 
270     // Finally - try to authenticate user
271     if (!pamAuthenticateUser(username, password)) {
272       httpRespCode = boost::beast::http::status::unauthorized;
273 
274       messages::addMessageToErrorJson(
275           errJson, messages::resourceAtUriUnauthorized(
276                        std::string(req.url), "Invalid username or password"));
277 
278       return false;
279     }
280 
281     // User authenticated successfully
282     httpRespCode = boost::beast::http::status::ok;
283     user = username;
284 
285     return true;
286   }
287 
288   /**
289    * Member session to ensure consistency between collection's doPost and
290    * member's doGet, as they should return 100% matching data
291    */
292   Sessions memberSession;
293 };
294 
295 class SessionService : public Node {
296  public:
297   SessionService(CrowApp& app) : Node(app, "/redfish/v1/SessionService/") {
298     Node::json["@odata.type"] = "#SessionService.v1_0_2.SessionService";
299     Node::json["@odata.id"] = "/redfish/v1/SessionService/";
300     Node::json["@odata.context"] =
301         "/redfish/v1/$metadata#SessionService.SessionService";
302     Node::json["Name"] = "Session Service";
303     Node::json["Id"] = "SessionService";
304     Node::json["Description"] = "Session Service";
305     Node::json["SessionTimeout"] =
306         crow::persistent_data::SessionStore::getInstance()
307             .getTimeoutInSeconds();
308     Node::json["ServiceEnabled"] = true;
309 
310     entityPrivileges = {
311         {boost::beast::http::verb::get, {{"Login"}}},
312         {boost::beast::http::verb::head, {{"Login"}}},
313         {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
314         {boost::beast::http::verb::put, {{"ConfigureManager"}}},
315         {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
316         {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
317   }
318 
319  private:
320   void doGet(crow::Response& res, const crow::Request& req,
321              const std::vector<std::string>& params) override {
322     res.jsonValue = Node::json;
323     res.end();
324   }
325 };
326 
327 }  // namespace redfish
328