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