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