xref: /openbmc/bmcweb/features/redfish/lib/redfish_sessions.hpp (revision 2b7981f6e53f76c662d427ced8bd9cffc5dde695)
1*2b7981f6SKowalski, Kamil /*
2*2b7981f6SKowalski, Kamil // Copyright (c) 2018 Intel Corporation
3*2b7981f6SKowalski, Kamil //
4*2b7981f6SKowalski, Kamil // Licensed under the Apache License, Version 2.0 (the "License");
5*2b7981f6SKowalski, Kamil // you may not use this file except in compliance with the License.
6*2b7981f6SKowalski, Kamil // You may obtain a copy of the License at
7*2b7981f6SKowalski, Kamil //
8*2b7981f6SKowalski, Kamil //      http://www.apache.org/licenses/LICENSE-2.0
9*2b7981f6SKowalski, Kamil //
10*2b7981f6SKowalski, Kamil // Unless required by applicable law or agreed to in writing, software
11*2b7981f6SKowalski, Kamil // distributed under the License is distributed on an "AS IS" BASIS,
12*2b7981f6SKowalski, Kamil // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*2b7981f6SKowalski, Kamil // See the License for the specific language governing permissions and
14*2b7981f6SKowalski, Kamil // limitations under the License.
15*2b7981f6SKowalski, Kamil */
16*2b7981f6SKowalski, Kamil #pragma once
17*2b7981f6SKowalski, Kamil #include <tuple>
18*2b7981f6SKowalski, Kamil #include "node.hpp"
19*2b7981f6SKowalski, Kamil #include "session_storage_singleton.hpp"
20*2b7981f6SKowalski, Kamil 
21*2b7981f6SKowalski, Kamil namespace redfish {
22*2b7981f6SKowalski, Kamil 
23*2b7981f6SKowalski, Kamil class SessionCollection;
24*2b7981f6SKowalski, Kamil 
25*2b7981f6SKowalski, Kamil class Sessions : public Node {
26*2b7981f6SKowalski, Kamil  public:
27*2b7981f6SKowalski, Kamil   template <typename CrowApp, typename PrivilegeProvider>
28*2b7981f6SKowalski, Kamil   Sessions(CrowApp& app, PrivilegeProvider& provider)
29*2b7981f6SKowalski, Kamil       : Node(app, provider, "#Session.v1_0_2.Session",
30*2b7981f6SKowalski, Kamil              "/redfish/v1/SessionService/Sessions/<str>", std::string()) {
31*2b7981f6SKowalski, Kamil     nodeJson["@odata.type"] = Node::odataType;
32*2b7981f6SKowalski, Kamil     nodeJson["@odata.context"] = "/redfish/v1/$metadata#Session.Session";
33*2b7981f6SKowalski, Kamil     nodeJson["Name"] = "User Session";
34*2b7981f6SKowalski, Kamil     nodeJson["Description"] = "Manager User Session";
35*2b7981f6SKowalski, Kamil   }
36*2b7981f6SKowalski, Kamil 
37*2b7981f6SKowalski, Kamil  private:
38*2b7981f6SKowalski, Kamil   void doGet(crow::response& res, const crow::request& req,
39*2b7981f6SKowalski, Kamil              const std::vector<std::string>& params) override {
40*2b7981f6SKowalski, Kamil     auto session =
41*2b7981f6SKowalski, Kamil         crow::PersistentData::session_store->get_session_by_uid(params[0]);
42*2b7981f6SKowalski, Kamil 
43*2b7981f6SKowalski, Kamil     if (session == nullptr) {
44*2b7981f6SKowalski, Kamil       res.code = static_cast<int>(HttpRespCode::NOT_FOUND);
45*2b7981f6SKowalski, Kamil       res.end();
46*2b7981f6SKowalski, Kamil       return;
47*2b7981f6SKowalski, Kamil     }
48*2b7981f6SKowalski, Kamil 
49*2b7981f6SKowalski, Kamil     nodeJson["Id"] = session->unique_id;
50*2b7981f6SKowalski, Kamil     nodeJson["UserName"] = session->username;
51*2b7981f6SKowalski, Kamil     nodeJson["@odata.id"] =
52*2b7981f6SKowalski, Kamil         "/redfish/v1/SessionService/Sessions/" + session->unique_id;
53*2b7981f6SKowalski, Kamil 
54*2b7981f6SKowalski, Kamil     res.json_value = nodeJson;
55*2b7981f6SKowalski, Kamil     res.end();
56*2b7981f6SKowalski, Kamil   }
57*2b7981f6SKowalski, Kamil 
58*2b7981f6SKowalski, Kamil   void doDelete(crow::response& res, const crow::request& req,
59*2b7981f6SKowalski, Kamil                 const std::vector<std::string>& params) override {
60*2b7981f6SKowalski, Kamil     // Need only 1 param which should be id of session to be deleted
61*2b7981f6SKowalski, Kamil     if (params.size() != 1) {
62*2b7981f6SKowalski, Kamil       res.code = static_cast<int>(HttpRespCode::BAD_REQUEST);
63*2b7981f6SKowalski, Kamil       res.end();
64*2b7981f6SKowalski, Kamil       return;
65*2b7981f6SKowalski, Kamil     }
66*2b7981f6SKowalski, Kamil 
67*2b7981f6SKowalski, Kamil     auto session =
68*2b7981f6SKowalski, Kamil         crow::PersistentData::session_store->get_session_by_uid(params[0]);
69*2b7981f6SKowalski, Kamil 
70*2b7981f6SKowalski, Kamil     if (session == nullptr) {
71*2b7981f6SKowalski, Kamil       res.code = static_cast<int>(HttpRespCode::NOT_FOUND);
72*2b7981f6SKowalski, Kamil       res.end();
73*2b7981f6SKowalski, Kamil       return;
74*2b7981f6SKowalski, Kamil     }
75*2b7981f6SKowalski, Kamil 
76*2b7981f6SKowalski, Kamil     crow::PersistentData::session_store->remove_session(session);
77*2b7981f6SKowalski, Kamil     res.code = static_cast<int>(HttpRespCode::OK);
78*2b7981f6SKowalski, Kamil     res.end();
79*2b7981f6SKowalski, Kamil   }
80*2b7981f6SKowalski, Kamil 
81*2b7981f6SKowalski, Kamil   /**
82*2b7981f6SKowalski, Kamil    * This allows SessionCollection to reuse this class' doGet method, to
83*2b7981f6SKowalski, Kamil    * maintain consistency of returned data, as Collection's doPost should return
84*2b7981f6SKowalski, Kamil    * data for created member which should match member's doGet result in 100%
85*2b7981f6SKowalski, Kamil    */
86*2b7981f6SKowalski, Kamil   friend SessionCollection;
87*2b7981f6SKowalski, Kamil 
88*2b7981f6SKowalski, Kamil   nlohmann::json nodeJson;
89*2b7981f6SKowalski, Kamil };
90*2b7981f6SKowalski, Kamil 
91*2b7981f6SKowalski, Kamil class SessionCollection : public Node {
92*2b7981f6SKowalski, Kamil  public:
93*2b7981f6SKowalski, Kamil   template <typename CrowApp, typename PrivilegeProvider>
94*2b7981f6SKowalski, Kamil   SessionCollection(CrowApp& app, PrivilegeProvider& provider)
95*2b7981f6SKowalski, Kamil       : Node(app, provider, "#SessionCollection.SessionCollection",
96*2b7981f6SKowalski, Kamil              "/redfish/v1/SessionService/Sessions/"),
97*2b7981f6SKowalski, Kamil         memberSession(app, provider) {
98*2b7981f6SKowalski, Kamil     nodeJson["@odata.type"] = Node::odataType;
99*2b7981f6SKowalski, Kamil     nodeJson["@odata.id"] = Node::odataId;
100*2b7981f6SKowalski, Kamil     nodeJson["@odata.context"] =
101*2b7981f6SKowalski, Kamil         "/redfish/v1/$metadata#SessionCollection.SessionCollection";
102*2b7981f6SKowalski, Kamil     nodeJson["Name"] = "Session Collection";
103*2b7981f6SKowalski, Kamil     nodeJson["Description"] = "Session Collection";
104*2b7981f6SKowalski, Kamil     nodeJson["Members@odata.count"] = 0;
105*2b7981f6SKowalski, Kamil     nodeJson["Members"] = nlohmann::json::array();
106*2b7981f6SKowalski, Kamil   }
107*2b7981f6SKowalski, Kamil 
108*2b7981f6SKowalski, Kamil  private:
109*2b7981f6SKowalski, Kamil   void doGet(crow::response& res, const crow::request& req,
110*2b7981f6SKowalski, Kamil              const std::vector<std::string>& params) override {
111*2b7981f6SKowalski, Kamil     std::vector<const std::string*> session_ids =
112*2b7981f6SKowalski, Kamil         crow::PersistentData::session_store->get_unique_ids(
113*2b7981f6SKowalski, Kamil             false, crow::PersistentData::PersistenceType::TIMEOUT);
114*2b7981f6SKowalski, Kamil 
115*2b7981f6SKowalski, Kamil     nodeJson["Members@odata.count"] = session_ids.size();
116*2b7981f6SKowalski, Kamil     nodeJson["Members"] = nlohmann::json::array();
117*2b7981f6SKowalski, Kamil     for (const auto& uid : session_ids) {
118*2b7981f6SKowalski, Kamil       nodeJson["Members"].push_back(
119*2b7981f6SKowalski, Kamil           {{"@odata.id", "/redfish/v1/SessionService/Sessions/" + *uid}});
120*2b7981f6SKowalski, Kamil     }
121*2b7981f6SKowalski, Kamil 
122*2b7981f6SKowalski, Kamil     res.json_value = nodeJson;
123*2b7981f6SKowalski, Kamil     res.end();
124*2b7981f6SKowalski, Kamil   }
125*2b7981f6SKowalski, Kamil 
126*2b7981f6SKowalski, Kamil   void doPost(crow::response& res, const crow::request& req,
127*2b7981f6SKowalski, Kamil               const std::vector<std::string>& params) override {
128*2b7981f6SKowalski, Kamil     std::string username;
129*2b7981f6SKowalski, Kamil     bool userAuthSuccessful = authenticateUser(req, &res.code, &username);
130*2b7981f6SKowalski, Kamil 
131*2b7981f6SKowalski, Kamil     if (!userAuthSuccessful) {
132*2b7981f6SKowalski, Kamil       res.end();
133*2b7981f6SKowalski, Kamil       return;
134*2b7981f6SKowalski, Kamil     }
135*2b7981f6SKowalski, Kamil 
136*2b7981f6SKowalski, Kamil     // User is authenticated - create session for him
137*2b7981f6SKowalski, Kamil     auto session =
138*2b7981f6SKowalski, Kamil         crow::PersistentData::session_store->generate_user_session(username);
139*2b7981f6SKowalski, Kamil     res.add_header("X-Auth-Token", session.session_token);
140*2b7981f6SKowalski, Kamil 
141*2b7981f6SKowalski, Kamil     // Return data for created session
142*2b7981f6SKowalski, Kamil     memberSession.doGet(res, req, {session.unique_id});
143*2b7981f6SKowalski, Kamil 
144*2b7981f6SKowalski, Kamil     // No need for res.end(), as it is called by doGet()
145*2b7981f6SKowalski, Kamil   }
146*2b7981f6SKowalski, Kamil 
147*2b7981f6SKowalski, Kamil   /**
148*2b7981f6SKowalski, Kamil    * @brief Verifies data provided in request and tries to authenticate user
149*2b7981f6SKowalski, Kamil    *
150*2b7981f6SKowalski, Kamil    * @param[in]  req            Crow request containing authentication data
151*2b7981f6SKowalski, Kamil    * @param[out] httpRespCode   HTTP Code that should be returned in response
152*2b7981f6SKowalski, Kamil    * @param[out] user           Retrieved username - not filled on failure
153*2b7981f6SKowalski, Kamil    *
154*2b7981f6SKowalski, Kamil    * @return true if authentication was successful, false otherwise
155*2b7981f6SKowalski, Kamil    */
156*2b7981f6SKowalski, Kamil   bool authenticateUser(const crow::request& req, int* httpRespCode,
157*2b7981f6SKowalski, Kamil                         std::string* user) {
158*2b7981f6SKowalski, Kamil     // We need only UserName and Password - nothing more, nothing less
159*2b7981f6SKowalski, Kamil     static constexpr const unsigned int numberOfRequiredFieldsInReq = 2;
160*2b7981f6SKowalski, Kamil 
161*2b7981f6SKowalski, Kamil     // call with exceptions disabled
162*2b7981f6SKowalski, Kamil     auto login_credentials = nlohmann::json::parse(req.body, nullptr, false);
163*2b7981f6SKowalski, Kamil     if (login_credentials.is_discarded()) {
164*2b7981f6SKowalski, Kamil       *httpRespCode = static_cast<int>(HttpRespCode::BAD_REQUEST);
165*2b7981f6SKowalski, Kamil 
166*2b7981f6SKowalski, Kamil       return false;
167*2b7981f6SKowalski, Kamil     }
168*2b7981f6SKowalski, Kamil 
169*2b7981f6SKowalski, Kamil     // Check that there are only as many fields as there should be
170*2b7981f6SKowalski, Kamil     if (login_credentials.size() != numberOfRequiredFieldsInReq) {
171*2b7981f6SKowalski, Kamil       *httpRespCode = static_cast<int>(HttpRespCode::BAD_REQUEST);
172*2b7981f6SKowalski, Kamil 
173*2b7981f6SKowalski, Kamil       return false;
174*2b7981f6SKowalski, Kamil     }
175*2b7981f6SKowalski, Kamil 
176*2b7981f6SKowalski, Kamil     // Find fields that we need - UserName and Password
177*2b7981f6SKowalski, Kamil     auto user_it = login_credentials.find("UserName");
178*2b7981f6SKowalski, Kamil     auto pass_it = login_credentials.find("Password");
179*2b7981f6SKowalski, Kamil     if (user_it == login_credentials.end() ||
180*2b7981f6SKowalski, Kamil         pass_it == login_credentials.end()) {
181*2b7981f6SKowalski, Kamil       *httpRespCode = static_cast<int>(HttpRespCode::BAD_REQUEST);
182*2b7981f6SKowalski, Kamil 
183*2b7981f6SKowalski, Kamil       return false;
184*2b7981f6SKowalski, Kamil     }
185*2b7981f6SKowalski, Kamil 
186*2b7981f6SKowalski, Kamil     // Check that given data is of valid type (string)
187*2b7981f6SKowalski, Kamil     if (!user_it->is_string() || !pass_it->is_string()) {
188*2b7981f6SKowalski, Kamil       *httpRespCode = static_cast<int>(HttpRespCode::BAD_REQUEST);
189*2b7981f6SKowalski, Kamil 
190*2b7981f6SKowalski, Kamil       return false;
191*2b7981f6SKowalski, Kamil     }
192*2b7981f6SKowalski, Kamil 
193*2b7981f6SKowalski, Kamil     // Extract username and password
194*2b7981f6SKowalski, Kamil     std::string username = user_it->get<const std::string>();
195*2b7981f6SKowalski, Kamil     std::string password = pass_it->get<const std::string>();
196*2b7981f6SKowalski, Kamil 
197*2b7981f6SKowalski, Kamil     // Verify that required fields are not empty
198*2b7981f6SKowalski, Kamil     if (username.empty() || password.empty()) {
199*2b7981f6SKowalski, Kamil       *httpRespCode = static_cast<int>(HttpRespCode::BAD_REQUEST);
200*2b7981f6SKowalski, Kamil 
201*2b7981f6SKowalski, Kamil       return false;
202*2b7981f6SKowalski, Kamil     }
203*2b7981f6SKowalski, Kamil 
204*2b7981f6SKowalski, Kamil     // Finally - try to authenticate user
205*2b7981f6SKowalski, Kamil     if (!pam_authenticate_user(username, password)) {
206*2b7981f6SKowalski, Kamil       *httpRespCode = static_cast<int>(HttpRespCode::UNAUTHORIZED);
207*2b7981f6SKowalski, Kamil 
208*2b7981f6SKowalski, Kamil       return false;
209*2b7981f6SKowalski, Kamil     }
210*2b7981f6SKowalski, Kamil 
211*2b7981f6SKowalski, Kamil     // User authenticated successfully
212*2b7981f6SKowalski, Kamil     *httpRespCode = static_cast<int>(HttpRespCode::OK);
213*2b7981f6SKowalski, Kamil     *user = username;
214*2b7981f6SKowalski, Kamil 
215*2b7981f6SKowalski, Kamil     return true;
216*2b7981f6SKowalski, Kamil   }
217*2b7981f6SKowalski, Kamil 
218*2b7981f6SKowalski, Kamil   /**
219*2b7981f6SKowalski, Kamil    * Member session to ensure consistency between collection's doPost and
220*2b7981f6SKowalski, Kamil    * member's doGet, as they should return 100% matching data
221*2b7981f6SKowalski, Kamil    */
222*2b7981f6SKowalski, Kamil   Sessions memberSession;
223*2b7981f6SKowalski, Kamil   nlohmann::json nodeJson;
224*2b7981f6SKowalski, Kamil };
225*2b7981f6SKowalski, Kamil 
226*2b7981f6SKowalski, Kamil }  // namespace redfish
227