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